int main(int argc, char **argv) { static VSTREAM *lock_fp; static VSTREAM *data_lock_fp; VSTRING *lock_path; VSTRING *data_lock_path; off_t inherited_limit; int debug_me = 0; int ch; int fd; int n; int test_lock = 0; VSTRING *why; WATCHDOG *watchdog; ARGV *import_env; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Initialize. */ umask(077); /* never fails! */ /* * Process environment options as early as we can. */ if (getenv(CONF_ENV_VERB)) msg_verbose = 1; if (getenv(CONF_ENV_DEBUG)) debug_me = 1; /* * Don't die when a process goes away unexpectedly. */ signal(SIGPIPE, SIG_IGN); /* * Strip and save the process name for diagnostics etc. */ var_procname = mystrdup(basename(argv[0])); /* * When running a child process, don't leak any open files that were * leaked to us by our own (privileged) parent process. Descriptors 0-2 * are taken care of after we have initialized error logging. * * Some systems such as AIX have a huge per-process open file limit. In * those cases, limit the search for potential file descriptor leaks to * just the first couple hundred. * * The Debian post-installation script passes an open file descriptor into * the master process and waits forever for someone to close it. Because * of this we have to close descriptors > 2, and pray that doing so does * not break things. */ closefrom(3); /* * Initialize logging and exit handler. */ msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * The mail system must be run by the superuser so it can revoke * privileges for selected operations. That's right - it takes privileges * to toss privileges. */ if (getuid() != 0) msg_fatal("the master command is reserved for the superuser"); if (unsafe() != 0) msg_fatal("the master command must not run as a set-uid process"); /* * Process JCL. */ while ((ch = GETOPT(argc, argv, "c:Dde:tv")) > 0) { switch (ch) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'd': master_detach = 0; break; case 'e': event_request_timer(master_exit_event, (char *) 0, atoi(optarg)); break; case 'D': debug_me = 1; break; case 't': test_lock = 1; break; case 'v': msg_verbose++; break; default: usage(argv[0]); /* NOTREACHED */ } } /* * This program takes no other arguments. */ if (argc > optind) usage(argv[0]); /* * If started from a terminal, get rid of any tty association. This also * means that all errors and warnings must go to the syslog daemon. */ if (master_detach) for (fd = 0; fd < 3; fd++) { (void) close(fd); if (open("/dev/null", O_RDWR, 0) != fd) msg_fatal("open /dev/null: %m"); } /* * Run in a separate process group, so that "postfix stop" can terminate * all MTA processes cleanly. Give up if we can't separate from our * parent process. We're not supposed to blow away the parent. */ if (debug_me == 0 && master_detach != 0 && setsid() == -1 && getsid(0) != getpid()) msg_fatal("unable to set session and process group ID: %m"); /* * Make some room for plumbing with file descriptors. XXX This breaks * when a service listens on many ports. In order to do this right we * must change the master-child interface so that descriptors do not need * to have fixed numbers. * * In a child we need two descriptors for the flow control pipe, one for * child->master status updates and at least one for listening. */ for (n = 0; n < 5; n++) { if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) msg_fatal("dup(0): %m"); } /* * Final initializations. Unfortunately, we must read the global Postfix * configuration file after doing command-line processing, so that we get * consistent results when we SIGHUP the server to reload configuration * files. */ master_vars_init(); /* * In case of multi-protocol support. This needs to be done because * master does not invoke mail_params_init() (it was written before that * code existed). */ (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); /* * Environment import filter, to enforce consistent behavior whether * Postfix is started by hand, or at system boot time. */ import_env = argv_split(var_import_environ, ", \t\r\n"); clean_env(import_env->argv); argv_free(import_env); if ((inherited_limit = get_file_limit()) < 0) set_file_limit(OFF_T_MAX); if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); /* * Lock down the master.pid file. In test mode, no file means that it * isn't locked. */ lock_path = vstring_alloc(10); data_lock_path = vstring_alloc(10); why = vstring_alloc(10); vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname); if (test_lock && access(vstring_str(lock_path), F_OK) < 0) exit(0); lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why); if (test_lock) exit(lock_fp ? 0 : 1); if (lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(lock_path), vstring_str(why)); vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path)); close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC); /* * Lock down the Postfix-writable data directory. */ vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname); set_eugid(var_owner_uid, var_owner_gid); data_lock_fp = open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why); set_ugid(getuid(), getgid()); if (data_lock_fp == 0) msg_fatal("open lock file %s: %s", vstring_str(data_lock_path), vstring_str(why)); vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, (unsigned long) var_pid); if (vstream_fflush(data_lock_fp)) msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path)); close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC); /* * Clean up. */ vstring_free(why); vstring_free(lock_path); vstring_free(data_lock_path); /* * Optionally start the debugger on ourself. */ if (debug_me) debug_process(); /* * Finish initialization, last part. We must process configuration files * after processing command-line parameters, so that we get consistent * results when we SIGHUP the server to reload configuration files. */ master_config(); master_sigsetup(); master_flow_init(); msg_info("daemon started -- version %s, configuration %s", var_mail_version, var_config_dir); /* * Process events. The event handler will execute the read/write/timer * action routines. Whenever something has happened, see if we received * any signal in the mean time. Although the master process appears to do * multiple things at the same time, it really is all a single thread, so * that there are no concurrency conflicts within the master process. */ #define MASTER_WATCHDOG_TIME 1000 watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (char *) 0); for (;;) { #ifdef HAS_VOLATILE_LOCKS if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("refresh exclusive lock: %m"); #endif watchdog_start(watchdog); /* same as trigger servers */ event_loop(MASTER_WATCHDOG_TIME / 2); if (master_gotsighup) { msg_info("reload -- version %s, configuration %s", var_mail_version, var_config_dir); master_gotsighup = 0; /* this first */ master_vars_init(); /* then this */ master_refresh(); /* then this */ } if (master_gotsigchld) { if (msg_verbose) msg_info("got sigchld"); master_gotsigchld = 0; /* this first */ master_reap_child(); /* then this */ } } }
static void enqueue(const int flags, const char *encoding, const char *dsn_envid, int dsn_ret, int dsn_notify, const char *rewrite_context, const char *sender, const char *full_name, char **recipients) { VSTRING *buf; VSTREAM *dst; char *saved_sender; char **cpp; int type; char *start; int skip_from_; TOK822 *tree; TOK822 *tp; int rcpt_count = 0; enum { STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR } strip_cr; MAIL_STREAM *handle; VSTRING *postdrop_command; uid_t uid = getuid(); int status; int naddr; int prev_type; MIME_STATE *mime_state = 0; SM_STATE state; int mime_errs; const char *errstr; int addr_count; int level; static NAME_CODE sm_fix_eol_table[] = { SM_FIX_EOL_ALWAYS, STRIP_CR_DO, SM_FIX_EOL_STRICT, STRIP_CR_DUNNO, SM_FIX_EOL_NEVER, STRIP_CR_DONT, 0, STRIP_CR_ERROR, }; /* * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Initialize. */ buf = vstring_alloc(100); /* * 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); /* * The sender name is provided by the user. In principle, the mail pickup * service could deduce the sender name from queue file ownership, but: * pickup would not be able to run chrooted, and it may not be desirable * to use login names at all. */ if (sender != 0) { VSTRING_RESET(buf); VSTRING_TERMINATE(buf); tree = tok822_parse(sender); for (naddr = 0, tp = tree; tp != 0; tp = tp->next) if (tp->type == TOK822_ADDR && naddr++ == 0) tok822_internalize(buf, tp->head, TOK822_STR_DEFL); tok822_free_tree(tree); saved_sender = mystrdup(STR(buf)); if (naddr > 1) msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", (unsigned long) uid); saved_sender = mystrdup(sender); } /* * Let the postdrop command open the queue file for us, and sanity check * the content. XXX Make postdrop a manifest constant. */ errno = 0; postdrop_command = vstring_alloc(1000); vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); for (level = 0; level < msg_verbose; level++) vstring_strcat(postdrop_command, " -v"); if ((handle = mail_stream_command(STR(postdrop_command))) == 0) msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", saved_sender, (long) uid, STR(postdrop_command)); vstring_free(postdrop_command); dst = handle->stream; /* * First, write envelope information to the output stream. * * For sendmail compatibility, parse each command-line recipient as if it * were an RFC 822 message header; some MUAs specify comma-separated * recipient lists; and some MUAs even specify "word word <address>". * * Sort-uniq-ing the recipient list is done after address canonicalization, * before recipients are written to queue file. That's cleaner than * having the queue manager nuke duplicate recipient status records. * * XXX Should limit the size of envelope records. * * With "sendmail -N", instead of a per-message NOTIFY record we store one * per recipient so that we can simplify the implementation somewhat. */ if (dsn_envid) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_DSN_ENVID, dsn_envid); if (dsn_ret) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_RET, dsn_ret); rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_RWR_CONTEXT, rewrite_context); if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); if (verp_delims && *saved_sender == 0) msg_fatal_status(EX_USAGE, "%s(%ld): -V option requires non-null sender address", saved_sender, (long) uid); if (encoding) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); if (DEL_REQ_TRACE_FLAGS(flags)) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, DEL_REQ_TRACE_FLAGS(flags)); if (verp_delims) rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; ++addr_count; } } tok822_free_tree(tree); if (addr_count == 0) { if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } } } /* * Append the message contents to the queue file. Write chunks of at most * 1kbyte. Internally, we use different record types for data ending in * LF and for data that doesn't, so we can actually be binary transparent * for local mail. Unfortunately, SMTP has no record continuation * convention, so there is no guarantee that arbitrary data will be * delivered intact via SMTP. Strip leading From_ lines. For the benefit * of UUCP environments, also get rid of leading >>>From_ lines. */ rec_fputs(dst, REC_TYPE_MESG, ""); if (DEL_REQ_TRACE_ONLY(flags) != 0) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", saved_sender, (long) uid); if (*saved_sender) rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); if (recipients) { rec_fprintf(dst, REC_TYPE_CONT, "To:"); for (cpp = recipients; *cpp != 0; cpp++) { rec_fprintf(dst, REC_TYPE_NORM, " %s%s", *cpp, cpp[1] ? "," : ""); } } } else { /* * Initialize the MIME processor and set up the callback context. */ if (flags & SM_FLAG_XRCPT) { state.dst = dst; state.recipients = argv_alloc(2); state.resent_recip = argv_alloc(2); state.resent = 0; state.saved_sender = saved_sender; state.uid = uid; state.temp = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME | MIME_OPT_REPORT_TRUNC_HEADER, output_header, (MIME_STATE_ANY_END) 0, output_text, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &state); } /* * Process header/body lines. */ skip_from_ = 1; strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE, var_sm_fix_eol); if (strip_cr == STRIP_CR_ERROR) msg_fatal_status(EX_USAGE, "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol); for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) != REC_TYPE_EOF; prev_type = type) { if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') strip_cr = STRIP_CR_DO; else strip_cr = STRIP_CR_DONT; } if (skip_from_) { if (type == REC_TYPE_NORM) { start = STR(buf); if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) continue; } skip_from_ = 0; } if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') vstring_truncate(buf, VSTRING_LEN(buf) - 1); if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (mime_state) { mime_errs = mime_state_update(mime_state, type, STR(buf), VSTRING_LEN(buf)); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); } else { if (REC_PUT_BUF(dst, type, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); } } } /* * Finish MIME processing. We need a final mime_state_update() call in * order to flush text that is still buffered. That can happen when the * last line did not end in newline. */ if (mime_state) { mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); mime_state = mime_state_free(mime_state); } /* * Append recipient addresses that were extracted from message headers. */ rec_fputs(dst, REC_TYPE_XTRA, ""); if (flags & SM_FLAG_XRCPT) { for (cpp = state.resent ? state.resent_recip->argv : state.recipients->argv; *cpp; cpp++) { if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } argv_free(state.recipients); argv_free(state.resent_recip); vstring_free(state.temp); } if (rcpt_count == 0) msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? "%s(%ld): No recipient addresses found in message header" : "%s(%ld): Recipient addresses must be specified on" " the command line or via the -t option", saved_sender, (long) uid); /* * Identify the end of the queue file. */ rec_fputs(dst, REC_TYPE_END, ""); /* * Make sure that the message makes it to the file system. Once we have * terminated with successful exit status we cannot lose the message due * to "frivolous reasons". If all goes well, prevent the run-time error * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", saved_sender, (long) uid); if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, (long) uid, cleanup_strerror(status)); /* * Don't leave them in the dark. */ if (DEL_REQ_TRACE_FLAGS(flags)) { vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", saved_sender); vstream_fflush(VSTREAM_OUT); } /* * Cleanup. Not really necessary as we're about to exit, but good for * debugging purposes. */ vstring_free(buf); myfree(saved_sender); }
/** *************************************************************************** * main() ;-) * */ int main(int argc, char * argv[]) { stats_main_start = get_current_time_millis(); int rv = 0; pthread_key_create(&thread_name, NULL); pthread_setspecific(thread_name, (char *)"[MAIN] "); pthread_key_create(&duplicate_path_buffer, NULL); rv = process_args(argc, argv); LOG(L_PROGRESS, "Log level: %s\n", log_level_name[log_level]); // If process_args returns non-zero it means we need to exit right away // with an exit code one less than the returned value. Need to exit via // the DONE section both to free any memory that may have been allocated // already and also to properly return (not exit) from main (see below). if (rv) { rv--; goto DONE; } // If bad --path values given, don't try to process them. Arguably one // could process the good ones (if any) but better to flag the path // error up front instead of spending time doing a partial scan. if (start_path_state == START_PATH_ERROR) { rv = 1; goto DONE; } LOG(L_INFO, "Claimed CPU cores: %d\n", cpu_cores()); max_open_files = get_file_limit() - 10; LOG(L_INFO, "Max open files: %d\n", max_open_files); signal(SIGUSR1, &handle_signal); signal(SIGUSR2, &handle_signal); switch (operation) { case COMMAND_scan: scan(); break; case COMMAND_refresh: operation_refresh(); break; case COMMAND_report: operation_report(); break; case COMMAND_uniques: operation_uniques(); break; case COMMAND_license: show_license(); break; case COMMAND_version: printf(DUPD_VERSION "\n"); break; case COMMAND_dups: operation_dups(); break; case COMMAND_file: operation_file(); break; case COMMAND_ls: operation_ls(); break; case COMMAND_rmsh: operation_shell_script(); break; case COMMAND_validate: rv = operation_validate(); break; case COMMAND_usage: show_usage(); break; case COMMAND_man: show_usage(); break; case COMMAND_help: show_help(); break; case COMMAND_testing: testing(); break; case COMMAND_hash: operation_hash_file(); break; case OPTGEN_NO_COMMAND: show_help(); rv = 1; break; default: // LCOV_EXCL_START printf("error: unknown operation [%d]\n", operation); rv = 1; } // LCOV_EXCL_STOP DONE: if (free_file_path) { free(file_path); } if (free_db_path) { free(db_path); } if (free_cache_db_path) { free(cache_db_path); } if (path_sep_string) { free(path_sep_string); } free_size_tree(); free_size_list(); free_path_block(); free_filecompare(); free_scanlist(); free_start_paths(); free_read_list(); free_dirtree(); free_path_buffer(); stats_time_total = get_current_time_millis() - stats_main_start; LOG(L_PROGRESS, "Total time: %ld ms\n", stats_time_total); if (stats_file != NULL) { save_stats(); } if (log_level >= 0) { if (operation == COMMAND_scan || operation == COMMAND_refresh || operation == COMMAND_license || operation == COMMAND_version || operation == COMMAND_validate || operation == COMMAND_usage || operation == COMMAND_man || operation == COMMAND_help) { if (!strcmp("dev", DUPD_VERSION + strlen(DUPD_VERSION) - 3)) { if (isatty(fileno(stdout))) { fprintf(stdout, "\nNote: This is a development version of dupd (" DUPD_VERSION ") (" GITHASH ")\n"); fprintf(stdout, "May contain known bugs or unstable work in progress!\n"); fprintf(stdout, "If stability is desired, use a release version of dupd.\n"); } } } } // Call return() instead of exit() just to make valgrind mark as // an error any reachable allocations. That makes them show up // when running the tests. return(rv); }
int mail_copy(const char *sender, const char *orig_rcpt, const char *delivered, VSTREAM *src, VSTREAM *dst, int flags, const char *eol, DSN_BUF *why) { const char *myname = "mail_copy"; VSTRING *buf; char *bp; off_t orig_length; int read_error; int write_error; int corrupt_error = 0; time_t now; int type; int prev_type; struct stat st; off_t size_limit; /* * Workaround 20090114. This will hopefully get someone's attention. The * problem with file_size_limit < message_size_limit is that mail will be * delivered again and again until someone removes it from the queue by * hand, because Postfix cannot mark a recipient record as "completed". */ if (fstat(vstream_fileno(src), &st) < 0) msg_fatal("fstat: %m"); if ((size_limit = get_file_limit()) < st.st_size) msg_panic("file size limit %lu < message size %lu. This " "causes large messages to be delivered repeatedly " "after they were submitted with \"sendmail -t\" " "or after recipients were added with the Milter " "SMFIR_ADDRCPT request", (unsigned long) size_limit, (unsigned long) st.st_size); /* * Initialize. */ #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(dst)); #endif buf = vstring_alloc(100); /* * Prepend a bunch of headers to the message. */ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) { if (sender == 0) msg_panic("%s: null sender", myname); quote_822_local(buf, sender); if (flags & MAIL_COPY_FROM) { time(&now); vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ? MAIL_ADDR_MAIL_DAEMON : vstring_str(buf), asctime(localtime(&now)), eol); } if (flags & MAIL_COPY_RETURN_PATH) { vstream_fprintf(dst, "Return-Path: <%s>%s", *sender ? vstring_str(buf) : "", eol); } } if (flags & MAIL_COPY_ORIG_RCPT) { if (orig_rcpt == 0) msg_panic("%s: null orig_rcpt", myname); /* * An empty original recipient record almost certainly means that * original recipient processing was disabled. */ if (*orig_rcpt) { quote_822_local(buf, orig_rcpt); vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); } } if (flags & MAIL_COPY_DELIVERED) { if (delivered == 0) msg_panic("%s: null delivered", myname); quote_822_local(buf, delivered); vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol); } /* * Copy the message. Escape lines that could be confused with the ugly * From_ line. Make sure that there is a blank line at the end of the * message so that the next ugly From_ can be found by mail reading * software. * * XXX Rely on the front-end services to enforce record size limits. */ #define VSTREAM_FWRITE_BUF(s,b) \ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b)) prev_type = REC_TYPE_NORM; while ((type = rec_get(src, buf, 0)) > 0) { if (type != REC_TYPE_NORM && type != REC_TYPE_CONT) break; bp = vstring_str(buf); if (prev_type == REC_TYPE_NORM) { if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5)) VSTREAM_PUTC('>', dst); if ((flags & MAIL_COPY_DOT) && *bp == '.') VSTREAM_PUTC('.', dst); } if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf)) break; if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF) break; prev_type = type; } if (vstream_ferror(dst) == 0) { if (var_fault_inj_code == 1) type = 0; if (type != REC_TYPE_XTRA) { /* XXX Where is the queue ID? */ msg_warn("bad record type: %d in message content", type); corrupt_error = mark_corrupt(src); } if (prev_type != REC_TYPE_NORM) vstream_fputs(eol, dst); if (flags & MAIL_COPY_BLANK) vstream_fputs(eol, dst); } vstring_free(buf); /* * Make sure we read and wrote all. Truncate the file to its original * length when the delivery failed. POSIX does not require ftruncate(), * so we may have a portability problem. Note that fclose() may fail even * while fflush and fsync() succeed. Think of remote file systems such as * AFS that copy the file back to the server upon close. Oh well, no * point optimizing the error case. XXX On systems that use flock() * locking, we must truncate the file file before closing it (and losing * the exclusive lock). */ read_error = vstream_ferror(src); write_error = vstream_fflush(dst); #ifdef HAS_FSYNC if ((flags & MAIL_COPY_TOFILE) != 0) write_error |= fsync(vstream_fileno(dst)); #endif if (var_fault_inj_code == 2) { read_error = 1; errno = ENOENT; } if (var_fault_inj_code == 3) { write_error = 1; errno = ENOENT; } #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if (corrupt_error || read_error || write_error) /* Complain about ignored "undo" errors? So sue me. */ (void) ftruncate(vstream_fileno(dst), orig_length); #endif write_error |= vstream_fclose(dst); /* * Return the optional verbose error description. */ #define TRY_AGAIN_ERROR(errno) \ (errno == EAGAIN || errno == ESTALE) if (why && read_error) dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0", sys_exits_detail(EX_IOERR)->text, "error reading message: %m"); if (why && write_error) dsb_unix(why, mbox_dsn(errno, "5.3.0"), sys_exits_detail(EX_IOERR)->text, "error writing message: %m"); /* * Use flag+errno description when the optional verbose description is * not desired. */ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) | (read_error ? MAIL_COPY_STAT_READ : 0) | (write_error ? MAIL_COPY_STAT_WRITE : 0)); }
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); }