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