Postfix3.3.1
spawn_command.c
[詳解]
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 /* spawn_command() takes a list of macros with arguments,
18 /* terminated by CA_SPAWN_CMD_END which has no arguments. The
19 /* following is a listing of macros and expected argument
20 /* types.
21 /* .RS
22 /* .IP "CA_SPAWN_CMD_COMMAND(const 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 CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
28 /* See also the SPAWN_CMD_SHELL attribute below.
29 /* .IP "CA_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 CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
33 /* .IP "CA_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 "CA_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 "CA_SPAWN_CMD_STDIN(int)"
41 /* .IP "CA_SPAWN_CMD_STDOUT(int)"
42 /* .IP "CA_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 "CA_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 "CA_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 "CA_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 "CA_SPAWN_CMD_SHELL(const char *)"
55 /* The shell to use when executing the command specified with
56 /* CA_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 
104 struct 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 
120 static 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;
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 
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, CHARS_SPACE);
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 }
char ** export
Definition: argv.h:17
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
WAIT_STATUS_T spawn_command(int key,...)
#define SPAWN_CMD_COMMAND
Definition: spawn_command.h:22
char ** argv
Definition: argv.h:20
char ** env
void argv_add(ARGV *argvp,...)
Definition: argv.c:197
#define SPAWN_CMD_UID
Definition: spawn_command.h:26
#define SPAWN_CMD_TIME_LIMIT
Definition: spawn_command.h:28
void clean_env(char **preserve_list)
Definition: clean_env.c:59
char * shell
#define SPAWN_CMD_STDIN
Definition: spawn_command.h:23
#define SPAWN_CMD_EXPORT
Definition: spawn_command.h:31
void msg_warn(const char *fmt,...)
Definition: msg.c:215
#define CHARS_SPACE
Definition: sys_defs.h:1762
char ** argv
#define SPAWN_CMD_END
Definition: spawn_command.h:20
int timed_waitpid(pid_t pid, WAIT_STATUS_T *statusp, int options, int time_limit)
Definition: timed_wait.c:80
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
ARGV * argv_split(const char *, const char *)
Definition: argv_split.c:63
void set_ugid(uid_t uid, gid_t gid)
Definition: set_ugid.c:45
#define SPAWN_CMD_SHELL
Definition: spawn_command.h:30
#define SPAWN_CMD_STDERR
Definition: spawn_command.h:25
char * command
#define SPAWN_CMD_ARGV
Definition: spawn_command.h:21
NORETURN exec_command(const char *command)
Definition: exec_command.c:51
#define SPAWN_CMD_ENV
Definition: spawn_command.h:29
#define SPAWN_CMD_STDOUT
Definition: spawn_command.h:24
#define SPAWN_CMD_GID
Definition: spawn_command.h:27
int WAIT_STATUS_T
Definition: sys_defs.h:1436
#define DUP2
Definition: sys_defs.h:1307
void argv_terminate(ARGV *argvp)
Definition: argv.c:242