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 (key, value) arguments, terminated by VSTREAM_POPEN_END. The key
22/*	argument specifies what value will follow. The following is a listing
23/*	of key codes together with the expected value type.
24/* .RS
25/* .IP "VSTREAM_POPEN_COMMAND (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 VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
31/* .IP "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 VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
35/*	See also the VSTREAM_POPEN_SHELL attribute below.
36/* .IP "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 "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 "VSTREAM_POPEN_UID (uid_t)"
45/*	The user ID to execute the command as. The user ID must be non-zero.
46/* .IP "VSTREAM_POPEN_GID (gid_t)"
47/*	The group ID to execute the command as. The group ID must be non-zero.
48/* .IP "VSTREAM_POPEN_SHELL (char *)"
49/*	The shell to use when executing the command specified with
50/*	VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
51/*	command content.
52/* .IP "VSTREAM_POPEN_WAITPID_FN ((*)(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
111typedef struct VSTREAM_POPEN_ARGS {
112    char  **argv;
113    char   *command;
114    uid_t   uid;
115    gid_t   gid;
116    int     privileged;
117    char  **env;
118    char  **export;
119    char   *shell;
120    VSTREAM_WAITPID_FN waitpid_fn;
121} VSTREAM_POPEN_ARGS;
122
123/* vstream_parse_args - get arguments from variadic list */
124
125static 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;
153	case VSTREAM_POPEN_COMMAND:
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;
169	case VSTREAM_POPEN_EXPORT:
170	    args->export = va_arg(ap, char **);
171	    break;
172	case VSTREAM_POPEN_SHELL:
173	    args->shell = va_arg(ap, char *);
174	    break;
175	case VSTREAM_POPEN_WAITPID_FN:
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
193VSTREAM *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, " \t\r\n");
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
279int     vstream_pclose(VSTREAM *stream)
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  */
317int     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) {
341	if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf))
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
364