No empty .Rs/.Re
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / util / spawn_command.c
blob1346809b304cc638a3afbee343799588b216d362
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* spawn_command 3
6 /* SUMMARY
7 /* run external command
8 /* SYNOPSIS
9 /* #include <spawn_command.h>
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.
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).
64 /* Fatal error: fork() failure, other system call failures.
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 /*--*/
80 /* System library. */
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>
94 /* Utility library. */
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>
104 /* Application-specific. */
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 */
120 /* get_spawn_args - capture the variadic argument list */
122 static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
124 const char *myname = "get_spawn_args";
125 int key;
128 * First, set the default values.
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;
143 * Then, override the defaults with user-supplied inputs.
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);
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);
201 /* spawn_command - execute command with extreme prejudice */
203 WAIT_STATUS_T spawn_command(int key,...)
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;
215 * Process the variadic argument list. This also does sanity checks on
216 * what data the caller is passing to us.
218 va_start(ap, key);
219 get_spawn_args(&args, key, ap);
220 va_end(ap);
223 * For convenience...
225 if (args.command == 0)
226 args.command = args.argv[0];
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.
234 switch (pid = fork()) {
237 * Error. Instead of trying again right now, back off, give the
238 * system a chance to recover, and try again later.
240 case -1:
241 msg_fatal("fork: %m");
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.
247 case 0:
248 if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
249 set_ugid(args.uid, args.gid);
250 setsid();
253 * Pipe plumbing.
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);
261 * Environment plumbing. Always reset the command search path. XXX
262 * That should probably be done by clean_env().
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");
274 * Process plumbing. If possible, avoid running a shell.
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);
289 /* NOTREACHED */
292 * Parent.
294 default:
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.
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);
311 if (err < 0)
312 msg_fatal("wait: %m");
313 return (wait_status);