Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: vstream_popen.c,v 1.2 2017/02/14 01:16:49 christos 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 macros with zero or more arguments, terminated by
     24 /*	CA_VSTREAM_POPEN_END.  The following is a listing of macros
     25 /*	with the expected argument type.
     26 /* .RS
     27 /* .IP "CA_VSTREAM_POPEN_COMMAND(const 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 CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
     33 /* .IP "CA_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 CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
     37 /*	See also the CA_VSTREAM_POPEN_SHELL attribute below.
     38 /* .IP "CA_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 "CA_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 "CA_VSTREAM_POPEN_UID(uid_t)"
     47 /*	The user ID to execute the command as. The user ID must be non-zero.
     48 /* .IP "CA_VSTREAM_POPEN_GID(gid_t)"
     49 /*	The group ID to execute the command as. The group ID must be non-zero.
     50 /* .IP "CA_VSTREAM_POPEN_SHELL(const char *)"
     51 /*	The shell to use when executing the command specified with
     52 /*	CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
     53 /*	command content.
     54 /* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(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, CHARS_SPACE);
    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