1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	spawn 8
6/* SUMMARY
7/*	Postfix external command spawner
8/* SYNOPSIS
9/*	\fBspawn\fR [generic Postfix daemon options] command_attributes...
10/* DESCRIPTION
11/*	The \fBspawn\fR(8) daemon provides the Postfix equivalent
12/*	of \fBinetd\fR.
13/*	It listens on a port as specified in the Postfix \fBmaster.cf\fR file
14/*	and spawns an external command whenever a connection is established.
15/*	The connection can be made over local IPC (such as UNIX-domain
16/*	sockets) or over non-local IPC (such as TCP sockets).
17/*	The command\'s standard input, output and error streams are connected
18/*	directly to the communication endpoint.
19/*
20/*	This daemon expects to be run from the \fBmaster\fR(8) process
21/*	manager.
22/* COMMAND ATTRIBUTE SYNTAX
23/* .ad
24/* .fi
25/*	The external command attributes are given in the \fBmaster.cf\fR
26/*	file at the end of a service definition.  The syntax is as follows:
27/* .IP "\fBuser\fR=\fIusername\fR (required)"
28/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR"
29/*	The external command is executed with the rights of the
30/*	specified \fIusername\fR.  The software refuses to execute
31/*	commands with root privileges, or with the privileges of the
32/*	mail system owner. If \fIgroupname\fR is specified, the
33/*	corresponding group ID is used instead of the group ID
34/*	of \fIusername\fR.
35/* .IP "\fBargv\fR=\fIcommand\fR... (required)"
36/*	The command to be executed. This must be specified as the
37/*	last command attribute.
38/*	The command is executed directly, i.e. without interpretation of
39/*	shell meta characters by a shell command interpreter.
40/* BUGS
41/*	In order to enforce standard Postfix process resource controls,
42/*	the \fBspawn\fR(8) daemon runs only one external command at a time.
43/*	As such, it presents a noticeable overhead by wasting precious
44/*	process resources. The \fBspawn\fR(8) daemon is expected to be
45/*	replaced by a more structural solution.
46/* DIAGNOSTICS
47/*	The \fBspawn\fR(8) daemon reports abnormal child exits.
48/*	Problems are logged to \fBsyslogd\fR(8).
49/* SECURITY
50/* .fi
51/* .ad
52/*	This program needs root privilege in order to execute external
53/*	commands as the specified user. It is therefore security sensitive.
54/*	However the \fBspawn\fR(8) daemon does not talk to the external command
55/*	and thus is not vulnerable to data-driven attacks.
56/* CONFIGURATION PARAMETERS
57/* .ad
58/* .fi
59/*	Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8)
60/*	processes run for only a limited amount of time. Use the command
61/*	"\fBpostfix reload\fR" to speed up a change.
62/*
63/*	The text below provides only a parameter summary. See
64/*	\fBpostconf\fR(5) for more details including examples.
65/*
66/*	In the text below, \fItransport\fR is the first field of the entry
67/*	in the \fBmaster.cf\fR file.
68/* RESOURCE AND RATE CONTROL
69/* .ad
70/* .fi
71/* .IP "\fItransport\fB_time_limit ($command_time_limit)\fR"
72/*	The amount of time the command is allowed to run before it is
73/*	terminated.
74/*
75/*	Postfix 2.4 and later support a suffix that specifies the
76/*	time unit: s (seconds), m (minutes), h (hours), d (days),
77/*	w (weeks). The default time unit is seconds.
78/* MISCELLANEOUS
79/* .ad
80/* .fi
81/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
82/*	The default location of the Postfix main.cf and master.cf
83/*	configuration files.
84/* .IP "\fBdaemon_timeout (18000s)\fR"
85/*	How much time a Postfix daemon process may take to handle a
86/*	request before it is terminated by a built-in watchdog timer.
87/* .IP "\fBexport_environment (see 'postconf -d' output)\fR"
88/*	The list of environment variables that a Postfix process will export
89/*	to non-Postfix processes.
90/* .IP "\fBipc_timeout (3600s)\fR"
91/*	The time limit for sending or receiving information over an internal
92/*	communication channel.
93/* .IP "\fBmail_owner (postfix)\fR"
94/*	The UNIX system account that owns the Postfix queue and most Postfix
95/*	daemon processes.
96/* .IP "\fBmax_idle (100s)\fR"
97/*	The maximum amount of time that an idle Postfix daemon process waits
98/*	for an incoming connection before terminating voluntarily.
99/* .IP "\fBmax_use (100)\fR"
100/*	The maximal number of incoming connections that a Postfix daemon
101/*	process will service before terminating voluntarily.
102/* .IP "\fBprocess_id (read-only)\fR"
103/*	The process ID of a Postfix command or daemon process.
104/* .IP "\fBprocess_name (read-only)\fR"
105/*	The process name of a Postfix command or daemon process.
106/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
107/*	The location of the Postfix top-level queue directory.
108/* .IP "\fBsyslog_facility (mail)\fR"
109/*	The syslog facility of Postfix logging.
110/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
111/*	The mail system name that is prepended to the process name in syslog
112/*	records, so that "smtpd" becomes, for example, "postfix/smtpd".
113/* SEE ALSO
114/*	postconf(5), configuration parameters
115/*	master(8), process manager
116/*	syslogd(8), system logging
117/* LICENSE
118/* .ad
119/* .fi
120/*	The Secure Mailer license must be distributed with this software.
121/* AUTHOR(S)
122/*	Wietse Venema
123/*	IBM T.J. Watson Research
124/*	P.O. Box 704
125/*	Yorktown Heights, NY 10598, USA
126/*--*/
127
128/* System library. */
129
130#include <sys_defs.h>
131#include <sys/wait.h>
132#include <unistd.h>
133#include <stdlib.h>
134#include <string.h>
135#include <pwd.h>
136#include <grp.h>
137#include <fcntl.h>
138#ifdef STRCASECMP_IN_STRINGS_H
139#include <strings.h>
140#endif
141
142/* Utility library. */
143
144#include <msg.h>
145#include <argv.h>
146#include <dict.h>
147#include <mymalloc.h>
148#include <spawn_command.h>
149#include <split_at.h>
150#include <timed_wait.h>
151#include <set_eugid.h>
152
153/* Global library. */
154
155#include <mail_version.h>
156
157/* Single server skeleton. */
158
159#include <mail_params.h>
160#include <mail_server.h>
161#include <mail_conf.h>
162
163/* Application-specific. */
164
165 /*
166  * Tunable parameters. Values are taken from the config file, after
167  * prepending the service name to _name, and so on.
168  */
169int     var_command_maxtime;		/* system-wide */
170
171 /*
172  * For convenience. Instead of passing around lists of parameters, bundle
173  * them up in convenient structures.
174  */
175typedef struct {
176    char  **argv;			/* argument vector */
177    uid_t   uid;			/* command privileges */
178    gid_t   gid;			/* command privileges */
179    int     time_limit;			/* per-service time limit */
180} SPAWN_ATTR;
181
182/* get_service_attr - get service attributes */
183
184static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
185{
186    const char *myname = "get_service_attr";
187    struct passwd *pwd;
188    struct group *grp;
189    char   *user;			/* user name */
190    char   *group;			/* group name */
191
192    /*
193     * Initialize.
194     */
195    user = 0;
196    group = 0;
197    attr->argv = 0;
198
199    /*
200     * Figure out the command time limit for this transport.
201     */
202    attr->time_limit =
203	get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
204
205    /*
206     * Iterate over the command-line attribute list.
207     */
208    for ( /* void */ ; *argv != 0; argv++) {
209
210	/*
211	 * user=username[:groupname]
212	 */
213	if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
214	    user = *argv + sizeof("user=") - 1;
215	    if ((group = split_at(user, ':')) != 0)	/* XXX clobbers argv */
216		if (*group == 0)
217		    group = 0;
218	    if ((pwd = getpwnam(user)) == 0)
219		msg_fatal("unknown user name: %s", user);
220	    attr->uid = pwd->pw_uid;
221	    if (group != 0) {
222		if ((grp = getgrnam(group)) == 0)
223		    msg_fatal("unknown group name: %s", group);
224		attr->gid = grp->gr_gid;
225	    } else {
226		attr->gid = pwd->pw_gid;
227	    }
228	}
229
230	/*
231	 * argv=command...
232	 */
233	else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
234	    *argv += sizeof("argv=") - 1;	/* XXX clobbers argv */
235	    attr->argv = argv;
236	    break;
237	}
238
239	/*
240	 * Bad.
241	 */
242	else
243	    msg_fatal("unknown attribute name: %s", *argv);
244    }
245
246    /*
247     * Sanity checks. Verify that every member has an acceptable value.
248     */
249    if (user == 0)
250	msg_fatal("missing user= attribute");
251    if (attr->argv == 0)
252	msg_fatal("missing argv= attribute");
253    if (attr->uid == 0)
254	msg_fatal("request to deliver as root");
255    if (attr->uid == var_owner_uid)
256	msg_fatal("request to deliver as mail system owner");
257    if (attr->gid == 0)
258	msg_fatal("request to use privileged group id %ld", (long) attr->gid);
259    if (attr->gid == var_owner_gid)
260	msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
261    if (attr->uid == (uid_t) (-1))
262	msg_fatal("user must not have user ID -1");
263    if (attr->gid == (gid_t) (-1))
264	msg_fatal("user must not have group ID -1");
265
266    /*
267     * Give the poor tester a clue of what is going on.
268     */
269    if (msg_verbose)
270	msg_info("%s: uid %ld, gid %ld; time %d",
271	      myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
272}
273
274/* spawn_service - perform service for client */
275
276static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
277{
278    const char *myname = "spawn_service";
279    static SPAWN_ATTR attr;
280    WAIT_STATUS_T status;
281    ARGV   *export_env;
282
283    /*
284     * This routine runs whenever a client connects to the UNIX-domain socket
285     * dedicated to running an external command.
286     */
287    if (msg_verbose)
288	msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
289
290    /*
291     * Look up service attributes and config information only once. This is
292     * safe since the information comes from a trusted source.
293     */
294    if (attr.argv == 0) {
295	get_service_attr(&attr, service, argv);
296    }
297
298    /*
299     * Execute the command.
300     */
301    export_env = argv_split(var_export_environ, ", \t\r\n");
302    status = spawn_command(SPAWN_CMD_STDIN, vstream_fileno(client_stream),
303			   SPAWN_CMD_STDOUT, vstream_fileno(client_stream),
304			   SPAWN_CMD_STDERR, vstream_fileno(client_stream),
305			   SPAWN_CMD_UID, attr.uid,
306			   SPAWN_CMD_GID, attr.gid,
307			   SPAWN_CMD_ARGV, attr.argv,
308			   SPAWN_CMD_TIME_LIMIT, attr.time_limit,
309			   SPAWN_CMD_EXPORT, export_env->argv,
310			   SPAWN_CMD_END);
311    argv_free(export_env);
312
313    /*
314     * Warn about unsuccessful completion.
315     */
316    if (!NORMAL_EXIT_STATUS(status)) {
317	if (WIFEXITED(status))
318	    msg_warn("command %s exit status %d",
319		     attr.argv[0], WEXITSTATUS(status));
320	if (WIFSIGNALED(status))
321	    msg_warn("command %s killed by signal %d",
322		     attr.argv[0], WTERMSIG(status));
323    }
324}
325
326/* pre_accept - see if tables have changed */
327
328static void pre_accept(char *unused_name, char **unused_argv)
329{
330    const char *table;
331
332    if ((table = dict_changed_name()) != 0) {
333	msg_info("table %s has changed -- restarting", table);
334	exit(0);
335    }
336}
337
338/* drop_privileges - drop privileges most of the time */
339
340static void drop_privileges(char *unused_name, char **unused_argv)
341{
342    set_eugid(var_owner_uid, var_owner_gid);
343}
344
345MAIL_VERSION_STAMP_DECLARE;
346
347/* main - pass control to the single-threaded skeleton */
348
349int     main(int argc, char **argv)
350{
351    static const CONFIG_TIME_TABLE time_table[] = {
352	VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
353	0,
354    };
355
356    /*
357     * Fingerprint executables and core dumps.
358     */
359    MAIL_VERSION_STAMP_ALLOCATE;
360
361    single_server_main(argc, argv, spawn_service,
362		       MAIL_SERVER_TIME_TABLE, time_table,
363		       MAIL_SERVER_POST_INIT, drop_privileges,
364		       MAIL_SERVER_PRE_ACCEPT, pre_accept,
365		       MAIL_SERVER_PRIVILEGED,
366		       0);
367}
368