xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/spawn_command.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1*e89934bbSchristos /*	$NetBSD: spawn_command.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
241fbaed0Stron 
341fbaed0Stron /*++
441fbaed0Stron /* NAME
541fbaed0Stron /*	spawn_command 3
641fbaed0Stron /* SUMMARY
741fbaed0Stron /*	run external command
841fbaed0Stron /* SYNOPSIS
941fbaed0Stron /*	#include <spawn_command.h>
1041fbaed0Stron /*
1141fbaed0Stron /*	WAIT_STATUS_T spawn_command(key, value, ...)
1241fbaed0Stron /*	int	key;
1341fbaed0Stron /* DESCRIPTION
1441fbaed0Stron /*	spawn_command() runs a command in a child process and returns
1541fbaed0Stron /*	the command exit status.
1641fbaed0Stron /*
1741fbaed0Stron /*	Arguments:
1841fbaed0Stron /* .IP key
19e262b48eSchristos /*	spawn_command() takes a list of macros with arguments,
20e262b48eSchristos /*	terminated by CA_SPAWN_CMD_END which has no arguments. The
21e262b48eSchristos /*	following is a listing of macros and expected argument
22e262b48eSchristos /*	types.
2341fbaed0Stron /* .RS
24e262b48eSchristos /* .IP "CA_SPAWN_CMD_COMMAND(const char *)"
2541fbaed0Stron /*	Specifies the command to execute as a string. The string is
2641fbaed0Stron /*	passed to the shell when it contains shell meta characters
2741fbaed0Stron /*	or when it appears to be a shell built-in command, otherwise
2841fbaed0Stron /*	the command is executed without invoking a shell.
29e262b48eSchristos /*	One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
3041fbaed0Stron /*	See also the SPAWN_CMD_SHELL attribute below.
31e262b48eSchristos /* .IP "CA_SPAWN_CMD_ARGV(char **)"
3241fbaed0Stron /*	The command is specified as an argument vector. This vector is
3341fbaed0Stron /*	passed without further inspection to the \fIexecvp\fR() routine.
34e262b48eSchristos /*	One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
35e262b48eSchristos /* .IP "CA_SPAWN_CMD_ENV(char **)"
3641fbaed0Stron /*	Additional environment information, in the form of a null-terminated
3741fbaed0Stron /*	list of name, value, name, value, ... elements. By default only the
3841fbaed0Stron /*	command search path is initialized to _PATH_DEFPATH.
39e262b48eSchristos /* .IP "CA_SPAWN_CMD_EXPORT(char **)"
4041fbaed0Stron /*	Null-terminated array of names of environment parameters that can
4141fbaed0Stron /*	be exported. By default, everything is exported.
42e262b48eSchristos /* .IP "CA_SPAWN_CMD_STDIN(int)"
43e262b48eSchristos /* .IP "CA_SPAWN_CMD_STDOUT(int)"
44e262b48eSchristos /* .IP "CA_SPAWN_CMD_STDERR(int)"
4541fbaed0Stron /*	Each of these specifies I/O redirection of one of the standard file
4641fbaed0Stron /*	descriptors for the command.
47e262b48eSchristos /* .IP "CA_SPAWN_CMD_UID(uid_t)"
4841fbaed0Stron /*	The user ID to execute the command as. The value -1 is reserved
4941fbaed0Stron /*	and cannot be specified.
50e262b48eSchristos /* .IP "CA_SPAWN_CMD_GID(gid_t)"
5141fbaed0Stron /*	The group ID to execute the command as. The value -1 is reserved
5241fbaed0Stron /*	and cannot be specified.
53e262b48eSchristos /* .IP "CA_SPAWN_CMD_TIME_LIMIT(int)"
5441fbaed0Stron /*	The amount of time in seconds the command is allowed to run before
5541fbaed0Stron /*	it is terminated with SIGKILL. The default is no time limit.
56e262b48eSchristos /* .IP "CA_SPAWN_CMD_SHELL(const char *)"
5741fbaed0Stron /*	The shell to use when executing the command specified with
58e262b48eSchristos /*	CA_SPAWN_CMD_COMMAND. This shell is invoked regardless of the
5941fbaed0Stron /*	command content.
6041fbaed0Stron /* .RE
6141fbaed0Stron /* DIAGNOSTICS
6241fbaed0Stron /*	Panic: interface violations (for example, a missing command).
6341fbaed0Stron /*
6441fbaed0Stron /*	Fatal error: fork() failure, other system call failures.
6541fbaed0Stron /*
6641fbaed0Stron /*	spawn_command() returns the exit status as defined by wait(2).
6741fbaed0Stron /* LICENSE
6841fbaed0Stron /* .ad
6941fbaed0Stron /* .fi
7041fbaed0Stron /*	The Secure Mailer license must be distributed with this software.
7141fbaed0Stron /* SEE ALSO
7241fbaed0Stron /*	exec_command(3) execute command
7341fbaed0Stron /* AUTHOR(S)
7441fbaed0Stron /*	Wietse Venema
7541fbaed0Stron /*	IBM T.J. Watson Research
7641fbaed0Stron /*	P.O. Box 704
7741fbaed0Stron /*	Yorktown Heights, NY 10598, USA
7841fbaed0Stron /*--*/
7941fbaed0Stron 
8041fbaed0Stron /* System library. */
8141fbaed0Stron 
8241fbaed0Stron #include <sys_defs.h>
8341fbaed0Stron #include <sys/wait.h>
8441fbaed0Stron #include <signal.h>
8541fbaed0Stron #include <unistd.h>
8641fbaed0Stron #include <errno.h>
8741fbaed0Stron #include <stdarg.h>
8841fbaed0Stron #include <stdlib.h>
8941fbaed0Stron #ifdef USE_PATHS_H
9041fbaed0Stron #include <paths.h>
9141fbaed0Stron #endif
9241fbaed0Stron #include <syslog.h>
9341fbaed0Stron 
9441fbaed0Stron /* Utility library. */
9541fbaed0Stron 
9641fbaed0Stron #include <msg.h>
9741fbaed0Stron #include <timed_wait.h>
9841fbaed0Stron #include <set_ugid.h>
9941fbaed0Stron #include <argv.h>
10041fbaed0Stron #include <spawn_command.h>
10141fbaed0Stron #include <exec_command.h>
10241fbaed0Stron #include <clean_env.h>
10341fbaed0Stron 
10441fbaed0Stron /* Application-specific. */
10541fbaed0Stron 
10641fbaed0Stron struct spawn_args {
10741fbaed0Stron     char  **argv;			/* argument vector */
10841fbaed0Stron     char   *command;			/* or a plain string */
10941fbaed0Stron     int     stdin_fd;			/* read stdin here */
11041fbaed0Stron     int     stdout_fd;			/* write stdout here */
11141fbaed0Stron     int     stderr_fd;			/* write stderr here */
11241fbaed0Stron     uid_t   uid;			/* privileges */
11341fbaed0Stron     gid_t   gid;			/* privileges */
11441fbaed0Stron     char  **env;			/* extra environment */
11541fbaed0Stron     char  **export;			/* exportable environment */
11641fbaed0Stron     char   *shell;			/* command shell */
11741fbaed0Stron     int     time_limit;			/* command time limit */
11841fbaed0Stron };
11941fbaed0Stron 
12041fbaed0Stron /* get_spawn_args - capture the variadic argument list */
12141fbaed0Stron 
get_spawn_args(struct spawn_args * args,int init_key,va_list ap)12241fbaed0Stron static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
12341fbaed0Stron {
12441fbaed0Stron     const char *myname = "get_spawn_args";
12541fbaed0Stron     int     key;
12641fbaed0Stron 
12741fbaed0Stron     /*
12841fbaed0Stron      * First, set the default values.
12941fbaed0Stron      */
13041fbaed0Stron     args->argv = 0;
13141fbaed0Stron     args->command = 0;
13241fbaed0Stron     args->stdin_fd = -1;
13341fbaed0Stron     args->stdout_fd = -1;
13441fbaed0Stron     args->stderr_fd = -1;
13541fbaed0Stron     args->uid = (uid_t) - 1;
13641fbaed0Stron     args->gid = (gid_t) - 1;
13741fbaed0Stron     args->env = 0;
13841fbaed0Stron     args->export = 0;
13941fbaed0Stron     args->shell = 0;
14041fbaed0Stron     args->time_limit = 0;
14141fbaed0Stron 
14241fbaed0Stron     /*
14341fbaed0Stron      * Then, override the defaults with user-supplied inputs.
14441fbaed0Stron      */
14541fbaed0Stron     for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
14641fbaed0Stron 	switch (key) {
14741fbaed0Stron 	case SPAWN_CMD_ARGV:
14841fbaed0Stron 	    if (args->command)
14941fbaed0Stron 		msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
15041fbaed0Stron 			  myname);
15141fbaed0Stron 	    args->argv = va_arg(ap, char **);
15241fbaed0Stron 	    break;
15341fbaed0Stron 	case SPAWN_CMD_COMMAND:
15441fbaed0Stron 	    if (args->argv)
15541fbaed0Stron 		msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
15641fbaed0Stron 			  myname);
15741fbaed0Stron 	    args->command = va_arg(ap, char *);
15841fbaed0Stron 	    break;
15941fbaed0Stron 	case SPAWN_CMD_STDIN:
16041fbaed0Stron 	    args->stdin_fd = va_arg(ap, int);
16141fbaed0Stron 	    break;
16241fbaed0Stron 	case SPAWN_CMD_STDOUT:
16341fbaed0Stron 	    args->stdout_fd = va_arg(ap, int);
16441fbaed0Stron 	    break;
16541fbaed0Stron 	case SPAWN_CMD_STDERR:
16641fbaed0Stron 	    args->stderr_fd = va_arg(ap, int);
16741fbaed0Stron 	    break;
16841fbaed0Stron 	case SPAWN_CMD_UID:
16941fbaed0Stron 	    args->uid = va_arg(ap, uid_t);
17041fbaed0Stron 	    if (args->uid == (uid_t) (-1))
17141fbaed0Stron 		msg_panic("spawn_command: request with reserved user ID: -1");
17241fbaed0Stron 	    break;
17341fbaed0Stron 	case SPAWN_CMD_GID:
17441fbaed0Stron 	    args->gid = va_arg(ap, gid_t);
17541fbaed0Stron 	    if (args->gid == (gid_t) (-1))
17641fbaed0Stron 		msg_panic("spawn_command: request with reserved group ID: -1");
17741fbaed0Stron 	    break;
17841fbaed0Stron 	case SPAWN_CMD_TIME_LIMIT:
17941fbaed0Stron 	    args->time_limit = va_arg(ap, int);
18041fbaed0Stron 	    break;
18141fbaed0Stron 	case SPAWN_CMD_ENV:
18241fbaed0Stron 	    args->env = va_arg(ap, char **);
18341fbaed0Stron 	    break;
18441fbaed0Stron 	case SPAWN_CMD_EXPORT:
18541fbaed0Stron 	    args->export = va_arg(ap, char **);
18641fbaed0Stron 	    break;
18741fbaed0Stron 	case SPAWN_CMD_SHELL:
18841fbaed0Stron 	    args->shell = va_arg(ap, char *);
18941fbaed0Stron 	    break;
19041fbaed0Stron 	default:
19141fbaed0Stron 	    msg_panic("%s: unknown key: %d", myname, key);
19241fbaed0Stron 	}
19341fbaed0Stron     }
19441fbaed0Stron     if (args->command == 0 && args->argv == 0)
19541fbaed0Stron 	msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
19641fbaed0Stron     if (args->command == 0 && args->shell != 0)
19741fbaed0Stron 	msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
19841fbaed0Stron 		  myname);
19941fbaed0Stron }
20041fbaed0Stron 
20141fbaed0Stron /* spawn_command - execute command with extreme prejudice */
20241fbaed0Stron 
spawn_command(int key,...)20341fbaed0Stron WAIT_STATUS_T spawn_command(int key,...)
20441fbaed0Stron {
20541fbaed0Stron     const char *myname = "spawn_comand";
20641fbaed0Stron     va_list ap;
20741fbaed0Stron     pid_t   pid;
20841fbaed0Stron     WAIT_STATUS_T wait_status;
20941fbaed0Stron     struct spawn_args args;
21041fbaed0Stron     char  **cpp;
21141fbaed0Stron     ARGV   *argv;
21241fbaed0Stron     int     err;
21341fbaed0Stron 
21441fbaed0Stron     /*
21541fbaed0Stron      * Process the variadic argument list. This also does sanity checks on
21641fbaed0Stron      * what data the caller is passing to us.
21741fbaed0Stron      */
21841fbaed0Stron     va_start(ap, key);
21941fbaed0Stron     get_spawn_args(&args, key, ap);
22041fbaed0Stron     va_end(ap);
22141fbaed0Stron 
22241fbaed0Stron     /*
22341fbaed0Stron      * For convenience...
22441fbaed0Stron      */
22541fbaed0Stron     if (args.command == 0)
22641fbaed0Stron 	args.command = args.argv[0];
22741fbaed0Stron 
22841fbaed0Stron     /*
22941fbaed0Stron      * Spawn off a child process and irrevocably change privilege to the
23041fbaed0Stron      * user. This includes revoking all rights on open files (via the close
23141fbaed0Stron      * on exec flag). If we cannot run the command now, try again some time
23241fbaed0Stron      * later.
23341fbaed0Stron      */
23441fbaed0Stron     switch (pid = fork()) {
23541fbaed0Stron 
23641fbaed0Stron 	/*
23741fbaed0Stron 	 * Error. Instead of trying again right now, back off, give the
23841fbaed0Stron 	 * system a chance to recover, and try again later.
23941fbaed0Stron 	 */
24041fbaed0Stron     case -1:
24141fbaed0Stron 	msg_fatal("fork: %m");
24241fbaed0Stron 
24341fbaed0Stron 	/*
24441fbaed0Stron 	 * Child. Run the child in a separate process group so that the
24541fbaed0Stron 	 * parent can kill not just the child but also its offspring.
24641fbaed0Stron 	 */
24741fbaed0Stron     case 0:
24841fbaed0Stron 	if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
24941fbaed0Stron 	    set_ugid(args.uid, args.gid);
25041fbaed0Stron 	setsid();
25141fbaed0Stron 
25241fbaed0Stron 	/*
25341fbaed0Stron 	 * Pipe plumbing.
25441fbaed0Stron 	 */
25541fbaed0Stron 	if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
25641fbaed0Stron 	 || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
25741fbaed0Stron 	|| (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
25841fbaed0Stron 	    msg_fatal("%s: dup2: %m", myname);
25941fbaed0Stron 
26041fbaed0Stron 	/*
26141fbaed0Stron 	 * Environment plumbing. Always reset the command search path. XXX
26241fbaed0Stron 	 * That should probably be done by clean_env().
26341fbaed0Stron 	 */
26441fbaed0Stron 	if (args.export)
26541fbaed0Stron 	    clean_env(args.export);
26641fbaed0Stron 	if (setenv("PATH", _PATH_DEFPATH, 1))
26741fbaed0Stron 	    msg_fatal("%s: setenv: %m", myname);
26841fbaed0Stron 	if (args.env)
26941fbaed0Stron 	    for (cpp = args.env; *cpp; cpp += 2)
27041fbaed0Stron 		if (setenv(cpp[0], cpp[1], 1))
27141fbaed0Stron 		    msg_fatal("setenv: %m");
27241fbaed0Stron 
27341fbaed0Stron 	/*
27441fbaed0Stron 	 * Process plumbing. If possible, avoid running a shell.
27541fbaed0Stron 	 */
27641fbaed0Stron 	closelog();
27741fbaed0Stron 	if (args.argv) {
27841fbaed0Stron 	    execvp(args.argv[0], args.argv);
27941fbaed0Stron 	    msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
28041fbaed0Stron 	} else if (args.shell && *args.shell) {
281e262b48eSchristos 	    argv = argv_split(args.shell, CHARS_SPACE);
28241fbaed0Stron 	    argv_add(argv, args.command, (char *) 0);
28341fbaed0Stron 	    argv_terminate(argv);
28441fbaed0Stron 	    execvp(argv->argv[0], argv->argv);
28541fbaed0Stron 	    msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
28641fbaed0Stron 	} else {
28741fbaed0Stron 	    exec_command(args.command);
28841fbaed0Stron 	}
28941fbaed0Stron 	/* NOTREACHED */
29041fbaed0Stron 
29141fbaed0Stron 	/*
29241fbaed0Stron 	 * Parent.
29341fbaed0Stron 	 */
29441fbaed0Stron     default:
29541fbaed0Stron 
29641fbaed0Stron 	/*
29741fbaed0Stron 	 * Be prepared for the situation that the child does not terminate.
29841fbaed0Stron 	 * Make sure that the child terminates before the parent attempts to
29941fbaed0Stron 	 * retrieve its exit status, otherwise the parent could become stuck,
30041fbaed0Stron 	 * and the mail system would eventually run out of exec daemons. Do a
30141fbaed0Stron 	 * thorough job, and kill not just the child process but also its
30241fbaed0Stron 	 * offspring.
30341fbaed0Stron 	 */
30441fbaed0Stron 	if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
30541fbaed0Stron 	    && errno == ETIMEDOUT) {
30641fbaed0Stron 	    msg_warn("%s: process id %lu: command time limit exceeded",
30741fbaed0Stron 		     args.command, (unsigned long) pid);
30841fbaed0Stron 	    kill(-pid, SIGKILL);
30941fbaed0Stron 	    err = waitpid(pid, &wait_status, 0);
31041fbaed0Stron 	}
31141fbaed0Stron 	if (err < 0)
31241fbaed0Stron 	    msg_fatal("wait: %m");
31341fbaed0Stron 	return (wait_status);
31441fbaed0Stron     }
31541fbaed0Stron }
316