Postfix3.3.1
vstream_popen.c
[詳解]
1 /*++
2 /* NAME
3 /* vstream_popen 3
4 /* SUMMARY
5 /* open stream to child process
6 /* SYNOPSIS
7 /* #include <vstream.h>
8 /*
9 /* VSTREAM *vstream_popen(flags, key, value, ...)
10 /* int flags;
11 /* int key;
12 /*
13 /* int vstream_pclose(stream)
14 /* VSTREAM *stream;
15 /* DESCRIPTION
16 /* vstream_popen() opens a one-way or two-way stream to a user-specified
17 /* command, which is executed by a child process. The \fIflags\fR
18 /* argument is as with vstream_fopen(). The child's standard input and
19 /* standard output are redirected to the stream, which is based on a
20 /* socketpair or other suitable local IPC. vstream_popen() takes a list
21 /* of macros with zero or more arguments, terminated by
22 /* CA_VSTREAM_POPEN_END. The following is a listing of macros
23 /* with the expected argument type.
24 /* .RS
25 /* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)"
26 /* Specifies the command to execute as a string. The string is
27 /* passed to the shell when it contains shell meta characters
28 /* or when it appears to be a shell built-in command, otherwise
29 /* the command is executed without invoking a shell.
30 /* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
31 /* .IP "CA_VSTREAM_POPEN_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 CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
35 /* See also the CA_VSTREAM_POPEN_SHELL attribute below.
36 /* .IP "CA_VSTREAM_POPEN_ENV(char **)"
37 /* Additional environment information, in the form of a null-terminated
38 /* list of name, value, name, value, ... elements. By default only the
39 /* command search path is initialized to _PATH_DEFPATH.
40 /* .IP "CA_VSTREAM_POPEN_EXPORT(char **)"
41 /* This argument is passed to clean_env().
42 /* Null-terminated array of names of environment parameters
43 /* that can be exported. By default, everything is exported.
44 /* .IP "CA_VSTREAM_POPEN_UID(uid_t)"
45 /* The user ID to execute the command as. The user ID must be non-zero.
46 /* .IP "CA_VSTREAM_POPEN_GID(gid_t)"
47 /* The group ID to execute the command as. The group ID must be non-zero.
48 /* .IP "CA_VSTREAM_POPEN_SHELL(const char *)"
49 /* The shell to use when executing the command specified with
50 /* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
51 /* command content.
52 /* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))"
53 /* waitpid()-like function to reap the child exit status when
54 /* vstream_pclose() is called.
55 /* .RE
56 /* .PP
57 /* vstream_pclose() closes the named stream and returns the child
58 /* exit status. It is an error to specify a stream that was not
59 /* returned by vstream_popen() or that is no longer open.
60 /* DIAGNOSTICS
61 /* Panics: interface violations. Fatal errors: out of memory.
62 /*
63 /* vstream_popen() returns a null pointer in case of trouble.
64 /* The nature of the problem is specified via the \fIerrno\fR
65 /* global variable.
66 /*
67 /* vstream_pclose() returns -1 in case of trouble.
68 /* The nature of the problem is specified via the \fIerrno\fR
69 /* global variable.
70 /* SEE ALSO
71 /* vstream(3) light-weight buffered I/O
72 /* BUGS
73 /* The interface, stolen from popen()/pclose(), ignores errors
74 /* returned when the stream is closed, and does not distinguish
75 /* between exit status codes and kill signals.
76 /* LICENSE
77 /* .ad
78 /* .fi
79 /* The Secure Mailer license must be distributed with this software.
80 /* AUTHOR(S)
81 /* Wietse Venema
82 /* IBM T.J. Watson Research
83 /* P.O. Box 704
84 /* Yorktown Heights, NY 10598, USA
85 /*--*/
86 
87 /* System library. */
88 
89 #include <sys_defs.h>
90 #include <sys/wait.h>
91 #include <unistd.h>
92 #include <stdlib.h>
93 #include <errno.h>
94 #ifdef USE_PATHS_H
95 #include <paths.h>
96 #endif
97 #include <syslog.h>
98 
99 /* Utility library. */
100 
101 #include <msg.h>
102 #include <exec_command.h>
103 #include <vstream.h>
104 #include <argv.h>
105 #include <set_ugid.h>
106 #include <clean_env.h>
107 #include <iostuff.h>
108 
109 /* Application-specific. */
110 
111 typedef struct VSTREAM_POPEN_ARGS {
112  char **argv;
113  char *command;
114  uid_t uid;
115  gid_t gid;
117  char **env;
118  char **export;
119  char *shell;
122 
123 /* vstream_parse_args - get arguments from variadic list */
124 
125 static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap)
126 {
127  const char *myname = "vstream_parse_args";
128  int key;
129 
130  /*
131  * First, set the default values (on all non-zero entries)
132  */
133  args->argv = 0;
134  args->command = 0;
135  args->uid = 0;
136  args->gid = 0;
137  args->privileged = 0;
138  args->env = 0;
139  args->export = 0;
140  args->shell = 0;
141  args->waitpid_fn = 0;
142 
143  /*
144  * Then, override the defaults with user-supplied inputs.
145  */
146  while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) {
147  switch (key) {
148  case VSTREAM_POPEN_ARGV:
149  if (args->command != 0)
150  msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
151  args->argv = va_arg(ap, char **);
152  break;
154  if (args->argv != 0)
155  msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
156  args->command = va_arg(ap, char *);
157  break;
158  case VSTREAM_POPEN_UID:
159  args->privileged = 1;
160  args->uid = va_arg(ap, uid_t);
161  break;
162  case VSTREAM_POPEN_GID:
163  args->privileged = 1;
164  args->gid = va_arg(ap, gid_t);
165  break;
166  case VSTREAM_POPEN_ENV:
167  args->env = va_arg(ap, char **);
168  break;
170  args->export = va_arg(ap, char **);
171  break;
172  case VSTREAM_POPEN_SHELL:
173  args->shell = va_arg(ap, char *);
174  break;
176  args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
177  break;
178  default:
179  msg_panic("%s: unknown key: %d", myname, key);
180  }
181  }
182 
183  if (args->command == 0 && args->argv == 0)
184  msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname);
185  if (args->privileged != 0 && args->uid == 0)
186  msg_panic("%s: privileged uid", myname);
187  if (args->privileged != 0 && args->gid == 0)
188  msg_panic("%s: privileged gid", myname);
189 }
190 
191 /* vstream_popen - open stream to child process */
192 
193 VSTREAM *vstream_popen(int flags,...)
194 {
195  const char *myname = "vstream_popen";
196  VSTREAM_POPEN_ARGS args;
197  va_list ap;
198  VSTREAM *stream;
199  int sockfd[2];
200  int pid;
201  int fd;
202  ARGV *argv;
203  char **cpp;
204 
205  va_start(ap, flags);
206  vstream_parse_args(&args, ap);
207  va_end(ap);
208 
209  if (args.command == 0)
210  args.command = args.argv[0];
211 
212  if (duplex_pipe(sockfd) < 0)
213  return (0);
214 
215  switch (pid = fork()) {
216  case -1: /* error */
217  (void) close(sockfd[0]);
218  (void) close(sockfd[1]);
219  return (0);
220  case 0: /* child */
221  (void) msg_cleanup((MSG_CLEANUP_FN) 0);
222  if (close(sockfd[1]))
223  msg_warn("close: %m");
224  for (fd = 0; fd < 2; fd++)
225  if (sockfd[0] != fd)
226  if (DUP2(sockfd[0], fd) < 0)
227  msg_fatal("dup2: %m");
228  if (sockfd[0] >= 2 && close(sockfd[0]))
229  msg_warn("close: %m");
230 
231  /*
232  * Don't try to become someone else unless the user specified it.
233  */
234  if (args.privileged)
235  set_ugid(args.uid, args.gid);
236 
237  /*
238  * Environment plumbing. Always reset the command search path. XXX
239  * That should probably be done by clean_env().
240  */
241  if (args.export)
242  clean_env(args.export);
243  if (setenv("PATH", _PATH_DEFPATH, 1))
244  msg_fatal("%s: setenv: %m", myname);
245  if (args.env)
246  for (cpp = args.env; *cpp; cpp += 2)
247  if (setenv(cpp[0], cpp[1], 1))
248  msg_fatal("setenv: %m");
249 
250  /*
251  * Process plumbing. If possible, avoid running a shell.
252  */
253  closelog();
254  if (args.argv) {
255  execvp(args.argv[0], args.argv);
256  msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
257  } else if (args.shell && *args.shell) {
258  argv = argv_split(args.shell, CHARS_SPACE);
259  argv_add(argv, args.command, (char *) 0);
260  argv_terminate(argv);
261  execvp(argv->argv[0], argv->argv);
262  msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
263  } else {
264  exec_command(args.command);
265  }
266  /* NOTREACHED */
267  default: /* parent */
268  if (close(sockfd[0]))
269  msg_warn("close: %m");
270  stream = vstream_fdopen(sockfd[1], flags);
271  stream->waitpid_fn = args.waitpid_fn;
272  stream->pid = pid;
273  return (stream);
274  }
275 }
276 
277 /* vstream_pclose - close stream to child process */
278 
280 {
281  pid_t saved_pid = stream->pid;
282  VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn;
283  pid_t pid;
284  WAIT_STATUS_T wait_status;
285 
286  /*
287  * Close the pipe. Don't trigger an alarm in vstream_fclose().
288  */
289  if (saved_pid == 0)
290  msg_panic("vstream_pclose: stream has no process");
291  stream->pid = 0;
292  vstream_fclose(stream);
293 
294  /*
295  * Reap the child exit status.
296  */
297  do {
298  if (saved_waitpid_fn != 0)
299  pid = saved_waitpid_fn(saved_pid, &wait_status, 0);
300  else
301  pid = waitpid(saved_pid, &wait_status, 0);
302  } while (pid == -1 && errno == EINTR);
303  return (pid == -1 ? -1 :
304  WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) :
305  WEXITSTATUS(wait_status));
306 }
307 
308 #ifdef TEST
309 
310 #include <fcntl.h>
311 #include <vstring.h>
312 #include <vstring_vstream.h>
313 
314  /*
315  * Test program. Run a command and copy lines one by one.
316  */
317 int main(int argc, char **argv)
318 {
319  VSTRING *buf = vstring_alloc(100);
320  VSTREAM *stream;
321  int status;
322 
323  /*
324  * Sanity check.
325  */
326  if (argc < 2)
327  msg_fatal("usage: %s 'command'", argv[0]);
328 
329  /*
330  * Open stream to child process.
331  */
332  if ((stream = vstream_popen(O_RDWR,
333  VSTREAM_POPEN_ARGV, argv + 1,
334  VSTREAM_POPEN_END)) == 0)
335  msg_fatal("vstream_popen: %m");
336 
337  /*
338  * Copy loop, one line at a time.
339  */
340  while (vstring_fgets(buf, stream) != 0) {
342  != VSTRING_LEN(buf))
343  msg_fatal("vstream_fwrite: %m");
344  if (vstream_fflush(VSTREAM_OUT) != 0)
345  msg_fatal("vstream_fflush: %m");
346  if (vstring_fgets(buf, VSTREAM_IN) == 0)
347  break;
348  if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf))
349  != VSTRING_LEN(buf))
350  msg_fatal("vstream_fwrite: %m");
351  }
352 
353  /*
354  * Cleanup.
355  */
356  vstring_free(buf);
357  if ((status = vstream_pclose(stream)) != 0)
358  msg_warn("exit status: %d", status);
359 
360  exit(status);
361 }
362 
363 #endif
#define vstring_fgets(s, p)
#define VSTREAM_POPEN_ARGV
Definition: vstream.h:193
int vstream_pclose(VSTREAM *stream)
#define VSTREAM_POPEN_ENV
Definition: vstream.h:196
Definition: argv.h:17
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
void(* MSG_CLEANUP_FN)(void)
Definition: msg.h:23
#define vstring_str(vp)
Definition: vstring.h:71
#define VSTREAM_OUT
Definition: vstream.h:67
int main(int argc, char **argv)
Definition: anvil.c:1010
char ** argv
Definition: argv.h:20
void argv_add(ARGV *argvp,...)
Definition: argv.c:197
#define VSTREAM_IN
Definition: vstream.h:66
#define VSTRING_LEN(vp)
Definition: vstring.h:72
pid_t pid
Definition: vstream.h:56
VSTREAM * vstream_popen(int flags,...)
#define VSTREAM_POPEN_COMMAND
Definition: vstream.h:192
void clean_env(char **preserve_list)
Definition: clean_env.c:59
#define VSTREAM_POPEN_EXPORT
Definition: vstream.h:199
int vstream_fclose(VSTREAM *stream)
Definition: vstream.c:1268
#define VSTREAM_POPEN_SHELL
Definition: vstream.h:197
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
#define CHARS_SPACE
Definition: sys_defs.h:1762
#define VSTREAM_POPEN_WAITPID_FN
Definition: vstream.h:198
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
int vstream_fflush(VSTREAM *stream)
Definition: vstream.c:1257
ARGV * argv_split(const char *, const char *)
Definition: argv_split.c:63
#define VSTREAM_POPEN_UID
Definition: vstream.h:194
#define vstream_fwrite(v, b, n)
Definition: vstream.h:105
VSTREAM_WAITPID_FN waitpid_fn
void set_ugid(uid_t uid, gid_t gid)
Definition: set_ugid.c:45
int duplex_pipe(int *fds)
Definition: duplex_pipe.c:41
struct VSTREAM_POPEN_ARGS VSTREAM_POPEN_ARGS
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
VSTREAM_WAITPID_FN waitpid_fn
Definition: vstream.h:57
NORETURN exec_command(const char *command)
Definition: exec_command.c:51
#define VSTREAM_POPEN_GID
Definition: vstream.h:195
pid_t(* VSTREAM_WAITPID_FN)(pid_t, WAIT_STATUS_T *, int)
Definition: vstream.h:35
int WAIT_STATUS_T
Definition: sys_defs.h:1436
MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn)
Definition: msg.c:317
#define DUP2
Definition: sys_defs.h:1307
VSTREAM * vstream_fdopen(int fd, int flags)
Definition: vstream.c:1204
#define VSTREAM_POPEN_END
Definition: vstream.h:191
void argv_terminate(ARGV *argvp)
Definition: argv.c:242