1/*++
2/* NAME
3/*	spawn_command 3
4/* SUMMARY
5/*	run external command
6/* SYNOPSIS
7/*	#include <spawn_command.h>
8/*
9/*	WAIT_STATUS_T spawn_command(key, value, ...)
10/*	int	key;
11/* DESCRIPTION
12/*	spawn_command() runs a command in a child process and returns
13/*	the command exit status.
14/*
15/*	Arguments:
16/* .IP key
17/*	Specifies what value will follow. spawn_command() takes a list
18/*	of (key, value) arguments, terminated by SPAWN_CMD_END. The
19/*	following is a listing of key codes together with the expected
20/*	value type.
21/* .RS
22/* .IP "SPAWN_CMD_COMMAND (char *)"
23/*	Specifies the command to execute as a string. The string is
24/*	passed to the shell when it contains shell meta characters
25/*	or when it appears to be a shell built-in command, otherwise
26/*	the command is executed without invoking a shell.
27/*	One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
28/*	See also the SPAWN_CMD_SHELL attribute below.
29/* .IP "SPAWN_CMD_ARGV (char **)"
30/*	The command is specified as an argument vector. This vector is
31/*	passed without further inspection to the \fIexecvp\fR() routine.
32/*	One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
33/* .IP "SPAWN_CMD_ENV (char **)"
34/*	Additional environment information, in the form of a null-terminated
35/*	list of name, value, name, value, ... elements. By default only the
36/*	command search path is initialized to _PATH_DEFPATH.
37/* .IP "SPAWN_CMD_EXPORT (char **)"
38/*	Null-terminated array of names of environment parameters that can
39/*	be exported. By default, everything is exported.
40/* .IP "SPAWN_CMD_STDIN (int)"
41/* .IP "SPAWN_CMD_STDOUT (int)"
42/* .IP "SPAWN_CMD_STDERR (int)"
43/*	Each of these specifies I/O redirection of one of the standard file
44/*	descriptors for the command.
45/* .IP "SPAWN_CMD_UID (uid_t)"
46/*	The user ID to execute the command as. The value -1 is reserved
47/*	and cannot be specified.
48/* .IP "SPAWN_CMD_GID (gid_t)"
49/*	The group ID to execute the command as. The value -1 is reserved
50/*	and cannot be specified.
51/* .IP "SPAWN_CMD_TIME_LIMIT (int)"
52/*	The amount of time in seconds the command is allowed to run before
53/*	it is terminated with SIGKILL. The default is no time limit.
54/* .IP "SPAWN_CMD_SHELL (char *)"
55/*	The shell to use when executing the command specified with
56/*	SPAWN_CMD_COMMAND. This shell is invoked regardless of the
57/*	command content.
58/* .RE
59/* DIAGNOSTICS
60/*	Panic: interface violations (for example, a missing command).
61/*
62/*	Fatal error: fork() failure, other system call failures.
63/*
64/*	spawn_command() returns the exit status as defined by wait(2).
65/* LICENSE
66/* .ad
67/* .fi
68/*	The Secure Mailer license must be distributed with this software.
69/* SEE ALSO
70/*	exec_command(3) execute command
71/* AUTHOR(S)
72/*	Wietse Venema
73/*	IBM T.J. Watson Research
74/*	P.O. Box 704
75/*	Yorktown Heights, NY 10598, USA
76/*--*/
77
78/* System library. */
79
80#include <sys_defs.h>
81#include <sys/wait.h>
82#include <signal.h>
83#include <unistd.h>
84#include <errno.h>
85#include <stdarg.h>
86#include <stdlib.h>
87#ifdef USE_PATHS_H
88#include <paths.h>
89#endif
90#include <syslog.h>
91
92/* Utility library. */
93
94#include <msg.h>
95#include <timed_wait.h>
96#include <set_ugid.h>
97#include <argv.h>
98#include <spawn_command.h>
99#include <exec_command.h>
100#include <clean_env.h>
101
102/* Application-specific. */
103
104struct spawn_args {
105    char  **argv;			/* argument vector */
106    char   *command;			/* or a plain string */
107    int     stdin_fd;			/* read stdin here */
108    int     stdout_fd;			/* write stdout here */
109    int     stderr_fd;			/* write stderr here */
110    uid_t   uid;			/* privileges */
111    gid_t   gid;			/* privileges */
112    char  **env;			/* extra environment */
113    char  **export;			/* exportable environment */
114    char   *shell;			/* command shell */
115    int     time_limit;			/* command time limit */
116};
117
118/* get_spawn_args - capture the variadic argument list */
119
120static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
121{
122    const char *myname = "get_spawn_args";
123    int     key;
124
125    /*
126     * First, set the default values.
127     */
128    args->argv = 0;
129    args->command = 0;
130    args->stdin_fd = -1;
131    args->stdout_fd = -1;
132    args->stderr_fd = -1;
133    args->uid = (uid_t) - 1;
134    args->gid = (gid_t) - 1;
135    args->env = 0;
136    args->export = 0;
137    args->shell = 0;
138    args->time_limit = 0;
139
140    /*
141     * Then, override the defaults with user-supplied inputs.
142     */
143    for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
144	switch (key) {
145	case SPAWN_CMD_ARGV:
146	    if (args->command)
147		msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
148			  myname);
149	    args->argv = va_arg(ap, char **);
150	    break;
151	case SPAWN_CMD_COMMAND:
152	    if (args->argv)
153		msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
154			  myname);
155	    args->command = va_arg(ap, char *);
156	    break;
157	case SPAWN_CMD_STDIN:
158	    args->stdin_fd = va_arg(ap, int);
159	    break;
160	case SPAWN_CMD_STDOUT:
161	    args->stdout_fd = va_arg(ap, int);
162	    break;
163	case SPAWN_CMD_STDERR:
164	    args->stderr_fd = va_arg(ap, int);
165	    break;
166	case SPAWN_CMD_UID:
167	    args->uid = va_arg(ap, uid_t);
168	    if (args->uid == (uid_t) (-1))
169		msg_panic("spawn_command: request with reserved user ID: -1");
170	    break;
171	case SPAWN_CMD_GID:
172	    args->gid = va_arg(ap, gid_t);
173	    if (args->gid == (gid_t) (-1))
174		msg_panic("spawn_command: request with reserved group ID: -1");
175	    break;
176	case SPAWN_CMD_TIME_LIMIT:
177	    args->time_limit = va_arg(ap, int);
178	    break;
179	case SPAWN_CMD_ENV:
180	    args->env = va_arg(ap, char **);
181	    break;
182	case SPAWN_CMD_EXPORT:
183	    args->export = va_arg(ap, char **);
184	    break;
185	case SPAWN_CMD_SHELL:
186	    args->shell = va_arg(ap, char *);
187	    break;
188	default:
189	    msg_panic("%s: unknown key: %d", myname, key);
190	}
191    }
192    if (args->command == 0 && args->argv == 0)
193	msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
194    if (args->command == 0 && args->shell != 0)
195	msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
196		  myname);
197}
198
199/* spawn_command - execute command with extreme prejudice */
200
201WAIT_STATUS_T spawn_command(int key,...)
202{
203    const char *myname = "spawn_comand";
204    va_list ap;
205    pid_t   pid;
206    WAIT_STATUS_T wait_status;
207    struct spawn_args args;
208    char  **cpp;
209    ARGV   *argv;
210    int     err;
211
212    /*
213     * Process the variadic argument list. This also does sanity checks on
214     * what data the caller is passing to us.
215     */
216    va_start(ap, key);
217    get_spawn_args(&args, key, ap);
218    va_end(ap);
219
220    /*
221     * For convenience...
222     */
223    if (args.command == 0)
224	args.command = args.argv[0];
225
226    /*
227     * Spawn off a child process and irrevocably change privilege to the
228     * user. This includes revoking all rights on open files (via the close
229     * on exec flag). If we cannot run the command now, try again some time
230     * later.
231     */
232    switch (pid = fork()) {
233
234	/*
235	 * Error. Instead of trying again right now, back off, give the
236	 * system a chance to recover, and try again later.
237	 */
238    case -1:
239	msg_fatal("fork: %m");
240
241	/*
242	 * Child. Run the child in a separate process group so that the
243	 * parent can kill not just the child but also its offspring.
244	 */
245    case 0:
246	if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
247	    set_ugid(args.uid, args.gid);
248	setsid();
249
250	/*
251	 * Pipe plumbing.
252	 */
253	if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
254	 || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
255	|| (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
256	    msg_fatal("%s: dup2: %m", myname);
257
258	/*
259	 * Environment plumbing. Always reset the command search path. XXX
260	 * That should probably be done by clean_env().
261	 */
262	if (args.export)
263	    clean_env(args.export);
264	if (setenv("PATH", _PATH_DEFPATH, 1))
265	    msg_fatal("%s: setenv: %m", myname);
266	if (args.env)
267	    for (cpp = args.env; *cpp; cpp += 2)
268		if (setenv(cpp[0], cpp[1], 1))
269		    msg_fatal("setenv: %m");
270
271	/*
272	 * Process plumbing. If possible, avoid running a shell.
273	 */
274	closelog();
275	if (args.argv) {
276	    execvp(args.argv[0], args.argv);
277	    msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
278	} else if (args.shell && *args.shell) {
279	    argv = argv_split(args.shell, " \t\r\n");
280	    argv_add(argv, args.command, (char *) 0);
281	    argv_terminate(argv);
282	    execvp(argv->argv[0], argv->argv);
283	    msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
284	} else {
285	    exec_command(args.command);
286	}
287	/* NOTREACHED */
288
289	/*
290	 * Parent.
291	 */
292    default:
293
294	/*
295	 * Be prepared for the situation that the child does not terminate.
296	 * Make sure that the child terminates before the parent attempts to
297	 * retrieve its exit status, otherwise the parent could become stuck,
298	 * and the mail system would eventually run out of exec daemons. Do a
299	 * thorough job, and kill not just the child process but also its
300	 * offspring.
301	 */
302	if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
303	    && errno == ETIMEDOUT) {
304	    msg_warn("%s: process id %lu: command time limit exceeded",
305		     args.command, (unsigned long) pid);
306	    kill(-pid, SIGKILL);
307	    err = waitpid(pid, &wait_status, 0);
308	}
309	if (err < 0)
310	    msg_fatal("wait: %m");
311	return (wait_status);
312    }
313}
314