1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	vstream_popen 3
6/* SUMMARY
7/*	open stream to child process
8/* SYNOPSIS
9/*	#include <vstream.h>
10/*
11/*	VSTREAM	*vstream_popen(flags, key, value, ...)
12/*	int	flags;
13/*	int	key;
14/*
15/*	int	vstream_pclose(stream)
16/*	VSTREAM	*stream;
17/* DESCRIPTION
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.
26/* .RS
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
53/*	command content.
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.
57/* .RE
58/* .PP
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.
62/* DIAGNOSTICS
63/*	Panics: interface violations. Fatal errors: out of memory.
64/*
65/*	vstream_popen() returns a null pointer in case of trouble.
66/*	The nature of the problem is specified via the \fIerrno\fR
67/*	global variable.
68/*
69/*	vstream_pclose() returns -1 in case of trouble.
70/*	The nature of the problem is specified via the \fIerrno\fR
71/*	global variable.
72/* SEE ALSO
73/*	vstream(3) light-weight buffered I/O
74/* BUGS
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.
78/* LICENSE
79/* .ad
80/* .fi
81/*	The Secure Mailer license must be distributed with this software.
82/* AUTHOR(S)
83/*	Wietse Venema
84/*	IBM T.J. Watson Research
85/*	P.O. Box 704
86/*	Yorktown Heights, NY 10598, USA
87/*--*/
88
89/* System library. */
90
91#include <sys_defs.h>
92#include <sys/wait.h>
93#include <unistd.h>
94#include <stdlib.h>
95#include <errno.h>
96#ifdef USE_PATHS_H
97#include <paths.h>
98#endif
99#include <syslog.h>
100
101/* Utility library. */
102
103#include <msg.h>
104#include <exec_command.h>
105#include <vstream.h>
106#include <argv.h>
107#include <set_ugid.h>
108#include <clean_env.h>
109#include <iostuff.h>
110
111/* Application-specific. */
112
113typedef struct VSTREAM_POPEN_ARGS {
114    char  **argv;
115    char   *command;
116    uid_t   uid;
117    gid_t   gid;
118    int     privileged;
119    char  **env;
120    char  **export;
121    char   *shell;
122    VSTREAM_WAITPID_FN waitpid_fn;
123} VSTREAM_POPEN_ARGS;
124
125/* vstream_parse_args - get arguments from variadic list */
126
127static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap)
128{
129    const char *myname = "vstream_parse_args";
130    int     key;
131
132    /*
133     * First, set the default values (on all non-zero entries)
134     */
135    args->argv = 0;
136    args->command = 0;
137    args->uid = 0;
138    args->gid = 0;
139    args->privileged = 0;
140    args->env = 0;
141    args->export = 0;
142    args->shell = 0;
143    args->waitpid_fn = 0;
144
145    /*
146     * Then, override the defaults with user-supplied inputs.
147     */
148    while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) {
149	switch (key) {
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 **);
154	    break;
155	case VSTREAM_POPEN_COMMAND:
156	    if (args->argv != 0)
157		msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
158	    args->command = va_arg(ap, char *);
159	    break;
160	case VSTREAM_POPEN_UID:
161	    args->privileged = 1;
162	    args->uid = va_arg(ap, uid_t);
163	    break;
164	case VSTREAM_POPEN_GID:
165	    args->privileged = 1;
166	    args->gid = va_arg(ap, gid_t);
167	    break;
168	case VSTREAM_POPEN_ENV:
169	    args->env = va_arg(ap, char **);
170	    break;
171	case VSTREAM_POPEN_EXPORT:
172	    args->export = va_arg(ap, char **);
173	    break;
174	case VSTREAM_POPEN_SHELL:
175	    args->shell = va_arg(ap, char *);
176	    break;
177	case VSTREAM_POPEN_WAITPID_FN:
178	    args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
179	    break;
180	default:
181	    msg_panic("%s: unknown key: %d", myname, key);
182	}
183    }
184
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);
191}
192
193/* vstream_popen - open stream to child process */
194
195VSTREAM *vstream_popen(int flags,...)
196{
197    const char *myname = "vstream_popen";
198    VSTREAM_POPEN_ARGS args;
199    va_list ap;
200    VSTREAM *stream;
201    int     sockfd[2];
202    int     pid;
203    int     fd;
204    ARGV   *argv;
205    char  **cpp;
206
207    va_start(ap, flags);
208    vstream_parse_args(&args, ap);
209    va_end(ap);
210
211    if (args.command == 0)
212	args.command = args.argv[0];
213
214    if (duplex_pipe(sockfd) < 0)
215	return (0);
216
217    switch (pid = fork()) {
218    case -1:					/* error */
219	(void) close(sockfd[0]);
220	(void) close(sockfd[1]);
221	return (0);
222    case 0:					/* child */
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++)
227	    if (sockfd[0] != 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");
232
233	/*
234	 * Don't try to become someone else unless the user specified it.
235	 */
236	if (args.privileged)
237	    set_ugid(args.uid, args.gid);
238
239	/*
240	 * Environment plumbing. Always reset the command search path. XXX
241	 * That should probably be done by clean_env().
242	 */
243	if (args.export)
244	    clean_env(args.export);
245	if (setenv("PATH", _PATH_DEFPATH, 1))
246	    msg_fatal("%s: setenv: %m", myname);
247	if (args.env)
248	    for (cpp = args.env; *cpp; cpp += 2)
249		if (setenv(cpp[0], cpp[1], 1))
250		    msg_fatal("setenv: %m");
251
252	/*
253	 * Process plumbing. If possible, avoid running a shell.
254	 */
255	closelog();
256	if (args.argv) {
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]);
265	} else {
266	    exec_command(args.command);
267	}
268	/* NOTREACHED */
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;
274	stream->pid = pid;
275	return (stream);
276    }
277}
278
279/* vstream_pclose - close stream to child process */
280
281int     vstream_pclose(VSTREAM *stream)
282{
283    pid_t   saved_pid = stream->pid;
284    VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn;
285    pid_t   pid;
286    WAIT_STATUS_T wait_status;
287
288    /*
289     * Close the pipe. Don't trigger an alarm in vstream_fclose().
290     */
291    if (saved_pid == 0)
292	msg_panic("vstream_pclose: stream has no process");
293    stream->pid = 0;
294    vstream_fclose(stream);
295
296    /*
297     * Reap the child exit status.
298     */
299    do {
300	if (saved_waitpid_fn != 0)
301	    pid = saved_waitpid_fn(saved_pid, &wait_status, 0);
302	else
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));
308}
309
310#ifdef TEST
311
312#include <fcntl.h>
313#include <vstring.h>
314#include <vstring_vstream.h>
315
316 /*
317  * Test program. Run a command and copy lines one by one.
318  */
319int     main(int argc, char **argv)
320{
321    VSTRING *buf = vstring_alloc(100);
322    VSTREAM *stream;
323    int     status;
324
325    /*
326     * Sanity check.
327     */
328    if (argc < 2)
329	msg_fatal("usage: %s 'command'", argv[0]);
330
331    /*
332     * Open stream to child process.
333     */
334    if ((stream = vstream_popen(O_RDWR,
335				VSTREAM_POPEN_ARGV, argv + 1,
336				VSTREAM_POPEN_END)) == 0)
337	msg_fatal("vstream_popen: %m");
338
339    /*
340     * Copy loop, one line at a time.
341     */
342    while (vstring_fgets(buf, stream) != 0) {
343	if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf))
344	    != 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)
349	    break;
350	if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf))
351	    != VSTRING_LEN(buf))
352	    msg_fatal("vstream_fwrite: %m");
353    }
354
355    /*
356     * Cleanup.
357     */
358    vstring_free(buf);
359    if ((status = vstream_pclose(stream)) != 0)
360	msg_warn("exit status: %d", status);
361
362    exit(status);
363}
364
365#endif
366