xref: /netbsd-src/external/ibm-public/postfix/dist/src/spawn/spawn.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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