1/* $NetBSD$ */ 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/* Specifies what value will follow. spawn_command() takes a list 20/* of (key, value) arguments, terminated by SPAWN_CMD_END. The 21/* following is a listing of key codes together with the expected 22/* value type. 23/* .RS 24/* .IP "SPAWN_CMD_COMMAND (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 SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified. 30/* See also the SPAWN_CMD_SHELL attribute below. 31/* .IP "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 SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified. 35/* .IP "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 "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 "SPAWN_CMD_STDIN (int)" 43/* .IP "SPAWN_CMD_STDOUT (int)" 44/* .IP "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 "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 "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 "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 "SPAWN_CMD_SHELL (char *)" 57/* The shell to use when executing the command specified with 58/* 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, " \t\r\n"); 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