xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/vstream_popen.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1*e89934bbSchristos /*	$NetBSD: vstream_popen.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
241fbaed0Stron 
341fbaed0Stron /*++
441fbaed0Stron /* NAME
541fbaed0Stron /*	vstream_popen 3
641fbaed0Stron /* SUMMARY
741fbaed0Stron /*	open stream to child process
841fbaed0Stron /* SYNOPSIS
941fbaed0Stron /*	#include <vstream.h>
1041fbaed0Stron /*
1141fbaed0Stron /*	VSTREAM	*vstream_popen(flags, key, value, ...)
1241fbaed0Stron /*	int	flags;
1341fbaed0Stron /*	int	key;
1441fbaed0Stron /*
1541fbaed0Stron /*	int	vstream_pclose(stream)
1641fbaed0Stron /*	VSTREAM	*stream;
1741fbaed0Stron /* DESCRIPTION
1841fbaed0Stron /*	vstream_popen() opens a one-way or two-way stream to a user-specified
1941fbaed0Stron /*	command, which is executed by a child process. The \fIflags\fR
2041fbaed0Stron /*	argument is as with vstream_fopen(). The child's standard input and
2141fbaed0Stron /*	standard output are redirected to the stream, which is based on a
2241fbaed0Stron /*	socketpair or other suitable local IPC. vstream_popen() takes a list
23e262b48eSchristos /*	of macros with zero or more arguments, terminated by
24e262b48eSchristos /*	CA_VSTREAM_POPEN_END.  The following is a listing of macros
25e262b48eSchristos /*	with the expected argument type.
2641fbaed0Stron /* .RS
27e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)"
2841fbaed0Stron /*	Specifies the command to execute as a string. The string is
2941fbaed0Stron /*	passed to the shell when it contains shell meta characters
3041fbaed0Stron /*	or when it appears to be a shell built-in command, otherwise
3141fbaed0Stron /*	the command is executed without invoking a shell.
32e262b48eSchristos /*	One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
33e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_ARGV(char **)"
3441fbaed0Stron /*	The command is specified as an argument vector. This vector is
3541fbaed0Stron /*	passed without further inspection to the \fIexecvp\fR() routine.
36e262b48eSchristos /*	One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
37e262b48eSchristos /*	See also the CA_VSTREAM_POPEN_SHELL attribute below.
38e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_ENV(char **)"
3941fbaed0Stron /*	Additional environment information, in the form of a null-terminated
4041fbaed0Stron /*	list of name, value, name, value, ... elements. By default only the
4141fbaed0Stron /*	command search path is initialized to _PATH_DEFPATH.
42e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_EXPORT(char **)"
4341fbaed0Stron /*	This argument is passed to clean_env().
4441fbaed0Stron /*	Null-terminated array of names of environment parameters
4541fbaed0Stron /*	that can be exported. By default, everything is exported.
46e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_UID(uid_t)"
4741fbaed0Stron /*	The user ID to execute the command as. The user ID must be non-zero.
48e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_GID(gid_t)"
4941fbaed0Stron /*	The group ID to execute the command as. The group ID must be non-zero.
50e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_SHELL(const char *)"
5141fbaed0Stron /*	The shell to use when executing the command specified with
52e262b48eSchristos /*	CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
5341fbaed0Stron /*	command content.
54e262b48eSchristos /* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))"
5541fbaed0Stron /*	waitpid()-like function to reap the child exit status when
5641fbaed0Stron /*	vstream_pclose() is called.
5741fbaed0Stron /* .RE
5841fbaed0Stron /* .PP
5941fbaed0Stron /*	vstream_pclose() closes the named stream and returns the child
6041fbaed0Stron /*	exit status. It is an error to specify a stream that was not
6141fbaed0Stron /*	returned by vstream_popen() or that is no longer open.
6241fbaed0Stron /* DIAGNOSTICS
6341fbaed0Stron /*	Panics: interface violations. Fatal errors: out of memory.
6441fbaed0Stron /*
6541fbaed0Stron /*	vstream_popen() returns a null pointer in case of trouble.
6641fbaed0Stron /*	The nature of the problem is specified via the \fIerrno\fR
6741fbaed0Stron /*	global variable.
6841fbaed0Stron /*
6941fbaed0Stron /*	vstream_pclose() returns -1 in case of trouble.
7041fbaed0Stron /*	The nature of the problem is specified via the \fIerrno\fR
7141fbaed0Stron /*	global variable.
7241fbaed0Stron /* SEE ALSO
7341fbaed0Stron /*	vstream(3) light-weight buffered I/O
7441fbaed0Stron /* BUGS
7541fbaed0Stron /*	The interface, stolen from popen()/pclose(), ignores errors
7641fbaed0Stron /*	returned when the stream is closed, and does not distinguish
7741fbaed0Stron /*	between exit status codes and kill signals.
7841fbaed0Stron /* LICENSE
7941fbaed0Stron /* .ad
8041fbaed0Stron /* .fi
8141fbaed0Stron /*	The Secure Mailer license must be distributed with this software.
8241fbaed0Stron /* AUTHOR(S)
8341fbaed0Stron /*	Wietse Venema
8441fbaed0Stron /*	IBM T.J. Watson Research
8541fbaed0Stron /*	P.O. Box 704
8641fbaed0Stron /*	Yorktown Heights, NY 10598, USA
8741fbaed0Stron /*--*/
8841fbaed0Stron 
8941fbaed0Stron /* System library. */
9041fbaed0Stron 
9141fbaed0Stron #include <sys_defs.h>
9241fbaed0Stron #include <sys/wait.h>
9341fbaed0Stron #include <unistd.h>
9441fbaed0Stron #include <stdlib.h>
9541fbaed0Stron #include <errno.h>
9641fbaed0Stron #ifdef USE_PATHS_H
9741fbaed0Stron #include <paths.h>
9841fbaed0Stron #endif
9941fbaed0Stron #include <syslog.h>
10041fbaed0Stron 
10141fbaed0Stron /* Utility library. */
10241fbaed0Stron 
10341fbaed0Stron #include <msg.h>
10441fbaed0Stron #include <exec_command.h>
10541fbaed0Stron #include <vstream.h>
10641fbaed0Stron #include <argv.h>
10741fbaed0Stron #include <set_ugid.h>
10841fbaed0Stron #include <clean_env.h>
10941fbaed0Stron #include <iostuff.h>
11041fbaed0Stron 
11141fbaed0Stron /* Application-specific. */
11241fbaed0Stron 
11341fbaed0Stron typedef struct VSTREAM_POPEN_ARGS {
11441fbaed0Stron     char  **argv;
11541fbaed0Stron     char   *command;
11641fbaed0Stron     uid_t   uid;
11741fbaed0Stron     gid_t   gid;
11841fbaed0Stron     int     privileged;
11941fbaed0Stron     char  **env;
12041fbaed0Stron     char  **export;
12141fbaed0Stron     char   *shell;
12241fbaed0Stron     VSTREAM_WAITPID_FN waitpid_fn;
12341fbaed0Stron } VSTREAM_POPEN_ARGS;
12441fbaed0Stron 
12541fbaed0Stron /* vstream_parse_args - get arguments from variadic list */
12641fbaed0Stron 
vstream_parse_args(VSTREAM_POPEN_ARGS * args,va_list ap)12741fbaed0Stron static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap)
12841fbaed0Stron {
12941fbaed0Stron     const char *myname = "vstream_parse_args";
13041fbaed0Stron     int     key;
13141fbaed0Stron 
13241fbaed0Stron     /*
13341fbaed0Stron      * First, set the default values (on all non-zero entries)
13441fbaed0Stron      */
13541fbaed0Stron     args->argv = 0;
13641fbaed0Stron     args->command = 0;
13741fbaed0Stron     args->uid = 0;
13841fbaed0Stron     args->gid = 0;
13941fbaed0Stron     args->privileged = 0;
14041fbaed0Stron     args->env = 0;
14141fbaed0Stron     args->export = 0;
14241fbaed0Stron     args->shell = 0;
14341fbaed0Stron     args->waitpid_fn = 0;
14441fbaed0Stron 
14541fbaed0Stron     /*
14641fbaed0Stron      * Then, override the defaults with user-supplied inputs.
14741fbaed0Stron      */
14841fbaed0Stron     while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) {
14941fbaed0Stron 	switch (key) {
15041fbaed0Stron 	case VSTREAM_POPEN_ARGV:
15141fbaed0Stron 	    if (args->command != 0)
15241fbaed0Stron 		msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
15341fbaed0Stron 	    args->argv = va_arg(ap, char **);
15441fbaed0Stron 	    break;
15541fbaed0Stron 	case VSTREAM_POPEN_COMMAND:
15641fbaed0Stron 	    if (args->argv != 0)
15741fbaed0Stron 		msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
15841fbaed0Stron 	    args->command = va_arg(ap, char *);
15941fbaed0Stron 	    break;
16041fbaed0Stron 	case VSTREAM_POPEN_UID:
16141fbaed0Stron 	    args->privileged = 1;
16241fbaed0Stron 	    args->uid = va_arg(ap, uid_t);
16341fbaed0Stron 	    break;
16441fbaed0Stron 	case VSTREAM_POPEN_GID:
16541fbaed0Stron 	    args->privileged = 1;
16641fbaed0Stron 	    args->gid = va_arg(ap, gid_t);
16741fbaed0Stron 	    break;
16841fbaed0Stron 	case VSTREAM_POPEN_ENV:
16941fbaed0Stron 	    args->env = va_arg(ap, char **);
17041fbaed0Stron 	    break;
17141fbaed0Stron 	case VSTREAM_POPEN_EXPORT:
17241fbaed0Stron 	    args->export = va_arg(ap, char **);
17341fbaed0Stron 	    break;
17441fbaed0Stron 	case VSTREAM_POPEN_SHELL:
17541fbaed0Stron 	    args->shell = va_arg(ap, char *);
17641fbaed0Stron 	    break;
17741fbaed0Stron 	case VSTREAM_POPEN_WAITPID_FN:
17841fbaed0Stron 	    args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
17941fbaed0Stron 	    break;
18041fbaed0Stron 	default:
18141fbaed0Stron 	    msg_panic("%s: unknown key: %d", myname, key);
18241fbaed0Stron 	}
18341fbaed0Stron     }
18441fbaed0Stron 
18541fbaed0Stron     if (args->command == 0 && args->argv == 0)
18641fbaed0Stron 	msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname);
18741fbaed0Stron     if (args->privileged != 0 && args->uid == 0)
18841fbaed0Stron 	msg_panic("%s: privileged uid", myname);
18941fbaed0Stron     if (args->privileged != 0 && args->gid == 0)
19041fbaed0Stron 	msg_panic("%s: privileged gid", myname);
19141fbaed0Stron }
19241fbaed0Stron 
19341fbaed0Stron /* vstream_popen - open stream to child process */
19441fbaed0Stron 
vstream_popen(int flags,...)19541fbaed0Stron VSTREAM *vstream_popen(int flags,...)
19641fbaed0Stron {
19741fbaed0Stron     const char *myname = "vstream_popen";
19841fbaed0Stron     VSTREAM_POPEN_ARGS args;
19941fbaed0Stron     va_list ap;
20041fbaed0Stron     VSTREAM *stream;
20141fbaed0Stron     int     sockfd[2];
20241fbaed0Stron     int     pid;
20341fbaed0Stron     int     fd;
20441fbaed0Stron     ARGV   *argv;
20541fbaed0Stron     char  **cpp;
20641fbaed0Stron 
20741fbaed0Stron     va_start(ap, flags);
20841fbaed0Stron     vstream_parse_args(&args, ap);
20941fbaed0Stron     va_end(ap);
21041fbaed0Stron 
21141fbaed0Stron     if (args.command == 0)
21241fbaed0Stron 	args.command = args.argv[0];
21341fbaed0Stron 
21441fbaed0Stron     if (duplex_pipe(sockfd) < 0)
21541fbaed0Stron 	return (0);
21641fbaed0Stron 
21741fbaed0Stron     switch (pid = fork()) {
21841fbaed0Stron     case -1:					/* error */
21941fbaed0Stron 	(void) close(sockfd[0]);
22041fbaed0Stron 	(void) close(sockfd[1]);
22141fbaed0Stron 	return (0);
22241fbaed0Stron     case 0:					/* child */
22341fbaed0Stron 	(void) msg_cleanup((MSG_CLEANUP_FN) 0);
22441fbaed0Stron 	if (close(sockfd[1]))
22541fbaed0Stron 	    msg_warn("close: %m");
22641fbaed0Stron 	for (fd = 0; fd < 2; fd++)
22741fbaed0Stron 	    if (sockfd[0] != fd)
22841fbaed0Stron 		if (DUP2(sockfd[0], fd) < 0)
22941fbaed0Stron 		    msg_fatal("dup2: %m");
23041fbaed0Stron 	if (sockfd[0] >= 2 && close(sockfd[0]))
23141fbaed0Stron 	    msg_warn("close: %m");
23241fbaed0Stron 
23341fbaed0Stron 	/*
23441fbaed0Stron 	 * Don't try to become someone else unless the user specified it.
23541fbaed0Stron 	 */
23641fbaed0Stron 	if (args.privileged)
23741fbaed0Stron 	    set_ugid(args.uid, args.gid);
23841fbaed0Stron 
23941fbaed0Stron 	/*
24041fbaed0Stron 	 * Environment plumbing. Always reset the command search path. XXX
24141fbaed0Stron 	 * That should probably be done by clean_env().
24241fbaed0Stron 	 */
24341fbaed0Stron 	if (args.export)
24441fbaed0Stron 	    clean_env(args.export);
24541fbaed0Stron 	if (setenv("PATH", _PATH_DEFPATH, 1))
24641fbaed0Stron 	    msg_fatal("%s: setenv: %m", myname);
24741fbaed0Stron 	if (args.env)
24841fbaed0Stron 	    for (cpp = args.env; *cpp; cpp += 2)
24941fbaed0Stron 		if (setenv(cpp[0], cpp[1], 1))
25041fbaed0Stron 		    msg_fatal("setenv: %m");
25141fbaed0Stron 
25241fbaed0Stron 	/*
25341fbaed0Stron 	 * Process plumbing. If possible, avoid running a shell.
25441fbaed0Stron 	 */
25541fbaed0Stron 	closelog();
25641fbaed0Stron 	if (args.argv) {
25741fbaed0Stron 	    execvp(args.argv[0], args.argv);
25841fbaed0Stron 	    msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
25941fbaed0Stron 	} else if (args.shell && *args.shell) {
260e262b48eSchristos 	    argv = argv_split(args.shell, CHARS_SPACE);
26141fbaed0Stron 	    argv_add(argv, args.command, (char *) 0);
26241fbaed0Stron 	    argv_terminate(argv);
26341fbaed0Stron 	    execvp(argv->argv[0], argv->argv);
26441fbaed0Stron 	    msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
26541fbaed0Stron 	} else {
26641fbaed0Stron 	    exec_command(args.command);
26741fbaed0Stron 	}
26841fbaed0Stron 	/* NOTREACHED */
26941fbaed0Stron     default:					/* parent */
27041fbaed0Stron 	if (close(sockfd[0]))
27141fbaed0Stron 	    msg_warn("close: %m");
27241fbaed0Stron 	stream = vstream_fdopen(sockfd[1], flags);
27341fbaed0Stron 	stream->waitpid_fn = args.waitpid_fn;
27441fbaed0Stron 	stream->pid = pid;
27541fbaed0Stron 	return (stream);
27641fbaed0Stron     }
27741fbaed0Stron }
27841fbaed0Stron 
27941fbaed0Stron /* vstream_pclose - close stream to child process */
28041fbaed0Stron 
vstream_pclose(VSTREAM * stream)28141fbaed0Stron int     vstream_pclose(VSTREAM *stream)
28241fbaed0Stron {
28341fbaed0Stron     pid_t   saved_pid = stream->pid;
28441fbaed0Stron     VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn;
28541fbaed0Stron     pid_t   pid;
28641fbaed0Stron     WAIT_STATUS_T wait_status;
28741fbaed0Stron 
28841fbaed0Stron     /*
28941fbaed0Stron      * Close the pipe. Don't trigger an alarm in vstream_fclose().
29041fbaed0Stron      */
29141fbaed0Stron     if (saved_pid == 0)
29241fbaed0Stron 	msg_panic("vstream_pclose: stream has no process");
29341fbaed0Stron     stream->pid = 0;
29441fbaed0Stron     vstream_fclose(stream);
29541fbaed0Stron 
29641fbaed0Stron     /*
29741fbaed0Stron      * Reap the child exit status.
29841fbaed0Stron      */
29941fbaed0Stron     do {
30041fbaed0Stron 	if (saved_waitpid_fn != 0)
30141fbaed0Stron 	    pid = saved_waitpid_fn(saved_pid, &wait_status, 0);
30241fbaed0Stron 	else
30341fbaed0Stron 	    pid = waitpid(saved_pid, &wait_status, 0);
30441fbaed0Stron     } while (pid == -1 && errno == EINTR);
30541fbaed0Stron     return (pid == -1 ? -1 :
30641fbaed0Stron 	    WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) :
30741fbaed0Stron 	    WEXITSTATUS(wait_status));
30841fbaed0Stron }
30941fbaed0Stron 
31041fbaed0Stron #ifdef TEST
31141fbaed0Stron 
31241fbaed0Stron #include <fcntl.h>
31341fbaed0Stron #include <vstring.h>
31441fbaed0Stron #include <vstring_vstream.h>
31541fbaed0Stron 
31641fbaed0Stron  /*
31741fbaed0Stron   * Test program. Run a command and copy lines one by one.
31841fbaed0Stron   */
main(int argc,char ** argv)31941fbaed0Stron int     main(int argc, char **argv)
32041fbaed0Stron {
32141fbaed0Stron     VSTRING *buf = vstring_alloc(100);
32241fbaed0Stron     VSTREAM *stream;
32341fbaed0Stron     int     status;
32441fbaed0Stron 
32541fbaed0Stron     /*
32641fbaed0Stron      * Sanity check.
32741fbaed0Stron      */
32841fbaed0Stron     if (argc < 2)
32941fbaed0Stron 	msg_fatal("usage: %s 'command'", argv[0]);
33041fbaed0Stron 
33141fbaed0Stron     /*
33241fbaed0Stron      * Open stream to child process.
33341fbaed0Stron      */
33441fbaed0Stron     if ((stream = vstream_popen(O_RDWR,
33541fbaed0Stron 				VSTREAM_POPEN_ARGV, argv + 1,
33641fbaed0Stron 				VSTREAM_POPEN_END)) == 0)
33741fbaed0Stron 	msg_fatal("vstream_popen: %m");
33841fbaed0Stron 
33941fbaed0Stron     /*
34041fbaed0Stron      * Copy loop, one line at a time.
34141fbaed0Stron      */
34241fbaed0Stron     while (vstring_fgets(buf, stream) != 0) {
34341fbaed0Stron 	if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf))
34441fbaed0Stron 	    != VSTRING_LEN(buf))
34541fbaed0Stron 	    msg_fatal("vstream_fwrite: %m");
34641fbaed0Stron 	if (vstream_fflush(VSTREAM_OUT) != 0)
34741fbaed0Stron 	    msg_fatal("vstream_fflush: %m");
34841fbaed0Stron 	if (vstring_fgets(buf, VSTREAM_IN) == 0)
34941fbaed0Stron 	    break;
35041fbaed0Stron 	if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf))
35141fbaed0Stron 	    != VSTRING_LEN(buf))
35241fbaed0Stron 	    msg_fatal("vstream_fwrite: %m");
35341fbaed0Stron     }
35441fbaed0Stron 
35541fbaed0Stron     /*
35641fbaed0Stron      * Cleanup.
35741fbaed0Stron      */
35841fbaed0Stron     vstring_free(buf);
35941fbaed0Stron     if ((status = vstream_pclose(stream)) != 0)
36041fbaed0Stron 	msg_warn("exit status: %d", status);
36141fbaed0Stron 
36241fbaed0Stron     exit(status);
36341fbaed0Stron }
36441fbaed0Stron 
36541fbaed0Stron #endif
366