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