xref: /netbsd-src/external/ibm-public/postfix/dist/src/spawn/spawn.c (revision c38e7cc395b1472a774ff828e46123de44c628e9)
1 /*	$NetBSD: spawn.c,v 1.2 2017/02/14 01:16:48 christos Exp $	*/
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 /*	Wietse Venema
128 /*	Google, Inc.
129 /*	111 8th Avenue
130 /*	New York, NY 10011, USA
131 /*--*/
132 
133 /* System library. */
134 
135 #include <sys_defs.h>
136 #include <sys/wait.h>
137 #include <unistd.h>
138 #include <stdlib.h>
139 #include <string.h>
140 #include <pwd.h>
141 #include <grp.h>
142 #include <fcntl.h>
143 #ifdef STRCASECMP_IN_STRINGS_H
144 #include <strings.h>
145 #endif
146 
147 /* Utility library. */
148 
149 #include <msg.h>
150 #include <argv.h>
151 #include <dict.h>
152 #include <mymalloc.h>
153 #include <spawn_command.h>
154 #include <split_at.h>
155 #include <timed_wait.h>
156 #include <set_eugid.h>
157 
158 /* Global library. */
159 
160 #include <mail_version.h>
161 
162 /* Single server skeleton. */
163 
164 #include <mail_params.h>
165 #include <mail_server.h>
166 #include <mail_conf.h>
167 #include <mail_parm_split.h>
168 
169 /* Application-specific. */
170 
171  /*
172   * Tunable parameters. Values are taken from the config file, after
173   * prepending the service name to _name, and so on.
174   */
175 int     var_command_maxtime;		/* system-wide */
176 
177  /*
178   * For convenience. Instead of passing around lists of parameters, bundle
179   * them up in convenient structures.
180   */
181 typedef struct {
182     char  **argv;			/* argument vector */
183     uid_t   uid;			/* command privileges */
184     gid_t   gid;			/* command privileges */
185     int     time_limit;			/* per-service time limit */
186 } SPAWN_ATTR;
187 
188 /* get_service_attr - get service attributes */
189 
190 static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv)
191 {
192     const char *myname = "get_service_attr";
193     struct passwd *pwd;
194     struct group *grp;
195     char   *user;			/* user name */
196     char   *group;			/* group name */
197 
198     /*
199      * Initialize.
200      */
201     user = 0;
202     group = 0;
203     attr->argv = 0;
204 
205     /*
206      * Figure out the command time limit for this transport.
207      */
208     attr->time_limit =
209 	get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0);
210 
211     /*
212      * Iterate over the command-line attribute list.
213      */
214     for ( /* void */ ; *argv != 0; argv++) {
215 
216 	/*
217 	 * user=username[:groupname]
218 	 */
219 	if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) {
220 	    user = *argv + sizeof("user=") - 1;
221 	    if ((group = split_at(user, ':')) != 0)	/* XXX clobbers argv */
222 		if (*group == 0)
223 		    group = 0;
224 	    if ((pwd = getpwnam(user)) == 0)
225 		msg_fatal("unknown user name: %s", user);
226 	    attr->uid = pwd->pw_uid;
227 	    if (group != 0) {
228 		if ((grp = getgrnam(group)) == 0)
229 		    msg_fatal("unknown group name: %s", group);
230 		attr->gid = grp->gr_gid;
231 	    } else {
232 		attr->gid = pwd->pw_gid;
233 	    }
234 	}
235 
236 	/*
237 	 * argv=command...
238 	 */
239 	else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) {
240 	    *argv += sizeof("argv=") - 1;	/* XXX clobbers argv */
241 	    attr->argv = argv;
242 	    break;
243 	}
244 
245 	/*
246 	 * Bad.
247 	 */
248 	else
249 	    msg_fatal("unknown attribute name: %s", *argv);
250     }
251 
252     /*
253      * Sanity checks. Verify that every member has an acceptable value.
254      */
255     if (user == 0)
256 	msg_fatal("missing user= attribute");
257     if (attr->argv == 0)
258 	msg_fatal("missing argv= attribute");
259     if (attr->uid == 0)
260 	msg_fatal("request to deliver as root");
261     if (attr->uid == var_owner_uid)
262 	msg_fatal("request to deliver as mail system owner");
263     if (attr->gid == 0)
264 	msg_fatal("request to use privileged group id %ld", (long) attr->gid);
265     if (attr->gid == var_owner_gid)
266 	msg_fatal("request to use mail system owner group id %ld", (long) attr->gid);
267     if (attr->uid == (uid_t) (-1))
268 	msg_fatal("user must not have user ID -1");
269     if (attr->gid == (gid_t) (-1))
270 	msg_fatal("user must not have group ID -1");
271 
272     /*
273      * Give the poor tester a clue of what is going on.
274      */
275     if (msg_verbose)
276 	msg_info("%s: uid %ld, gid %ld; time %d",
277 	      myname, (long) attr->uid, (long) attr->gid, attr->time_limit);
278 }
279 
280 /* spawn_service - perform service for client */
281 
282 static void spawn_service(VSTREAM *client_stream, char *service, char **argv)
283 {
284     const char *myname = "spawn_service";
285     static SPAWN_ATTR attr;
286     WAIT_STATUS_T status;
287     ARGV   *export_env;
288 
289     /*
290      * This routine runs whenever a client connects to the UNIX-domain socket
291      * dedicated to running an external command.
292      */
293     if (msg_verbose)
294 	msg_info("%s: service=%s, command=%s...", myname, service, argv[0]);
295 
296     /*
297      * Look up service attributes and config information only once. This is
298      * safe since the information comes from a trusted source.
299      */
300     if (attr.argv == 0) {
301 	get_service_attr(&attr, service, argv);
302     }
303 
304     /*
305      * Execute the command.
306      */
307     export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
308     status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)),
309 			 CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)),
310 			 CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)),
311 			   CA_SPAWN_CMD_UID(attr.uid),
312 			   CA_SPAWN_CMD_GID(attr.gid),
313 			   CA_SPAWN_CMD_ARGV(attr.argv),
314 			   CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit),
315 			   CA_SPAWN_CMD_EXPORT(export_env->argv),
316 			   CA_SPAWN_CMD_END);
317     argv_free(export_env);
318 
319     /*
320      * Warn about unsuccessful completion.
321      */
322     if (!NORMAL_EXIT_STATUS(status)) {
323 	if (WIFEXITED(status))
324 	    msg_warn("command %s exit status %d",
325 		     attr.argv[0], WEXITSTATUS(status));
326 	if (WIFSIGNALED(status))
327 	    msg_warn("command %s killed by signal %d",
328 		     attr.argv[0], WTERMSIG(status));
329     }
330 }
331 
332 /* pre_accept - see if tables have changed */
333 
334 static void pre_accept(char *unused_name, char **unused_argv)
335 {
336     const char *table;
337 
338     if ((table = dict_changed_name()) != 0) {
339 	msg_info("table %s has changed -- restarting", table);
340 	exit(0);
341     }
342 }
343 
344 /* drop_privileges - drop privileges most of the time */
345 
346 static void drop_privileges(char *unused_name, char **unused_argv)
347 {
348     set_eugid(var_owner_uid, var_owner_gid);
349 }
350 
351 MAIL_VERSION_STAMP_DECLARE;
352 
353 /* main - pass control to the single-threaded skeleton */
354 
355 int     main(int argc, char **argv)
356 {
357     static const CONFIG_TIME_TABLE time_table[] = {
358 	VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0,
359 	0,
360     };
361 
362     /*
363      * Fingerprint executables and core dumps.
364      */
365     MAIL_VERSION_STAMP_ALLOCATE;
366 
367     single_server_main(argc, argv, spawn_service,
368 		       CA_MAIL_SERVER_TIME_TABLE(time_table),
369 		       CA_MAIL_SERVER_POST_INIT(drop_privileges),
370 		       CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
371 		       CA_MAIL_SERVER_PRIVILEGED,
372 		       0);
373 }
374