diff options
Diffstat (limited to 'src/master/master.c')
-rw-r--r-- | src/master/master.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/src/master/master.c b/src/master/master.c new file mode 100644 index 0000000..1fc3fe9 --- /dev/null +++ b/src/master/master.c @@ -0,0 +1,598 @@ +/*++ +/* NAME +/* master 8 +/* SUMMARY +/* Postfix master process +/* SYNOPSIS +/* \fBmaster\fR [\fB-Dditvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR] +/* DESCRIPTION +/* The \fBmaster\fR(8) daemon is the resident process that runs Postfix +/* daemons on demand: daemons to send or receive messages via the +/* network, daemons to deliver mail locally, etc. These daemons are +/* created on demand up to a configurable maximum number per service. +/* +/* Postfix daemons terminate voluntarily, either after being idle for +/* a configurable amount of time, or after having serviced a +/* configurable number of requests. Exceptions to this rule are the +/* resident queue manager, address verification server, and the TLS +/* session cache and pseudo-random number server. +/* +/* The behavior of the \fBmaster\fR(8) daemon is controlled by the +/* \fBmaster.cf\fR configuration file, as described in \fBmaster\fR(5). +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in +/* the named directory instead of the default configuration directory. +/* This also overrides the configuration files for other Postfix +/* daemon processes. +/* .IP \fB-D\fR +/* After initialization, run a debugger on the master process. The +/* debugging command is specified with the \fBdebugger_command\fR in +/* the \fBmain.cf\fR global configuration file. +/* .IP \fB-d\fR +/* Do not redirect stdin, stdout or stderr to /dev/null, and +/* do not discard the controlling terminal. This must be used +/* for debugging only. +/* .IP "\fB-e \fIexit_time\fR" +/* Terminate the master process after \fIexit_time\fR seconds. Child +/* processes terminate at their convenience. +/* .IP \fB-i\fR +/* Enable \fBinit\fR mode: do not become a session or process +/* group leader; and similar to \fB-s\fR, do not redirect stdout +/* to /dev/null, so that "maillog_file = /dev/stdout" works. +/* This mode is allowed only if the process ID equals 1. +/* .sp +/* This feature is available in Postfix 3.3 and later. +/* .IP \fB-s\fR +/* Do not redirect stdout to /dev/null, so that "maillog_file +/* = /dev/stdout" works. +/* .sp +/* This feature is available in Postfix 3.4 and later. +/* .IP \fB-t\fR +/* Test mode. Return a zero exit status when the \fBmaster.pid\fR lock +/* file does not exist or when that file is not locked. This is evidence +/* that the \fBmaster\fR(8) daemon is not running. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. This option +/* is passed on to child processes. Multiple \fB-v\fR options +/* make the software increasingly verbose. +/* .IP \fB-w\fR +/* Wait in a dummy foreground process, while the real master +/* daemon initializes in a background process. The dummy +/* foreground process returns a zero exit status only if the +/* master daemon initialization is successful, and if it +/* completes in a reasonable amount of time. +/* .sp +/* This feature is available in Postfix 2.10 and later. +/* .PP +/* Signals: +/* .IP \fBSIGHUP\fR +/* Upon receipt of a \fBHUP\fR signal (e.g., after "\fBpostfix reload\fR"), +/* the master process re-reads its configuration files. If a service has +/* been removed from the \fBmaster.cf\fR file, its running processes +/* are terminated immediately. +/* Otherwise, running processes are allowed to terminate as soon +/* as is convenient, so that changes in configuration settings +/* affect only new service requests. +/* .IP \fBSIGTERM\fR +/* Upon receipt of a \fBTERM\fR signal (e.g., after "\fBpostfix abort\fR"), +/* the master process passes the signal on to its child processes and +/* terminates. +/* This is useful for an emergency shutdown. Normally one would +/* terminate only the master ("\fBpostfix stop\fR") and allow running +/* processes to finish what they are doing. +/* DIAGNOSTICS +/* Problems are reported to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* The exit status +/* is non-zero in case of problems, including problems while +/* initializing as a master daemon process in the background. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_DEBUG\fR +/* After initialization, start a debugger as specified with the +/* \fBdebugger_command\fR configuration parameter in the \fBmain.cf\fR +/* configuration file. +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Unlike most Postfix daemon processes, the \fBmaster\fR(8) server does +/* not automatically pick up changes to \fBmain.cf\fR. Changes +/* to \fBmaster.cf\fR are never picked up automatically. +/* Use the "\fBpostfix reload\fR" command after a configuration change. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_process_limit (100)\fR" +/* The default maximal number of Postfix child processes that provide +/* a given service. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBservice_throttle_time (60s)\fR" +/* How long the Postfix \fBmaster\fR(8) waits before forking a server that +/* appears to be malfunctioning. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBmaster_service_disable (empty)\fR" +/* Selectively disable \fBmaster\fR(8) listener ports by service type +/* or by service name and type. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBdebugger_command (empty)\fR" +/* The external command to execute when a Postfix daemon program is +/* invoked with the -D option. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (see 'postconf -d output')\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.6 and later: +/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR" +/* Optional setting that avoids lookups in the \fBservices\fR(5) database. +/* FILES +/* .ad +/* .fi +/* To expand the directory names below into their actual values, +/* use the command "\fBpostconf config_directory\fR" etc. +/* .na +/* .nf +/* +/* $config_directory/main.cf, global configuration file. +/* $config_directory/master.cf, master server configuration file. +/* $queue_directory/pid/master.pid, master lock file. +/* $data_directory/master.lock, master lock file. +/* SEE ALSO +/* qmgr(8), queue manager +/* verify(8), address verification +/* master(5), master.cf configuration file syntax +/* postconf(5), main.cf configuration file syntax +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <limits.h> + +/* Utility library. */ + +#include <events.h> +#include <msg.h> +#include <vstring.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <vstream.h> +#include <stringops.h> +#include <myflock.h> +#include <watchdog.h> +#include <clean_env.h> +#include <argv.h> +#include <safe.h> +#include <set_eugid.h> +#include <set_ugid.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_version.h> +#include <debug_process.h> +#include <mail_task.h> +#include <mail_conf.h> +#include <open_lock.h> +#include <inet_proto.h> +#include <mail_parm_split.h> +#include <maillog_client.h> + +/* Application-specific. */ + +#include "master.h" + +int master_detach = 1; +int init_mode = 0; + +/* master_exit_event - exit for memory leak testing purposes */ + +static void master_exit_event(int unused_event, void *unused_context) +{ + msg_info("master exit time has arrived"); + exit(0); +} + +/* usage - show hint and terminate */ + +static NORETURN usage(const char *me) +{ + msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - main program */ + +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 keep_stdout = 0; + int ch; + int fd; + int n; + int test_lock = 0; + VSTRING *why; + WATCHDOG *watchdog; + ARGV *import_env; + int wait_flag = 0; + int monitor_fd = -1; + + /* + * 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. + */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * 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:istvw")) > 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, (void *) 0, atoi(optarg)); + break; + case 'i': + if (getpid() != 1) + msg_fatal("-i is allowed only for PID 1 process"); + init_mode = 1; + keep_stdout = 1; + break; + case 'D': + debug_me = 1; + break; + case 's': + keep_stdout = 1; + break; + case 't': + test_lock = 1; + break; + case 'v': + msg_verbose++; + break; + case 'w': + wait_flag = 1; + break; + default: + usage(argv[0]); + /* NOTREACHED */ + } + } + + /* + * This program takes no other arguments. + */ + if (argc > optind) + usage(argv[0]); + + /* + * Sanity check. + */ + if (test_lock && wait_flag) + msg_fatal("the -t and -w options cannot be used together"); + if (init_mode && (debug_me || !master_detach || wait_flag)) + msg_fatal("the -i option cannot be used with -D, -d, or -w"); + + /* + * Run a foreground monitor process that returns an exit status of 0 when + * the child background process reports successful initialization as a + * daemon process. We use a generous limit in case main/master.cf specify + * symbolic hosts/ports and the naming service is slow. + */ +#define MASTER_INIT_TIMEOUT 100 /* keep this limit generous */ + + if (wait_flag) + monitor_fd = master_monitor(MASTER_INIT_TIMEOUT); + + /* + * 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. + * Some new world has no terminals and prefers logging to stdout. + */ + if (master_detach) + for (fd = 0; fd < 3; fd++) { + if (fd == STDOUT_FILENO && keep_stdout) + continue; + (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 (init_mode == 0 && 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 = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + 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(); + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + msg_info("daemon started -- version %s, configuration %s", + var_mail_version, var_config_dir); + + /* + * Report successful initialization to the foreground monitor process. + */ + if (monitor_fd >= 0) { + write(monitor_fd, "", 1); + (void) close(monitor_fd); + } + + /* + * 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, (void *) 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 */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + } + if (master_gotsigchld) { + if (msg_verbose) + msg_info("got sigchld"); + master_gotsigchld = 0; /* this first */ + master_reap_child(); /* then this */ + } + } +} |