7 /* run external command
9 /* #include <spawn_command.h>
11 /* WAIT_STATUS_T spawn_command(key, value, ...)
14 /* spawn_command() runs a command in a child process and returns
15 /* the command exit status.
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
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
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).
70 /* The Secure Mailer license must be distributed with this software.
72 /* exec_command(3) execute command
75 /* IBM T.J. Watson Research
77 /* Yorktown Heights, NY 10598, USA
94 /* Utility library. */
97 #include <timed_wait.h>
100 #include <spawn_command.h>
101 #include <exec_command.h>
102 #include <clean_env.h>
104 /* Application-specific. */
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";
128 * First, set the default values.
133 args
->stdout_fd
= -1;
134 args
->stderr_fd
= -1;
135 args
->uid
= (uid_t
) - 1;
136 args
->gid
= (gid_t
) - 1;
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)) {
149 msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
151 args
->argv
= va_arg(ap
, char **);
153 case SPAWN_CMD_COMMAND
:
155 msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
157 args
->command
= va_arg(ap
, char *);
159 case SPAWN_CMD_STDIN
:
160 args
->stdin_fd
= va_arg(ap
, int);
162 case SPAWN_CMD_STDOUT
:
163 args
->stdout_fd
= va_arg(ap
, int);
165 case SPAWN_CMD_STDERR
:
166 args
->stderr_fd
= va_arg(ap
, int);
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");
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");
178 case SPAWN_CMD_TIME_LIMIT
:
179 args
->time_limit
= va_arg(ap
, int);
182 args
->env
= va_arg(ap
, char **);
184 case SPAWN_CMD_EXPORT
:
185 args
->export
= va_arg(ap
, char **);
187 case SPAWN_CMD_SHELL
:
188 args
->shell
= va_arg(ap
, char *);
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",
201 /* spawn_command - execute command with extreme prejudice */
203 WAIT_STATUS_T
spawn_command(int key
,...)
205 const char *myname
= "spawn_comand";
208 WAIT_STATUS_T wait_status
;
209 struct spawn_args args
;
215 * Process the variadic argument list. This also does sanity checks on
216 * what data the caller is passing to us.
219 get_spawn_args(&args
, key
, ap
);
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
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.
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.
248 if (args
.uid
!= (uid_t
) - 1 || args
.gid
!= (gid_t
) - 1)
249 set_ugid(args
.uid
, args
.gid
);
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().
265 clean_env(args
.export
);
266 if (setenv("PATH", _PATH_DEFPATH
, 1))
267 msg_fatal("%s: setenv: %m", myname
);
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.
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]);
287 exec_command(args
.command
);
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
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
);
309 err
= waitpid(pid
, &wait_status
, 0);
312 msg_fatal("wait: %m");
313 return (wait_status
);