7 /* open stream to child process
9 /* #include <vstream.h>
11 /* VSTREAM *vstream_popen(flags, key, value, ...)
15 /* int vstream_pclose(stream)
18 /* vstream_popen() opens a one-way or two-way stream to a user-specified
19 /* command, which is executed by a child process. The \fIflags\fR
20 /* argument is as with vstream_fopen(). The child's standard input and
21 /* standard output are redirected to the stream, which is based on a
22 /* socketpair or other suitable local IPC. vstream_popen() takes a list
23 /* of (key, value) arguments, terminated by VSTREAM_POPEN_END. The key
24 /* argument specifies what value will follow. The following is a listing
25 /* of key codes together with the expected value type.
27 /* .IP "VSTREAM_POPEN_COMMAND (char *)"
28 /* Specifies the command to execute as a string. The string is
29 /* passed to the shell when it contains shell meta characters
30 /* or when it appears to be a shell built-in command, otherwise
31 /* the command is executed without invoking a shell.
32 /* One of VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
33 /* .IP "VSTREAM_POPEN_ARGV (char **)"
34 /* The command is specified as an argument vector. This vector is
35 /* passed without further inspection to the \fIexecvp\fR() routine.
36 /* One of VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
37 /* See also the VSTREAM_POPEN_SHELL attribute below.
38 /* .IP "VSTREAM_POPEN_ENV (char **)"
39 /* Additional environment information, in the form of a null-terminated
40 /* list of name, value, name, value, ... elements. By default only the
41 /* command search path is initialized to _PATH_DEFPATH.
42 /* .IP "VSTREAM_POPEN_EXPORT (char **)"
43 /* This argument is passed to clean_env().
44 /* Null-terminated array of names of environment parameters
45 /* that can be exported. By default, everything is exported.
46 /* .IP "VSTREAM_POPEN_UID (uid_t)"
47 /* The user ID to execute the command as. The user ID must be non-zero.
48 /* .IP "VSTREAM_POPEN_GID (gid_t)"
49 /* The group ID to execute the command as. The group ID must be non-zero.
50 /* .IP "VSTREAM_POPEN_SHELL (char *)"
51 /* The shell to use when executing the command specified with
52 /* VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
54 /* .IP "VSTREAM_POPEN_WAITPID_FN ((*)(pid_t, WAIT_STATUS_T *, int))"
55 /* waitpid()-like function to reap the child exit status when
56 /* vstream_pclose() is called.
59 /* vstream_pclose() closes the named stream and returns the child
60 /* exit status. It is an error to specify a stream that was not
61 /* returned by vstream_popen() or that is no longer open.
63 /* Panics: interface violations. Fatal errors: out of memory.
65 /* vstream_popen() returns a null pointer in case of trouble.
66 /* The nature of the problem is specified via the \fIerrno\fR
69 /* vstream_pclose() returns -1 in case of trouble.
70 /* The nature of the problem is specified via the \fIerrno\fR
73 /* vstream(3) light-weight buffered I/O
75 /* The interface, stolen from popen()/pclose(), ignores errors
76 /* returned when the stream is closed, and does not distinguish
77 /* between exit status codes and kill signals.
81 /* The Secure Mailer license must be distributed with this software.
84 /* IBM T.J. Watson Research
86 /* Yorktown Heights, NY 10598, USA
101 /* Utility library. */
104 #include <exec_command.h>
107 #include <set_ugid.h>
108 #include <clean_env.h>
111 /* Application-specific. */
113 typedef struct VSTREAM_POPEN_ARGS
{
122 VSTREAM_WAITPID_FN waitpid_fn
;
123 } VSTREAM_POPEN_ARGS
;
125 /* vstream_parse_args - get arguments from variadic list */
127 static void vstream_parse_args(VSTREAM_POPEN_ARGS
*args
, va_list ap
)
129 const char *myname
= "vstream_parse_args";
133 * First, set the default values (on all non-zero entries)
139 args
->privileged
= 0;
143 args
->waitpid_fn
= 0;
146 * Then, override the defaults with user-supplied inputs.
148 while ((key
= va_arg(ap
, int)) != VSTREAM_POPEN_END
) {
150 case VSTREAM_POPEN_ARGV
:
151 if (args
->command
!= 0)
152 msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname
);
153 args
->argv
= va_arg(ap
, char **);
155 case VSTREAM_POPEN_COMMAND
:
157 msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname
);
158 args
->command
= va_arg(ap
, char *);
160 case VSTREAM_POPEN_UID
:
161 args
->privileged
= 1;
162 args
->uid
= va_arg(ap
, uid_t
);
164 case VSTREAM_POPEN_GID
:
165 args
->privileged
= 1;
166 args
->gid
= va_arg(ap
, gid_t
);
168 case VSTREAM_POPEN_ENV
:
169 args
->env
= va_arg(ap
, char **);
171 case VSTREAM_POPEN_EXPORT
:
172 args
->export
= va_arg(ap
, char **);
174 case VSTREAM_POPEN_SHELL
:
175 args
->shell
= va_arg(ap
, char *);
177 case VSTREAM_POPEN_WAITPID_FN
:
178 args
->waitpid_fn
= va_arg(ap
, VSTREAM_WAITPID_FN
);
181 msg_panic("%s: unknown key: %d", myname
, key
);
185 if (args
->command
== 0 && args
->argv
== 0)
186 msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname
);
187 if (args
->privileged
!= 0 && args
->uid
== 0)
188 msg_panic("%s: privileged uid", myname
);
189 if (args
->privileged
!= 0 && args
->gid
== 0)
190 msg_panic("%s: privileged gid", myname
);
193 /* vstream_popen - open stream to child process */
195 VSTREAM
*vstream_popen(int flags
,...)
197 const char *myname
= "vstream_popen";
198 VSTREAM_POPEN_ARGS args
;
208 vstream_parse_args(&args
, ap
);
211 if (args
.command
== 0)
212 args
.command
= args
.argv
[0];
214 if (duplex_pipe(sockfd
) < 0)
217 switch (pid
= fork()) {
219 (void) close(sockfd
[0]);
220 (void) close(sockfd
[1]);
223 (void) msg_cleanup((MSG_CLEANUP_FN
) 0);
224 if (close(sockfd
[1]))
225 msg_warn("close: %m");
226 for (fd
= 0; fd
< 2; fd
++)
228 if (DUP2(sockfd
[0], fd
) < 0)
229 msg_fatal("dup2: %m");
230 if (sockfd
[0] >= 2 && close(sockfd
[0]))
231 msg_warn("close: %m");
234 * Don't try to become someone else unless the user specified it.
237 set_ugid(args
.uid
, args
.gid
);
240 * Environment plumbing. Always reset the command search path. XXX
241 * That should probably be done by clean_env().
244 clean_env(args
.export
);
245 if (setenv("PATH", _PATH_DEFPATH
, 1))
246 msg_fatal("%s: setenv: %m", myname
);
248 for (cpp
= args
.env
; *cpp
; cpp
+= 2)
249 if (setenv(cpp
[0], cpp
[1], 1))
250 msg_fatal("setenv: %m");
253 * Process plumbing. If possible, avoid running a shell.
257 execvp(args
.argv
[0], args
.argv
);
258 msg_fatal("%s: execvp %s: %m", myname
, args
.argv
[0]);
259 } else if (args
.shell
&& *args
.shell
) {
260 argv
= argv_split(args
.shell
, " \t\r\n");
261 argv_add(argv
, args
.command
, (char *) 0);
262 argv_terminate(argv
);
263 execvp(argv
->argv
[0], argv
->argv
);
264 msg_fatal("%s: execvp %s: %m", myname
, argv
->argv
[0]);
266 exec_command(args
.command
);
269 default: /* parent */
270 if (close(sockfd
[0]))
271 msg_warn("close: %m");
272 stream
= vstream_fdopen(sockfd
[1], flags
);
273 stream
->waitpid_fn
= args
.waitpid_fn
;
279 /* vstream_pclose - close stream to child process */
281 int vstream_pclose(VSTREAM
*stream
)
283 pid_t saved_pid
= stream
->pid
;
284 VSTREAM_WAITPID_FN saved_waitpid_fn
= stream
->waitpid_fn
;
286 WAIT_STATUS_T wait_status
;
289 * Close the pipe. Don't trigger an alarm in vstream_fclose().
292 msg_panic("vstream_pclose: stream has no process");
294 vstream_fclose(stream
);
297 * Reap the child exit status.
300 if (saved_waitpid_fn
!= 0)
301 pid
= saved_waitpid_fn(saved_pid
, &wait_status
, 0);
303 pid
= waitpid(saved_pid
, &wait_status
, 0);
304 } while (pid
== -1 && errno
== EINTR
);
305 return (pid
== -1 ? -1 :
306 WIFSIGNALED(wait_status
) ? WTERMSIG(wait_status
) :
307 WEXITSTATUS(wait_status
));
314 #include <vstring_vstream.h>
317 * Test program. Run a command and copy lines one by one.
319 int main(int argc
, char **argv
)
321 VSTRING
*buf
= vstring_alloc(100);
329 msg_fatal("usage: %s 'command'", argv
[0]);
332 * Open stream to child process.
334 if ((stream
= vstream_popen(O_RDWR
,
335 VSTREAM_POPEN_ARGV
, argv
+ 1,
336 VSTREAM_POPEN_END
)) == 0)
337 msg_fatal("vstream_popen: %m");
340 * Copy loop, one line at a time.
342 while (vstring_fgets(buf
, stream
) != 0) {
343 if (vstream_fwrite(VSTREAM_OUT
, vstring_str(buf
), VSTRING_LEN(buf
))
345 msg_fatal("vstream_fwrite: %m");
346 if (vstream_fflush(VSTREAM_OUT
) != 0)
347 msg_fatal("vstream_fflush: %m");
348 if (vstring_fgets(buf
, VSTREAM_IN
) == 0)
350 if (vstream_fwrite(stream
, vstring_str(buf
), VSTRING_LEN(buf
))
352 msg_fatal("vstream_fwrite: %m");
359 if ((status
= vstream_pclose(stream
)) != 0)
360 msg_warn("exit status: %d", status
);