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 
106 struct 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 
get_spawn_args(struct spawn_args * args,int init_key,va_list ap)122 static 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 
spawn_command(int key,...)203 WAIT_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