1 /* $NetBSD: spawn_command.c,v 1.1.1.1 2009/06/23 10:09:01 tron 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 /* 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 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 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 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, " \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