static void tlsp_service(VSTREAM *plaintext_stream, char *service, char **argv) { TLSP_STATE *state; int plaintext_fd = vstream_fileno(plaintext_stream); /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This program handles multiple connections, so it must not block. We * use event-driven code for all operations that introduce latency. * Except that attribute lists are sent/received synchronously, once the * socket is found to be ready for transmission. */ non_blocking(plaintext_fd, NON_BLOCKING); vstream_control(plaintext_stream, CA_VSTREAM_CTL_PATH("plaintext"), CA_VSTREAM_CTL_TIMEOUT(5), CA_VSTREAM_CTL_END); /* * Receive postscreen's remote SMTP client address/port and socket. */ state = tlsp_state_create(service, plaintext_stream); event_enable_read(plaintext_fd, tlsp_get_request_event, (void *) state); event_request_timer(tlsp_get_request_event, (void *) state, TLSP_INIT_TIMEOUT); }
static void single_server_wakeup(int fd, HTABLE *attr) { VSTREAM *stream; char *tmp; /* * If the accept() succeeds, be sure to disable non-blocking I/O, because * the application is supposed to be single-threaded. Notice the master * of our (un)availability to service connection requests. Commit suicide * when the master process disconnected from us. Don't drop the already * accepted client request after "postfix reload"; that would be rude. */ if (msg_verbose) msg_info("connection established"); non_blocking(fd, BLOCKING); close_on_exec(fd, CLOSE_ON_EXEC); stream = vstream_fdopen(fd, O_RDWR); tmp = concatenate(single_server_name, " socket", (char *) 0); vstream_control(stream, CA_VSTREAM_CTL_PATH(tmp), CA_VSTREAM_CTL_CONTEXT((void *) attr), CA_VSTREAM_CTL_END); myfree(tmp); timed_ipc_setup(stream); if (master_notify(var_pid, single_server_generation, MASTER_STAT_TAKEN) < 0) /* void */ ; if (single_server_in_flow_delay && mail_flow_get(1) < 0) doze(var_in_flow_delay * 1000000); single_server_service(stream, single_server_name, single_server_argv); (void) vstream_fclose(stream); if (master_notify(var_pid, single_server_generation, MASTER_STAT_AVAIL) < 0) single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); if (msg_verbose) msg_info("connection closed"); /* Avoid integer wrap-around in a persistent process. */ if (use_count < INT_MAX) use_count++; if (var_idle_limit > 0) event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); if (attr) htable_free(attr, myfree); }
static void multi_server_wakeup(int fd, HTABLE *attr) { VSTREAM *stream; char *tmp; #if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT) #ifndef THRESHOLD_FD_WORKAROUND #define THRESHOLD_FD_WORKAROUND 128 #endif int new_fd; /* * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely * case of a multi-server with a thousand clients. */ if (fd < THRESHOLD_FD_WORKAROUND) { if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0) msg_fatal("fcntl F_DUPFD: %m"); (void) close(fd); fd = new_fd; } #endif if (msg_verbose) msg_info("connection established fd %d", fd); non_blocking(fd, BLOCKING); close_on_exec(fd, CLOSE_ON_EXEC); client_count++; stream = vstream_fdopen(fd, O_RDWR); tmp = concatenate(multi_server_name, " socket", (char *) 0); vstream_control(stream, CA_VSTREAM_CTL_PATH(tmp), CA_VSTREAM_CTL_CONTEXT((void *) attr), CA_VSTREAM_CTL_END); myfree(tmp); timed_ipc_setup(stream); multi_server_saved_flags = vstream_flags(stream); if (multi_server_in_flow_delay && mail_flow_get(1) < 0) event_request_timer(multi_server_enable_read, (void *) stream, var_in_flow_delay); else multi_server_enable_read(0, (void *) stream); }
static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp) { const char *myname = "xsasl_dovecot_server_connect"; VSTRING *line_str; VSTREAM *sasl_stream; char *line, *cmd, *mech_name; unsigned int major_version, minor_version; int fd, success, have_mech_line; int sec_props; const char *path; if (msg_verbose) msg_info("%s: Connecting", myname); /* * Not documented, but necessary for testing. */ path = xp->socket_path; if (strncmp(path, "inet:", 5) == 0) { fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT); } else { if (strncmp(path, "unix:", 5) == 0) path += 5; fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT); } if (fd < 0) { msg_warn("SASL: Connect to %s failed: %m", xp->socket_path); return (-1); } sasl_stream = vstream_fdopen(fd, O_RDWR); vstream_control(sasl_stream, CA_VSTREAM_CTL_PATH(xp->socket_path), CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT), CA_VSTREAM_CTL_END); /* XXX Encapsulate for logging. */ vstream_fprintf(sasl_stream, "VERSION\t%u\t%u\n" "CPID\t%u\n", AUTH_PROTOCOL_MAJOR_VERSION, AUTH_PROTOCOL_MINOR_VERSION, (unsigned int) getpid()); if (vstream_fflush(sasl_stream) == VSTREAM_EOF) { msg_warn("SASL: Couldn't send handshake: %m"); return (-1); } success = 0; have_mech_line = 0; line_str = vstring_alloc(256); /* XXX Encapsulate for logging. */ while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) { line = vstring_str(line_str); if (msg_verbose) msg_info("%s: auth reply: %s", myname, line); cmd = line; line = split_at(line, '\t'); if (strcmp(cmd, "VERSION") == 0) { if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) { msg_warn("SASL: Protocol version error"); break; } if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) { /* Major version is different from ours. */ msg_warn("SASL: Protocol version mismatch (%d vs. %d)", major_version, AUTH_PROTOCOL_MAJOR_VERSION); break; } } else if (strcmp(cmd, "MECH") == 0 && line != NULL) { mech_name = line; have_mech_line = 1; line = split_at(line, '\t'); if (line != 0) { sec_props = name_mask_delim_opt(myname, xsasl_dovecot_serv_sec_props, line, "\t", NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); if ((sec_props & SEC_PROPS_PRIVATE) != 0) continue; } else sec_props = 0; xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name, sec_props); } else if (strcmp(cmd, "SPID") == 0) { /* * Unfortunately the auth protocol handshake wasn't designed well * to differentiate between auth-client/userdb/master. * auth-userdb and auth-master send VERSION + SPID lines only and * nothing afterwards, while auth-client sends VERSION + MECH + * SPID + CUID + more. The simplest way that we can determine if * we've connected to the correct socket is to see if MECH line * exists or not (alternatively we'd have to have a small timeout * after SPID to see if CUID is sent or not). */ if (!have_mech_line) { msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)"); break; } } else if (strcmp(cmd, "DONE") == 0) { /* Handshake finished. */ success = 1; break; } else { /* ignore any unknown commands */ } } vstring_free(line_str); if (!success) { /* handshake failed */ (void) vstream_fclose(sasl_stream); return (-1); } xp->sasl_stream = sasl_stream; return (0); }
static void postmap(char *map_type, char *path_name, int postmap_flags, int open_flags, int dict_flags) { VSTREAM *NOCLOBBER source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; int last_line; char *key; char *value; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { /* Incremental mode. */ source_fp = VSTREAM_IN; vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); } else { /* Create database. */ if (strcmp(map_type, DICT_TYPE_PROXY) == 0) msg_fatal("can't create maps via the proxy service"); dict_flags |= DICT_FLAG_BULK_UPDATE; if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postmap does not * allow privilege escalation. */ if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, optionally create it when it does not exist, * optionally truncate it when it does exist, and lock out any * spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Trap "exceptions" so that we can restart a bulk-mode update after a * recoverable error. */ for (;;) { if (dict_isjmp(mkmap->dict) != 0 && dict_setjmp(mkmap->dict) != 0 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* * Add records to the database. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { /* * First some UTF-8 checks sans casefolding. */ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) && !allascii(STR(line_buffer)) && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { msg_warn("%s, line %d: non-UTF-8 input \"%s\"" " -- ignoring this line", VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); continue; } /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ key = STR(line_buffer); value = key + strcspn(key, CHARS_SPACE); if (*value) *value++ = 0; while (ISSPACE(*value)) value++; trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; /* * Enforce the "key whitespace value" format. Disallow missing * keys or missing values. */ if (*key == 0 || *value == 0) { msg_warn("%s, line %d: expected format: key whitespace value", VSTREAM_PATH(source_fp), lineno); continue; } if (key[strlen(key) - 1] == ':') msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?", VSTREAM_PATH(source_fp), lineno); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, key, value); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } break; } /* * Close the mapping database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }
int main(int argc, char **argv) { struct stat st; int fd; int c; VSTRING *buf; int status; MAIL_STREAM *dst; int rec_type; static char *segment_info[] = { REC_TYPE_POST_ENVELOPE, REC_TYPE_POST_CONTENT, REC_TYPE_POST_EXTRACT, "" }; char **expected; uid_t uid = getuid(); ARGV *import_env; const char *error_text; char *attr_name; char *attr_value; const char *errstr; char *junk; struct timeval start; int saved_errno; int from_count = 0; int rcpt_count = 0; int validate_input = 1; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal("open /dev/null: %m"); /* * Set up logging. Censor the process name: it is provided by the user. */ argv[0] = "postdrop"; msg_vstream_init(argv[0], VSTREAM_ERR); msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Parse JCL. This program is set-gid and must sanitize all command-line * arguments. The configuration directory argument is validated by the * mail configuration read routine. Don't do complex things until we have * completed initializations. */ while ((c = GETOPT(argc, argv, "c:rv")) > 0) { switch (c) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'r': /* forward compatibility */ break; case 'v': if (geteuid() == 0) msg_verbose++; break; default: msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]); } } /* * Read the global configuration file and extract configuration * information. Some claim that the user should supply the working * directory instead. That might be OK, given that this command needs * write permission in a subdirectory called "maildrop". However we still * need to reliably detect incomplete input, and so we must perform * record-level I/O. With that, we should also take the opportunity to * perform some sanity checks on the input. */ mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("postdrop"), LOG_PID, LOG_FACILITY); get_mail_conf_str_table(str_table); /* * Mail submission access control. Should this be in the user-land gate, * or in the daemon process? */ mail_dict_init(); if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal("User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * This program is installed with setgid privileges. Strip the process * environment so that we don't have to trust the C library. */ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); clean_env(import_env->argv); argv_free(import_env); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); if (msg_verbose) msg_info("chdir %s", var_queue_dir); /* * Set up signal handlers and a runtime error handler so that we can * clean up incomplete output. * * postdrop_sig() uses the in-kernel SIGINT handler address as an atomic * variable to prevent nested postdrop_sig() calls. For this reason, the * SIGINT handler must be configured before other signal handlers are * allowed to invoke postdrop_sig(). */ signal(SIGPIPE, SIG_IGN); signal(SIGXFSZ, SIG_IGN); signal(SIGINT, postdrop_sig); signal(SIGQUIT, postdrop_sig); if (signal(SIGTERM, SIG_IGN) == SIG_DFL) signal(SIGTERM, postdrop_sig); if (signal(SIGHUP, SIG_IGN) == SIG_DFL) signal(SIGHUP, postdrop_sig); msg_cleanup(postdrop_cleanup); /* End of initializations. */ /* * Don't trust the caller's time information. */ GETTIMEOFDAY(&start); /* * Create queue file. mail_stream_file() never fails. Send the queue ID * to the caller. Stash away a copy of the queue file name so we can * clean up in case of a fatal error or an interrupt. */ dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC, var_pickup_service, 0444); attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, SEND_ATTR_STR(MAIL_ATTR_QUEUEID, dst->id), ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); postdrop_path = mystrdup(VSTREAM_PATH(dst->stream)); /* * Copy stdin to file. The format is checked so that we can recognize * incomplete input and cancel the operation. With the sanity checks * applied here, the pickup daemon could skip format checks and pass a * file descriptor to the cleanup daemon. These are by no means all * sanity checks - the cleanup service and queue manager services will * reject messages that lack required information. * * If something goes wrong, slurp up the input before responding to the * client, otherwise the client will give up after detecting SIGPIPE. * * Allow attribute records if the attribute specifies the MIME body type * (sendmail -B). */ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); buf = vstring_alloc(100); expected = segment_info; /* Override time information from the untrusted caller. */ rec_fprintf(dst->stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(start)); for (;;) { /* Don't allow PTR records. */ rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE); if (rec_type == REC_TYPE_EOF) { /* request cancelled */ mail_stream_cleanup(dst); if (remove(postdrop_path)) msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path); else if (msg_verbose) msg_info("remove %s", postdrop_path); myfree(postdrop_path); postdrop_path = 0; exit(0); } if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); if (strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); if (rec_type == **expected) expected++; /* Override time information from the untrusted caller. */ if (rec_type == REC_TYPE_TIME) continue; /* Check these at submission time instead of pickup time. */ if (rec_type == REC_TYPE_FROM) from_count++; if (rec_type == REC_TYPE_RCPT) rcpt_count++; /* Limit the attribute types that users may specify. */ if (rec_type == REC_TYPE_ATTR) { if ((error_text = split_nameval(vstring_str(buf), &attr_name, &attr_value)) != 0) { msg_warn("uid=%ld: ignoring malformed record: %s: %.200s", (long) uid, error_text, vstring_str(buf)); continue; } #define STREQ(x,y) (strcmp(x,y) == 0) if ((STREQ(attr_name, MAIL_ATTR_ENCODING) && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT) || STREQ(attr_value, MAIL_ATTR_ENC_8BIT) || STREQ(attr_value, MAIL_ATTR_ENC_NONE))) || STREQ(attr_name, MAIL_ATTR_DSN_ENVID) || STREQ(attr_name, MAIL_ATTR_DSN_NOTIFY) || rec_attr_map(attr_name) || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT) && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL) || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE))) || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */ rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s", attr_name, attr_value); } else { msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s", (long) uid, attr_name, attr_value); } continue; } if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) { /* rec_get() errors must not clobber errno. */ saved_errno = errno; while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE)) != REC_TYPE_END && rec_type != REC_TYPE_EOF) if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); validate_input = 0; errno = saved_errno; break; } if (rec_type == REC_TYPE_END) break; } vstring_free(buf); /* * As of Postfix 2.7 the pickup daemon discards mail without recipients. * Such mail may enter the maildrop queue when "postsuper -r" is invoked * before the queue manager deletes an already delivered message. Looking * at file ownership is not a good way to make decisions on what mail to * discard. Instead, the pickup server now requires that new submissions * always have at least one recipient record. * * The Postfix sendmail command already rejects mail without recipients. * However, in the future postdrop may receive mail via other programs, * so we add a redundant recipient check here for future proofing. * * The test for the sender address is just for consistency of error * reporting (report at submission time instead of pickup time). Besides * the segment terminator records, there aren't any other mandatory * records in a Postfix submission queue file. */ if (validate_input && (from_count == 0 || rcpt_count == 0)) { status = CLEANUP_STAT_BAD; mail_stream_cleanup(dst); } /* * Finish the file. */ else if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0) { msg_warn("uid=%ld: %m", (long) uid); postdrop_cleanup(); } /* * Disable deletion on fatal error before reporting success, so the file * will not be deleted after we have taken responsibility for delivery. */ if (postdrop_path) { junk = postdrop_path; postdrop_path = 0; myfree(junk); } /* * Send the completion status to the caller and terminate. */ attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), SEND_ATTR_STR(MAIL_ATTR_WHY, ""), ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); exit(status); }
static void postalias(char *map_type, char *path_name, int postalias_flags, int open_flags, int dict_flags) { VSTREAM *NOCLOBBER source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; int last_line; VSTRING *key_buffer; VSTRING *value_buffer; TOK822 *tok_list; TOK822 *key_list; TOK822 *colon; TOK822 *value_list; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); key_buffer = vstring_alloc(100); value_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { /* Incremental mode. */ source_fp = VSTREAM_IN; vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); } else { /* Create database. */ if (strcmp(map_type, DICT_TYPE_PROXY) == 0) msg_fatal("can't create maps via the proxy service"); dict_flags |= DICT_FLAG_BULK_UPDATE; if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postalias does not * allow privilege escalation. */ if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, create it when it does not exist, truncate it when * it does exist, and lock out any spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Trap "exceptions" so that we can restart a bulk-mode update after a * recoverable error. */ for (;;) { if (dict_isjmp(mkmap->dict) != 0 && dict_setjmp(mkmap->dict) != 0 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* * Add records to the database. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { /* * First some UTF-8 checks sans casefolding. */ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) && !allascii(STR(line_buffer)) && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { msg_warn("%s, line %d: non-UTF-8 input \"%s\"" " -- ignoring this line", VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); continue; } /* * Tokenize the input, so that we do the right thing when a * quoted localpart contains special characters such as "@", ":" * and so on. */ if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0) continue; /* * Enforce the key:value format. Disallow missing keys, * multi-address keys, or missing values. In order to specify an * empty string or value, enclose it in double quotes. */ if ((colon = tok822_find_type(tok_list, ':')) == 0 || colon->prev == 0 || colon->next == 0 || tok822_rfind_type(colon, ',')) { msg_warn("%s, line %d: need name:value pair", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Key must be local. XXX We should use the Postfix rewriting and * resolving services to handle all address forms correctly. * However, we can't count on the mail system being up when the * alias database is being built, so we're guessing a bit. */ if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) { msg_warn("%s, line %d: name must be local", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Split the input into key and value parts, and convert from * token representation back to string representation. Convert * the key to internal (unquoted) form, because the resolver * produces addresses in internal form. Convert the value to * external (quoted) form, because it will have to be re-parsed * upon lookup. Discard the token representation when done. */ key_list = tok_list; tok_list = 0; value_list = tok822_cut_after(colon); tok822_unlink(colon); tok822_free(colon); tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL); tok822_free_tree(key_list); tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL); tok822_free_tree(value_list); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, STR(key_buffer), STR(value_buffer)); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } break; } /* * Update or append sendmail and NIS signatures. */ if ((open_flags & O_TRUNC) == 0) mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE; /* * Sendmail compatibility: add the @:@ signature to indicate that the * database is complete. This might be needed by NIS clients running * sendmail. */ mkmap_append(mkmap, "@", "@"); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); /* * NIS compatibility: add time and master info. Unlike other information, * this information MUST be written without a trailing null appended to * key or value. */ mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL; mkmap->dict->flags |= DICT_FLAG_TRY0NULL; vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0)); #if (defined(HAS_NIS) || defined(HAS_NISPLUS)) mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX; mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer)); mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname); #endif /* * Close the alias database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(value_buffer); vstring_free(key_buffer); vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }