1*67b9b338Schristos /* $NetBSD: spawn.c,v 1.4 2022/10/08 16:12:49 christos Exp $ */
241fbaed0Stron
341fbaed0Stron /*++
441fbaed0Stron /* NAME
541fbaed0Stron /* spawn 8
641fbaed0Stron /* SUMMARY
741fbaed0Stron /* Postfix external command spawner
841fbaed0Stron /* SYNOPSIS
941fbaed0Stron /* \fBspawn\fR [generic Postfix daemon options] command_attributes...
1041fbaed0Stron /* DESCRIPTION
1141fbaed0Stron /* The \fBspawn\fR(8) daemon provides the Postfix equivalent
1241fbaed0Stron /* of \fBinetd\fR.
1341fbaed0Stron /* It listens on a port as specified in the Postfix \fBmaster.cf\fR file
1441fbaed0Stron /* and spawns an external command whenever a connection is established.
1541fbaed0Stron /* The connection can be made over local IPC (such as UNIX-domain
1641fbaed0Stron /* sockets) or over non-local IPC (such as TCP sockets).
17*67b9b338Schristos /* The command's standard input, output and error streams are connected
1841fbaed0Stron /* directly to the communication endpoint.
1941fbaed0Stron /*
2041fbaed0Stron /* This daemon expects to be run from the \fBmaster\fR(8) process
2141fbaed0Stron /* manager.
2241fbaed0Stron /* COMMAND ATTRIBUTE SYNTAX
2341fbaed0Stron /* .ad
2441fbaed0Stron /* .fi
2541fbaed0Stron /* The external command attributes are given in the \fBmaster.cf\fR
2641fbaed0Stron /* file at the end of a service definition. The syntax is as follows:
2741fbaed0Stron /* .IP "\fBuser\fR=\fIusername\fR (required)"
2841fbaed0Stron /* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
2941fbaed0Stron /* The external command is executed with the rights of the
3041fbaed0Stron /* specified \fIusername\fR. The software refuses to execute
3141fbaed0Stron /* commands with root privileges, or with the privileges of the
3241fbaed0Stron /* mail system owner. If \fIgroupname\fR is specified, the
3341fbaed0Stron /* corresponding group ID is used instead of the group ID
3441fbaed0Stron /* of \fIusername\fR.
3541fbaed0Stron /* .IP "\fBargv\fR=\fIcommand\fR... (required)"
3641fbaed0Stron /* The command to be executed. This must be specified as the
3741fbaed0Stron /* last command attribute.
3841fbaed0Stron /* The command is executed directly, i.e. without interpretation of
3941fbaed0Stron /* shell meta characters by a shell command interpreter.
4041fbaed0Stron /* BUGS
4141fbaed0Stron /* In order to enforce standard Postfix process resource controls,
4241fbaed0Stron /* the \fBspawn\fR(8) daemon runs only one external command at a time.
4341fbaed0Stron /* As such, it presents a noticeable overhead by wasting precious
4441fbaed0Stron /* process resources. The \fBspawn\fR(8) daemon is expected to be
4541fbaed0Stron /* replaced by a more structural solution.
4641fbaed0Stron /* DIAGNOSTICS
4741fbaed0Stron /* The \fBspawn\fR(8) daemon reports abnormal child exits.
4833881f77Schristos /* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
4941fbaed0Stron /* SECURITY
5041fbaed0Stron /* .fi
5141fbaed0Stron /* .ad
5241fbaed0Stron /* This program needs root privilege in order to execute external
5341fbaed0Stron /* commands as the specified user. It is therefore security sensitive.
5441fbaed0Stron /* However the \fBspawn\fR(8) daemon does not talk to the external command
5541fbaed0Stron /* and thus is not vulnerable to data-driven attacks.
5641fbaed0Stron /* CONFIGURATION PARAMETERS
5741fbaed0Stron /* .ad
5841fbaed0Stron /* .fi
5941fbaed0Stron /* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8)
6041fbaed0Stron /* processes run for only a limited amount of time. Use the command
6141fbaed0Stron /* "\fBpostfix reload\fR" to speed up a change.
6241fbaed0Stron /*
6341fbaed0Stron /* The text below provides only a parameter summary. See
6441fbaed0Stron /* \fBpostconf\fR(5) for more details including examples.
6541fbaed0Stron /*
6641fbaed0Stron /* In the text below, \fItransport\fR is the first field of the entry
6741fbaed0Stron /* in the \fBmaster.cf\fR file.
6841fbaed0Stron /* RESOURCE AND RATE CONTROL
6941fbaed0Stron /* .ad
7041fbaed0Stron /* .fi
7133881f77Schristos /* .IP "\fBtransport_time_limit ($command_time_limit)\fR"
7233881f77Schristos /* A transport-specific override for the command_time_limit parameter
7333881f77Schristos /* value, where \fItransport\fR is the master.cf name of the message
7433881f77Schristos /* delivery transport.
7541fbaed0Stron /* MISCELLANEOUS
7641fbaed0Stron /* .ad
7741fbaed0Stron /* .fi
7841fbaed0Stron /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
7941fbaed0Stron /* The default location of the Postfix main.cf and master.cf
8041fbaed0Stron /* configuration files.
8141fbaed0Stron /* .IP "\fBdaemon_timeout (18000s)\fR"
8241fbaed0Stron /* How much time a Postfix daemon process may take to handle a
8341fbaed0Stron /* request before it is terminated by a built-in watchdog timer.
8441fbaed0Stron /* .IP "\fBexport_environment (see 'postconf -d' output)\fR"
8541fbaed0Stron /* The list of environment variables that a Postfix process will export
8641fbaed0Stron /* to non-Postfix processes.
8741fbaed0Stron /* .IP "\fBipc_timeout (3600s)\fR"
8841fbaed0Stron /* The time limit for sending or receiving information over an internal
8941fbaed0Stron /* communication channel.
9041fbaed0Stron /* .IP "\fBmail_owner (postfix)\fR"
9141fbaed0Stron /* The UNIX system account that owns the Postfix queue and most Postfix
9241fbaed0Stron /* daemon processes.
9341fbaed0Stron /* .IP "\fBmax_idle (100s)\fR"
9441fbaed0Stron /* The maximum amount of time that an idle Postfix daemon process waits
9541fbaed0Stron /* for an incoming connection before terminating voluntarily.
9641fbaed0Stron /* .IP "\fBmax_use (100)\fR"
9741fbaed0Stron /* The maximal number of incoming connections that a Postfix daemon
9841fbaed0Stron /* process will service before terminating voluntarily.
9941fbaed0Stron /* .IP "\fBprocess_id (read-only)\fR"
10041fbaed0Stron /* The process ID of a Postfix command or daemon process.
10141fbaed0Stron /* .IP "\fBprocess_name (read-only)\fR"
10241fbaed0Stron /* The process name of a Postfix command or daemon process.
10341fbaed0Stron /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
10441fbaed0Stron /* The location of the Postfix top-level queue directory.
10541fbaed0Stron /* .IP "\fBsyslog_facility (mail)\fR"
10641fbaed0Stron /* The syslog facility of Postfix logging.
10741fbaed0Stron /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
10833881f77Schristos /* A prefix that is prepended to the process name in syslog
10933881f77Schristos /* records, so that, for example, "smtpd" becomes "prefix/smtpd".
11033881f77Schristos /* .PP
11133881f77Schristos /* Available in Postfix 3.3 and later:
11233881f77Schristos /* .IP "\fBservice_name (read-only)\fR"
11333881f77Schristos /* The master.cf service name of a Postfix daemon process.
11441fbaed0Stron /* SEE ALSO
11541fbaed0Stron /* postconf(5), configuration parameters
11641fbaed0Stron /* master(8), process manager
11733881f77Schristos /* postlogd(8), Postfix logging
11841fbaed0Stron /* syslogd(8), system logging
11941fbaed0Stron /* LICENSE
12041fbaed0Stron /* .ad
12141fbaed0Stron /* .fi
12241fbaed0Stron /* The Secure Mailer license must be distributed with this software.
12341fbaed0Stron /* AUTHOR(S)
12441fbaed0Stron /* Wietse Venema
12541fbaed0Stron /* IBM T.J. Watson Research
12641fbaed0Stron /* P.O. Box 704
12741fbaed0Stron /* Yorktown Heights, NY 10598, USA
128e262b48eSchristos /*
129e262b48eSchristos /* Wietse Venema
130e262b48eSchristos /* Google, Inc.
131e262b48eSchristos /* 111 8th Avenue
132e262b48eSchristos /* New York, NY 10011, USA
13341fbaed0Stron /*--*/
13441fbaed0Stron
13541fbaed0Stron /* System library. */
13641fbaed0Stron
13741fbaed0Stron #include <sys_defs.h>
13841fbaed0Stron #include <sys/wait.h>
13941fbaed0Stron #include <unistd.h>
14041fbaed0Stron #include <stdlib.h>
14141fbaed0Stron #include <string.h>
14241fbaed0Stron #include <pwd.h>
14341fbaed0Stron #include <grp.h>
14441fbaed0Stron #include <fcntl.h>
14541fbaed0Stron #ifdef STRCASECMP_IN_STRINGS_H
14641fbaed0Stron #include <strings.h>
14741fbaed0Stron #endif
14841fbaed0Stron
14941fbaed0Stron /* Utility library. */
15041fbaed0Stron
15141fbaed0Stron #include <msg.h>
15241fbaed0Stron #include <argv.h>
15341fbaed0Stron #include <dict.h>
15441fbaed0Stron #include <mymalloc.h>
15541fbaed0Stron #include <spawn_command.h>
15641fbaed0Stron #include <split_at.h>
15741fbaed0Stron #include <timed_wait.h>
15841fbaed0Stron #include <set_eugid.h>
15941fbaed0Stron
16041fbaed0Stron /* Global library. */
16141fbaed0Stron
16241fbaed0Stron #include <mail_version.h>
16341fbaed0Stron
16441fbaed0Stron /* Single server skeleton. */
16541fbaed0Stron
16641fbaed0Stron #include <mail_params.h>
16741fbaed0Stron #include <mail_server.h>
16841fbaed0Stron #include <mail_conf.h>
169e262b48eSchristos #include <mail_parm_split.h>
17041fbaed0Stron
17141fbaed0Stron /* Application-specific. */
17241fbaed0Stron
17341fbaed0Stron /*
17441fbaed0Stron * Tunable parameters. Values are taken from the config file, after
17541fbaed0Stron * prepending the service name to _name, and so on.
17641fbaed0Stron */
17741fbaed0Stron int var_command_maxtime; /* system-wide */
17841fbaed0Stron
17941fbaed0Stron /*
18041fbaed0Stron * For convenience. Instead of passing around lists of parameters, bundle
18141fbaed0Stron * them up in convenient structures.
18241fbaed0Stron */
18341fbaed0Stron typedef struct {
18441fbaed0Stron char **argv; /* argument vector */
18541fbaed0Stron uid_t uid; /* command privileges */
18641fbaed0Stron gid_t gid; /* command privileges */
18741fbaed0Stron int time_limit; /* per-service time limit */
18841fbaed0Stron } SPAWN_ATTR;
18941fbaed0Stron
19041fbaed0Stron /* get_service_attr - get service attributes */
19141fbaed0Stron
get_service_attr(SPAWN_ATTR * attr,char * service,char ** argv)19241fbaed0Stron static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
19341fbaed0Stron {
19441fbaed0Stron const char *myname = "get_service_attr";
19541fbaed0Stron struct passwd *pwd;
19641fbaed0Stron struct group *grp;
19741fbaed0Stron char *user; /* user name */
19841fbaed0Stron char *group; /* group name */
19941fbaed0Stron
20041fbaed0Stron /*
20141fbaed0Stron * Initialize.
20241fbaed0Stron */
20341fbaed0Stron user = 0;
20441fbaed0Stron group = 0;
20541fbaed0Stron attr->argv = 0;
20641fbaed0Stron
20741fbaed0Stron /*
20841fbaed0Stron * Figure out the command time limit for this transport.
20941fbaed0Stron */
21041fbaed0Stron attr->time_limit =
21141fbaed0Stron get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
21241fbaed0Stron
21341fbaed0Stron /*
21441fbaed0Stron * Iterate over the command-line attribute list.
21541fbaed0Stron */
21641fbaed0Stron for ( /* void */ ; *argv != 0; argv++) {
21741fbaed0Stron
21841fbaed0Stron /*
21941fbaed0Stron * user=username[:groupname]
22041fbaed0Stron */
22141fbaed0Stron if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
22241fbaed0Stron user = *argv + sizeof("user=") - 1;
22341fbaed0Stron if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */
22441fbaed0Stron if (*group == 0)
22541fbaed0Stron group = 0;
22641fbaed0Stron if ((pwd = getpwnam(user)) == 0)
22741fbaed0Stron msg_fatal("unknown user name: %s", user);
22841fbaed0Stron attr->uid = pwd->pw_uid;
22941fbaed0Stron if (group != 0) {
23041fbaed0Stron if ((grp = getgrnam(group)) == 0)
23141fbaed0Stron msg_fatal("unknown group name: %s", group);
23241fbaed0Stron attr->gid = grp->gr_gid;
23341fbaed0Stron } else {
23441fbaed0Stron attr->gid = pwd->pw_gid;
23541fbaed0Stron }
23641fbaed0Stron }
23741fbaed0Stron
23841fbaed0Stron /*
23941fbaed0Stron * argv=command...
24041fbaed0Stron */
24141fbaed0Stron else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
24241fbaed0Stron *argv += sizeof("argv=") - 1; /* XXX clobbers argv */
24341fbaed0Stron attr->argv = argv;
24441fbaed0Stron break;
24541fbaed0Stron }
24641fbaed0Stron
24741fbaed0Stron /*
24841fbaed0Stron * Bad.
24941fbaed0Stron */
25041fbaed0Stron else
25141fbaed0Stron msg_fatal("unknown attribute name: %s", *argv);
25241fbaed0Stron }
25341fbaed0Stron
25441fbaed0Stron /*
25541fbaed0Stron * Sanity checks. Verify that every member has an acceptable value.
25641fbaed0Stron */
25741fbaed0Stron if (user == 0)
25841fbaed0Stron msg_fatal("missing user= attribute");
25941fbaed0Stron if (attr->argv == 0)
26041fbaed0Stron msg_fatal("missing argv= attribute");
26141fbaed0Stron if (attr->uid == 0)
26241fbaed0Stron msg_fatal("request to deliver as root");
26341fbaed0Stron if (attr->uid == var_owner_uid)
26441fbaed0Stron msg_fatal("request to deliver as mail system owner");
26541fbaed0Stron if (attr->gid == 0)
26641fbaed0Stron msg_fatal("request to use privileged group id %ld", (long) attr->gid);
26741fbaed0Stron if (attr->gid == var_owner_gid)
26841fbaed0Stron msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
26941fbaed0Stron if (attr->uid == (uid_t) (-1))
27041fbaed0Stron msg_fatal("user must not have user ID -1");
27141fbaed0Stron if (attr->gid == (gid_t) (-1))
27241fbaed0Stron msg_fatal("user must not have group ID -1");
27341fbaed0Stron
27441fbaed0Stron /*
27541fbaed0Stron * Give the poor tester a clue of what is going on.
27641fbaed0Stron */
27741fbaed0Stron if (msg_verbose)
27841fbaed0Stron msg_info("%s: uid %ld, gid %ld; time %d",
27941fbaed0Stron myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
28041fbaed0Stron }
28141fbaed0Stron
28241fbaed0Stron /* spawn_service - perform service for client */
28341fbaed0Stron
spawn_service(VSTREAM * client_stream,char * service,char ** argv)28441fbaed0Stron static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
28541fbaed0Stron {
28641fbaed0Stron const char *myname = "spawn_service";
28741fbaed0Stron static SPAWN_ATTR attr;
28841fbaed0Stron WAIT_STATUS_T status;
28941fbaed0Stron ARGV *export_env;
29041fbaed0Stron
29141fbaed0Stron /*
29241fbaed0Stron * This routine runs whenever a client connects to the UNIX-domain socket
29341fbaed0Stron * dedicated to running an external command.
29441fbaed0Stron */
29541fbaed0Stron if (msg_verbose)
29641fbaed0Stron msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
29741fbaed0Stron
29841fbaed0Stron /*
29941fbaed0Stron * Look up service attributes and config information only once. This is
30041fbaed0Stron * safe since the information comes from a trusted source.
30141fbaed0Stron */
30241fbaed0Stron if (attr.argv == 0) {
30341fbaed0Stron get_service_attr(&attr, service, argv);
30441fbaed0Stron }
30541fbaed0Stron
30641fbaed0Stron /*
30741fbaed0Stron * Execute the command.
30841fbaed0Stron */
309e262b48eSchristos export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
310e262b48eSchristos status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)),
311e262b48eSchristos CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)),
312e262b48eSchristos CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)),
313e262b48eSchristos CA_SPAWN_CMD_UID(attr.uid),
314e262b48eSchristos CA_SPAWN_CMD_GID(attr.gid),
315e262b48eSchristos CA_SPAWN_CMD_ARGV(attr.argv),
316e262b48eSchristos CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit),
317e262b48eSchristos CA_SPAWN_CMD_EXPORT(export_env->argv),
318e262b48eSchristos CA_SPAWN_CMD_END);
31941fbaed0Stron argv_free(export_env);
32041fbaed0Stron
32141fbaed0Stron /*
32241fbaed0Stron * Warn about unsuccessful completion.
32341fbaed0Stron */
32441fbaed0Stron if (!NORMAL_EXIT_STATUS(status)) {
32541fbaed0Stron if (WIFEXITED(status))
32641fbaed0Stron msg_warn("command %s exit status %d",
32741fbaed0Stron attr.argv[0], WEXITSTATUS(status));
32841fbaed0Stron if (WIFSIGNALED(status))
32941fbaed0Stron msg_warn("command %s killed by signal %d",
33041fbaed0Stron attr.argv[0], WTERMSIG(status));
33141fbaed0Stron }
33241fbaed0Stron }
33341fbaed0Stron
33441fbaed0Stron /* pre_accept - see if tables have changed */
33541fbaed0Stron
pre_accept(char * unused_name,char ** unused_argv)33641fbaed0Stron static void pre_accept(char *unused_name, char **unused_argv)
33741fbaed0Stron {
33841fbaed0Stron const char *table;
33941fbaed0Stron
34041fbaed0Stron if ((table = dict_changed_name()) != 0) {
34141fbaed0Stron msg_info("table %s has changed -- restarting", table);
34241fbaed0Stron exit(0);
34341fbaed0Stron }
34441fbaed0Stron }
34541fbaed0Stron
34641fbaed0Stron /* drop_privileges - drop privileges most of the time */
34741fbaed0Stron
drop_privileges(char * unused_name,char ** unused_argv)34841fbaed0Stron static void drop_privileges(char *unused_name, char **unused_argv)
34941fbaed0Stron {
35041fbaed0Stron set_eugid(var_owner_uid, var_owner_gid);
35141fbaed0Stron }
35241fbaed0Stron
35341fbaed0Stron MAIL_VERSION_STAMP_DECLARE;
35441fbaed0Stron
35541fbaed0Stron /* main - pass control to the single-threaded skeleton */
35641fbaed0Stron
main(int argc,char ** argv)35741fbaed0Stron int main(int argc, char **argv)
35841fbaed0Stron {
35941fbaed0Stron static const CONFIG_TIME_TABLE time_table[] = {
36041fbaed0Stron VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
36141fbaed0Stron 0,
36241fbaed0Stron };
36341fbaed0Stron
36441fbaed0Stron /*
36541fbaed0Stron * Fingerprint executables and core dumps.
36641fbaed0Stron */
36741fbaed0Stron MAIL_VERSION_STAMP_ALLOCATE;
36841fbaed0Stron
36941fbaed0Stron single_server_main(argc, argv, spawn_service,
370e262b48eSchristos CA_MAIL_SERVER_TIME_TABLE(time_table),
371e262b48eSchristos CA_MAIL_SERVER_POST_INIT(drop_privileges),
372e262b48eSchristos CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
373e262b48eSchristos CA_MAIL_SERVER_PRIVILEGED,
37441fbaed0Stron 0);
37541fbaed0Stron }
376