1 /*	$NetBSD: vstream_popen.c,v 1.1.1.1 2009/06/23 10:09:01 tron Exp $	*/
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 
113 typedef 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 
127 static 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 
195 VSTREAM *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 
281 int     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   */
319 int     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