2000-04-30 22:52:26 +04:00
|
|
|
/*++
|
|
|
|
/* NAME
|
|
|
|
/* spawn_command 3
|
|
|
|
/* SUMMARY
|
|
|
|
/* run external command
|
|
|
|
/* SYNOPSIS
|
|
|
|
/* #include <spawn_command.h>
|
|
|
|
/*
|
|
|
|
/* WAIT_STATUS_T spawn_command(key, value, ...)
|
|
|
|
/* int key;
|
|
|
|
/* DESCRIPTION
|
|
|
|
/* spawn_command() runs a command in a child process and returns
|
|
|
|
/* the command exit status.
|
|
|
|
/*
|
|
|
|
/* Arguments:
|
|
|
|
/* .IP key
|
|
|
|
/* Specifies what value will follow. spawn_command() takes a list
|
|
|
|
/* of (key, value) arguments, terminated by SPAWN_CMD_END. The
|
|
|
|
/* following is a listing of key codes together with the expected
|
|
|
|
/* value type.
|
|
|
|
/* .RS
|
|
|
|
/* .IP "SPAWN_CMD_COMMAND (char *)"
|
|
|
|
/* Specifies the command to execute as a string. The string is
|
|
|
|
/* passed to the shell when it contains shell meta characters
|
|
|
|
/* or when it appears to be a shell built-in command, otherwise
|
|
|
|
/* the command is executed without invoking a shell.
|
|
|
|
/* One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
|
|
|
|
/* See also the SPAWN_CMD_SHELL attribute below.
|
|
|
|
/* .IP "SPAWN_CMD_ARGV (char **)"
|
|
|
|
/* The command is specified as an argument vector. This vector is
|
|
|
|
/* passed without further inspection to the \fIexecvp\fR() routine.
|
|
|
|
/* One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
|
|
|
|
/* .IP "SPAWN_CMD_ENV (char **)"
|
|
|
|
/* Additional environment information, in the form of a null-terminated
|
|
|
|
/* list of name, value, name, value, ... elements. By default only the
|
|
|
|
/* command search path is initialized to _PATH_DEFPATH.
|
|
|
|
/* .IP "SPAWN_CMD_STDIN (int)"
|
|
|
|
/* .IP "SPAWN_CMD_STDOUT (int)"
|
|
|
|
/* .IP "SPAWN_CMD_STDERR (int)"
|
|
|
|
/* Each of these specifies I/O redirection of one of the standard file
|
|
|
|
/* descriptors for the command.
|
|
|
|
/* .IP "SPAWN_CMD_UID (int)"
|
|
|
|
/* The user ID to execute the command as.
|
|
|
|
/* .IP "SPAWN_CMD_GID (int)"
|
|
|
|
/* The group ID to execute the command as.
|
|
|
|
/* .IP "SPAWN_CMD_TIME_LIMIT (int)"
|
|
|
|
/* The amount of time in seconds the command is allowed to run before
|
|
|
|
/* it is terminated with SIGKILL. The default is no time limit.
|
|
|
|
/* .IP "SPAWN_CMD_SHELL (char *)"
|
|
|
|
/* The shell to use when executing the command specified with
|
|
|
|
/* SPAWN_CMD_COMMAND. This shell is invoked regardless of the
|
|
|
|
/* command content.
|
|
|
|
/* .RE
|
|
|
|
/* DIAGNOSTICS
|
|
|
|
/* Panic: interface violations (for example, a missing command).
|
|
|
|
/*
|
|
|
|
/* Fatal error: fork() failure, other system call failures.
|
|
|
|
/*
|
|
|
|
/* spawn_command() returns the exit status as defined by wait(2).
|
|
|
|
/* LICENSE
|
|
|
|
/* .ad
|
|
|
|
/* .fi
|
|
|
|
/* The Secure Mailer license must be distributed with this software.
|
|
|
|
/* SEE ALSO
|
|
|
|
/* exec_command(3) execute command
|
|
|
|
/* AUTHOR(S)
|
|
|
|
/* Wietse Venema
|
|
|
|
/* IBM T.J. Watson Research
|
|
|
|
/* P.O. Box 704
|
|
|
|
/* Yorktown Heights, NY 10598, USA
|
|
|
|
/*--*/
|
|
|
|
|
|
|
|
/* System library. */
|
|
|
|
|
|
|
|
#include <sys_defs.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#ifdef USE_PATHS_H
|
|
|
|
#include <paths.h>
|
|
|
|
#endif
|
|
|
|
#include <syslog.h>
|
|
|
|
|
|
|
|
/* Utility library. */
|
|
|
|
|
|
|
|
#include <msg.h>
|
|
|
|
#include <timed_wait.h>
|
|
|
|
#include <set_ugid.h>
|
|
|
|
#include <argv.h>
|
|
|
|
#include <spawn_command.h>
|
|
|
|
#include <exec_command.h>
|
|
|
|
#include <clean_env.h>
|
|
|
|
|
|
|
|
/* Application-specific. */
|
|
|
|
|
|
|
|
struct spawn_args {
|
|
|
|
char **argv; /* argument vector */
|
|
|
|
char *command; /* or a plain string */
|
|
|
|
int stdin_fd; /* read stdin here */
|
|
|
|
int stdout_fd; /* write stdout here */
|
|
|
|
int stderr_fd; /* write stderr here */
|
|
|
|
uid_t uid; /* privileges */
|
|
|
|
gid_t gid; /* privileges */
|
|
|
|
char **env; /* extra environment */
|
|
|
|
char *shell; /* command shell */
|
|
|
|
int time_limit; /* command time limit */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* get_spawn_args - capture the variadic argument list */
|
|
|
|
|
|
|
|
static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
|
|
|
|
{
|
2000-05-14 06:29:30 +04:00
|
|
|
const char *myname = "get_spawn_args";
|
2000-04-30 22:52:26 +04:00
|
|
|
int key;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First, set the default values.
|
|
|
|
*/
|
|
|
|
args->argv = 0;
|
|
|
|
args->command = 0;
|
|
|
|
args->stdin_fd = -1;
|
|
|
|
args->stdout_fd = -1;
|
|
|
|
args->stderr_fd = -1;
|
|
|
|
args->uid = (uid_t) - 1;
|
|
|
|
args->gid = (gid_t) - 1;
|
|
|
|
args->env = 0;
|
|
|
|
args->shell = 0;
|
|
|
|
args->time_limit = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Then, override the defaults with user-supplied inputs.
|
|
|
|
*/
|
|
|
|
for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
|
|
|
|
switch (key) {
|
|
|
|
case SPAWN_CMD_ARGV:
|
|
|
|
if (args->command)
|
|
|
|
msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
|
|
|
|
myname);
|
|
|
|
args->argv = va_arg(ap, char **);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_COMMAND:
|
|
|
|
if (args->argv)
|
|
|
|
msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
|
|
|
|
myname);
|
|
|
|
args->command = va_arg(ap, char *);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_STDIN:
|
|
|
|
args->stdin_fd = va_arg(ap, int);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_STDOUT:
|
|
|
|
args->stdout_fd = va_arg(ap, int);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_STDERR:
|
|
|
|
args->stderr_fd = va_arg(ap, int);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_UID:
|
|
|
|
args->uid = va_arg(ap, int); /* in case uid_t is short */
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_GID:
|
|
|
|
args->gid = va_arg(ap, int); /* in case gid_t is short */
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_TIME_LIMIT:
|
|
|
|
args->time_limit = va_arg(ap, int);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_ENV:
|
|
|
|
args->env = va_arg(ap, char **);
|
|
|
|
break;
|
|
|
|
case SPAWN_CMD_SHELL:
|
|
|
|
args->shell = va_arg(ap, char *);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
msg_panic("%s: unknown key: %d", myname, key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (args->command == 0 && args->argv == 0)
|
|
|
|
msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
|
|
|
|
if (args->command == 0 && args->shell != 0)
|
|
|
|
msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
|
|
|
|
myname);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* spawn_command - execute command with extreme prejudice */
|
|
|
|
|
|
|
|
WAIT_STATUS_T spawn_command(int key,...)
|
|
|
|
{
|
2000-05-14 06:29:30 +04:00
|
|
|
const char *myname = "spawn_comand";
|
2000-04-30 22:52:26 +04:00
|
|
|
va_list ap;
|
|
|
|
pid_t pid;
|
|
|
|
WAIT_STATUS_T wait_status;
|
|
|
|
struct spawn_args args;
|
|
|
|
char **cpp;
|
|
|
|
ARGV *argv;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process the variadic argument list. This also does sanity checks on
|
|
|
|
* what data the caller is passing to us.
|
|
|
|
*/
|
|
|
|
va_start(ap, key);
|
|
|
|
get_spawn_args(&args, key, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For convenience...
|
|
|
|
*/
|
|
|
|
if (args.command == 0)
|
|
|
|
args.command = args.argv[0];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Spawn off a child process and irrevocably change privilege to the
|
|
|
|
* user. This includes revoking all rights on open files (via the close
|
|
|
|
* on exec flag). If we cannot run the command now, try again some time
|
|
|
|
* later.
|
|
|
|
*/
|
|
|
|
switch (pid = fork()) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Error. Instead of trying again right now, back off, give the
|
|
|
|
* system a chance to recover, and try again later.
|
|
|
|
*/
|
|
|
|
case -1:
|
|
|
|
msg_fatal("fork: %m");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Child. Run the child in a separate process group so that the
|
|
|
|
* parent can kill not just the child but also its offspring.
|
|
|
|
*/
|
|
|
|
case 0:
|
|
|
|
if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
|
|
|
|
set_ugid(args.uid, args.gid);
|
|
|
|
setsid();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pipe plumbing.
|
|
|
|
*/
|
|
|
|
if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
|
|
|
|
|| (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
|
|
|
|
|| (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
|
|
|
|
msg_fatal("%s: dup2: %m", myname);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Environment plumbing. Always reset the command search path. XXX
|
|
|
|
* That should probably be done by clean_env().
|
|
|
|
*/
|
|
|
|
clean_env();
|
|
|
|
if (setenv("PATH", _PATH_DEFPATH, 1))
|
|
|
|
msg_fatal("%s: setenv: %m", myname);
|
|
|
|
if (args.env)
|
|
|
|
for (cpp = args.env; *cpp; cpp += 2)
|
|
|
|
if (setenv(cpp[0], cpp[1], 1))
|
|
|
|
msg_fatal("setenv: %m");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process plumbing. If possible, avoid running a shell.
|
|
|
|
*/
|
|
|
|
closelog();
|
|
|
|
if (args.argv) {
|
|
|
|
execvp(args.argv[0], args.argv);
|
|
|
|
msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
|
|
|
|
} else if (args.shell && *args.shell) {
|
|
|
|
argv = argv_split(args.shell, " \t\r\n");
|
|
|
|
argv_add(argv, args.command, (char *) 0);
|
|
|
|
argv_terminate(argv);
|
|
|
|
execvp(argv->argv[0], argv->argv);
|
|
|
|
msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
|
|
|
|
} else {
|
|
|
|
exec_command(args.command);
|
|
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parent.
|
|
|
|
*/
|
|
|
|
default:
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Be prepared for the situation that the child does not terminate.
|
|
|
|
* Make sure that the child terminates before the parent attempts to
|
|
|
|
* retrieve its exit status, otherwise the parent could become stuck,
|
|
|
|
* and the mail system would eventually run out of exec daemons. Do a
|
|
|
|
* thorough job, and kill not just the child process but also its
|
|
|
|
* offspring.
|
|
|
|
*/
|
|
|
|
if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
|
|
|
|
&& errno == ETIMEDOUT) {
|
|
|
|
msg_warn("%s: process id %d: command time limit exceeded",
|
|
|
|
args.command, pid);
|
|
|
|
kill(-pid, SIGKILL);
|
|
|
|
err = waitpid(pid, &wait_status, 0);
|
|
|
|
}
|
|
|
|
if (err < 0)
|
|
|
|
msg_fatal("wait: %m");
|
|
|
|
return (wait_status);
|
|
|
|
}
|
|
|
|
}
|