/*++ /* NAME /* spawn 8 /* SUMMARY /* Postfix external command spawner /* SYNOPSIS /* \fBspawn\fR [generic Postfix daemon options] command_attributes... /* DESCRIPTION /* The \fBspawn\fR(8) daemon monitors a TCP or UNIX-domain stream /* socket, configured in \fBmaster.cf\fR with a service type /* \fBinet\fR or \fBunix\fR. /* /* This daemon spawns an external command whenever a connection /* is established, with the standard input, output and error file /* descriptors connected to the remote client. /* /* The command process is subject to the time limit specified /* with the parameter \fItransport\fR_time_limit (default: /* command_time_limit) where \fItransport\fR equals the service /* name field in master.cf. A process that exceeds the time limit /* will receive a SIGKILL signal. /* /* The \fBspawn\fR(8) daemon service typically has a process limit > /* 1 in its \fBmaster.cf\fR service definition, so that the number /* of processes can scale with demand. /* COMMAND ATTRIBUTE SYNTAX /* .ad /* .fi /* The external command attributes are given in the \fBmaster.cf\fR /* file at the end of a service definition. The syntax is as follows: /* .IP "\fBuser\fR=\fIusername\fR (required)" /* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" /* The external command is executed with the rights of the /* specified \fIusername\fR. The software refuses to execute /* commands with root privileges, or with the privileges of the /* mail system owner. If \fIgroupname\fR is specified, the /* corresponding group ID is used instead of the group ID /* of \fIusername\fR. /* .IP "\fBargv\fR=\fIcommand\fR... (required)" /* The command to be executed. This must be specified as the /* last command attribute. /* The command is executed directly, i.e. without interpretation of /* shell meta characters by a shell command interpreter. /* .sp /* If a command argument must contain whitespace, or if a command /* argument must begin with "{", enclose the argument with "{" and /* "}". This form will ignore whitespace after the outer "{" and /* before the outer "}". Example: /* .sp /* .fi /* argv=/bin/sh -c { shell syntax here } /* DIAGNOSTICS /* The \fBspawn\fR(8) daemon reports abnormal child exits. /* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). /* SECURITY /* .fi /* .ad /* The \fBspawn\fR(8) daemon needs root privilege in order to /* execute external commands as the specified user. It is therefore /* security sensitive. /* /* However, the \fBspawn\fR(8) daemon does not receive data from /* or about service clients or external commands, and thus is not /* vulnerable to data-driven attacks. /* CONFIGURATION PARAMETERS /* .ad /* .fi /* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8) /* processes run for only a limited amount of time. Use the command /* "\fBpostfix reload\fR" to speed up a change. /* /* The text below provides only a parameter summary. See /* \fBpostconf\fR(5) for more details including examples. /* /* In the text below, \fItransport\fR is the first field of the entry /* in the \fBmaster.cf\fR file. /* RESOURCE AND RATE CONTROL /* .ad /* .fi /* .IP "\fBtransport_time_limit ($command_time_limit)\fR" /* A transport-specific override for the command_time_limit parameter /* value, where \fItransport\fR is the master.cf name of the message /* delivery transport. /* MISCELLANEOUS /* .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_timeout (18000s)\fR" /* How much time a Postfix daemon process may take to handle a /* request before it is terminated by a built-in watchdog timer. /* .IP "\fBexport_environment (see 'postconf -d' output)\fR" /* The list of environment variables that a Postfix process will export /* to non-Postfix processes. /* .IP "\fBipc_timeout (3600s)\fR" /* The time limit for sending or receiving information over an internal /* communication channel. /* .IP "\fBmail_owner (postfix)\fR" /* The UNIX system account that owns the Postfix queue and most Postfix /* daemon processes. /* .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 "\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. /* SEE ALSO /* postconf(5), configuration parameters /* master(8), process manager /* 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 library. */ #include #include #include #include #include #include #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include #include #include #include #include #include #include /* Global library. */ #include /* Single server skeleton. */ #include #include #include #include /* Application-specific. */ /* * Tunable parameters. Values are taken from the config file, after * prepending the service name to _name, and so on. */ int var_command_maxtime; /* system-wide */ /* * For convenience. Instead of passing around lists of parameters, bundle * them up in convenient structures. */ typedef struct { char **argv; /* argument vector */ uid_t uid; /* command privileges */ gid_t gid; /* command privileges */ int time_limit; /* per-service time limit */ } SPAWN_ATTR; /* get_service_attr - get service attributes */ static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv) { const char *myname = "get_service_attr"; struct passwd *pwd; struct group *grp; char *user; /* user name */ char *group; /* group name */ /* * Initialize. */ user = 0; group = 0; attr->argv = 0; /* * Figure out the command time limit for this transport. */ attr->time_limit = get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); /* * Iterate over the command-line attribute list. */ for ( /* void */ ; *argv != 0; argv++) { /* * user=username[:groupname] */ if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { user = *argv + sizeof("user=") - 1; if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ if (*group == 0) group = 0; if ((pwd = getpwnam(user)) == 0) msg_fatal("unknown user name: %s", user); attr->uid = pwd->pw_uid; if (group != 0) { if ((grp = getgrnam(group)) == 0) msg_fatal("unknown group name: %s", group); attr->gid = grp->gr_gid; } else { attr->gid = pwd->pw_gid; } } /* * argv=command... */ else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ attr->argv = argv; break; } /* * Bad. */ else msg_fatal("unknown attribute name: %s", *argv); } /* * Sanity checks. Verify that every member has an acceptable value. */ if (user == 0) msg_fatal("missing user= attribute"); if (attr->argv == 0) msg_fatal("missing argv= attribute"); if (attr->uid == 0) msg_fatal("request to deliver as root"); if (attr->uid == var_owner_uid) msg_fatal("request to deliver as mail system owner"); if (attr->gid == 0) msg_fatal("request to use privileged group id %ld", (long) attr->gid); if (attr->gid == var_owner_gid) msg_fatal("request to use mail system owner group id %ld", (long) attr->gid); if (attr->uid == (uid_t) (-1)) msg_fatal("user must not have user ID -1"); if (attr->gid == (gid_t) (-1)) msg_fatal("user must not have group ID -1"); /* * Give the poor tester a clue of what is going on. */ if (msg_verbose) msg_info("%s: uid %ld, gid %ld; time %d", myname, (long) attr->uid, (long) attr->gid, attr->time_limit); } /* spawn_service - perform service for client */ static void spawn_service(VSTREAM *client_stream, char *service, char **argv) { const char *myname = "spawn_service"; static SPAWN_ATTR attr; WAIT_STATUS_T status; ARGV *export_env; /* * This routine runs whenever a client connects to the UNIX-domain socket * dedicated to running an external command. */ if (msg_verbose) msg_info("%s: service=%s, command=%s...", myname, service, argv[0]); /* * Look up service attributes and config information only once. This is * safe since the information comes from a trusted source. */ if (attr.argv == 0) { get_service_attr(&attr, service, argv); } /* * Execute the command. */ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)), CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)), CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)), CA_SPAWN_CMD_UID(attr.uid), CA_SPAWN_CMD_GID(attr.gid), CA_SPAWN_CMD_ARGV(attr.argv), CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit), CA_SPAWN_CMD_EXPORT(export_env->argv), CA_SPAWN_CMD_END); argv_free(export_env); /* * Warn about unsuccessful completion. */ if (!NORMAL_EXIT_STATUS(status)) { if (WIFEXITED(status)) msg_warn("command %s exit status %d", attr.argv[0], WEXITSTATUS(status)); if (WIFSIGNALED(status)) msg_warn("command %s killed by signal %d", attr.argv[0], WTERMSIG(status)); } } /* pre_accept - see if tables have changed */ static void pre_accept(char *unused_name, char **unused_argv) { const char *table; if ((table = dict_changed_name()) != 0) { msg_info("table %s has changed -- restarting", table); exit(0); } } /* drop_privileges - drop privileges most of the time */ static void drop_privileges(char *unused_name, char **unused_argv) { set_eugid(var_owner_uid, var_owner_gid); } MAIL_VERSION_STAMP_DECLARE; /* main - pass control to the single-threaded skeleton */ int main(int argc, char **argv) { static const CONFIG_TIME_TABLE time_table[] = { VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, 0, }; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; single_server_main(argc, argv, spawn_service, CA_MAIL_SERVER_TIME_TABLE(time_table), CA_MAIL_SERVER_POST_INIT(drop_privileges), CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), CA_MAIL_SERVER_PRIVILEGED, 0); }