diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/vstream_popen.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/util/vstream_popen.c b/src/util/vstream_popen.c new file mode 100644 index 0000000..d00d49e --- /dev/null +++ b/src/util/vstream_popen.c @@ -0,0 +1,363 @@ +/*++ +/* NAME +/* vstream_popen 3 +/* SUMMARY +/* open stream to child process +/* SYNOPSIS +/* #include <vstream.h> +/* +/* VSTREAM *vstream_popen(flags, key, value, ...) +/* int flags; +/* int key; +/* +/* int vstream_pclose(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* vstream_popen() opens a one-way or two-way stream to a user-specified +/* command, which is executed by a child process. The \fIflags\fR +/* argument is as with vstream_fopen(). The child's standard input and +/* standard output are redirected to the stream, which is based on a +/* socketpair or other suitable local IPC. vstream_popen() takes a list +/* of macros with zero or more arguments, terminated by +/* CA_VSTREAM_POPEN_END. The following is a listing of macros +/* with the expected argument type. +/* .RS +/* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)" +/* Specifies the command to execute as a string. The string is +/* passed to the shell when it contains shell meta characters +/* or when it appears to be a shell built-in command, otherwise +/* the command is executed without invoking a shell. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* .IP "CA_VSTREAM_POPEN_ARGV(char **)" +/* The command is specified as an argument vector. This vector is +/* passed without further inspection to the \fIexecvp\fR() routine. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* See also the CA_VSTREAM_POPEN_SHELL attribute below. +/* .IP "CA_VSTREAM_POPEN_ENV(char **)" +/* Additional environment information, in the form of a null-terminated +/* list of name, value, name, value, ... elements. By default only the +/* command search path is initialized to _PATH_DEFPATH. +/* .IP "CA_VSTREAM_POPEN_EXPORT(char **)" +/* This argument is passed to clean_env(). +/* Null-terminated array of names of environment parameters +/* that can be exported. By default, everything is exported. +/* .IP "CA_VSTREAM_POPEN_UID(uid_t)" +/* The user ID to execute the command as. The user ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_GID(gid_t)" +/* The group ID to execute the command as. The group ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_SHELL(const char *)" +/* The shell to use when executing the command specified with +/* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the +/* command content. +/* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))" +/* waitpid()-like function to reap the child exit status when +/* vstream_pclose() is called. +/* .RE +/* .PP +/* vstream_pclose() closes the named stream and returns the child +/* exit status. It is an error to specify a stream that was not +/* returned by vstream_popen() or that is no longer open. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory. +/* +/* vstream_popen() returns a null pointer in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* +/* vstream_pclose() returns -1 in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* SEE ALSO +/* vstream(3) light-weight buffered I/O +/* BUGS +/* The interface, stolen from popen()/pclose(), ignores errors +/* returned when the stream is closed, and does not distinguish +/* between exit status codes and kill signals. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#ifdef USE_PATHS_H +#include <paths.h> +#endif +#include <syslog.h> + +/* Utility library. */ + +#include <msg.h> +#include <exec_command.h> +#include <vstream.h> +#include <argv.h> +#include <set_ugid.h> +#include <clean_env.h> +#include <iostuff.h> + +/* Application-specific. */ + +typedef struct VSTREAM_POPEN_ARGS { + char **argv; + char *command; + uid_t uid; + gid_t gid; + int privileged; + char **env; + char **export; + char *shell; + VSTREAM_WAITPID_FN waitpid_fn; +} VSTREAM_POPEN_ARGS; + +/* vstream_parse_args - get arguments from variadic list */ + +static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap) +{ + const char *myname = "vstream_parse_args"; + int key; + + /* + * First, set the default values (on all non-zero entries) + */ + args->argv = 0; + args->command = 0; + args->uid = 0; + args->gid = 0; + args->privileged = 0; + args->env = 0; + args->export = 0; + args->shell = 0; + args->waitpid_fn = 0; + + /* + * Then, override the defaults with user-supplied inputs. + */ + while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) { + switch (key) { + case VSTREAM_POPEN_ARGV: + if (args->command != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->argv = va_arg(ap, char **); + break; + case VSTREAM_POPEN_COMMAND: + if (args->argv != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->command = va_arg(ap, char *); + break; + case VSTREAM_POPEN_UID: + args->privileged = 1; + args->uid = va_arg(ap, uid_t); + break; + case VSTREAM_POPEN_GID: + args->privileged = 1; + args->gid = va_arg(ap, gid_t); + break; + case VSTREAM_POPEN_ENV: + args->env = va_arg(ap, char **); + break; + case VSTREAM_POPEN_EXPORT: + args->export = va_arg(ap, char **); + break; + case VSTREAM_POPEN_SHELL: + args->shell = va_arg(ap, char *); + break; + case VSTREAM_POPEN_WAITPID_FN: + args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN); + break; + default: + msg_panic("%s: unknown key: %d", myname, key); + } + } + + if (args->command == 0 && args->argv == 0) + msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname); + if (args->privileged != 0 && args->uid == 0) + msg_panic("%s: privileged uid", myname); + if (args->privileged != 0 && args->gid == 0) + msg_panic("%s: privileged gid", myname); +} + +/* vstream_popen - open stream to child process */ + +VSTREAM *vstream_popen(int flags,...) +{ + const char *myname = "vstream_popen"; + VSTREAM_POPEN_ARGS args; + va_list ap; + VSTREAM *stream; + int sockfd[2]; + int pid; + int fd; + ARGV *argv; + char **cpp; + + va_start(ap, flags); + vstream_parse_args(&args, ap); + va_end(ap); + + if (args.command == 0) + args.command = args.argv[0]; + + if (duplex_pipe(sockfd) < 0) + return (0); + + switch (pid = fork()) { + case -1: /* error */ + (void) close(sockfd[0]); + (void) close(sockfd[1]); + return (0); + case 0: /* child */ + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + if (close(sockfd[1])) + msg_warn("close: %m"); + for (fd = 0; fd < 2; fd++) + if (sockfd[0] != fd) + if (DUP2(sockfd[0], fd) < 0) + msg_fatal("dup2: %m"); + if (sockfd[0] >= 2 && close(sockfd[0])) + msg_warn("close: %m"); + + /* + * Don't try to become someone else unless the user specified it. + */ + if (args.privileged) + set_ugid(args.uid, args.gid); + + /* + * Environment plumbing. Always reset the command search path. XXX + * That should probably be done by clean_env(). + */ + if (args.export) + clean_env(args.export); + if (setenv("PATH", _PATH_DEFPATH, 1)) + msg_fatal("%s: setenv: %m", myname); + if (args.env) + for (cpp = args.env; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv: %m"); + + /* + * Process plumbing. If possible, avoid running a shell. + */ + closelog(); + if (args.argv) { + execvp(args.argv[0], args.argv); + msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); + } else if (args.shell && *args.shell) { + argv = argv_split(args.shell, CHARS_SPACE); + argv_add(argv, args.command, (char *) 0); + argv_terminate(argv); + execvp(argv->argv[0], argv->argv); + msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); + } else { + exec_command(args.command); + } + /* NOTREACHED */ + default: /* parent */ + if (close(sockfd[0])) + msg_warn("close: %m"); + stream = vstream_fdopen(sockfd[1], flags); + stream->waitpid_fn = args.waitpid_fn; + stream->pid = pid; + return (stream); + } +} + +/* vstream_pclose - close stream to child process */ + +int vstream_pclose(VSTREAM *stream) +{ + pid_t saved_pid = stream->pid; + VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn; + pid_t pid; + WAIT_STATUS_T wait_status; + + /* + * Close the pipe. Don't trigger an alarm in vstream_fclose(). + */ + if (saved_pid == 0) + msg_panic("vstream_pclose: stream has no process"); + stream->pid = 0; + vstream_fclose(stream); + + /* + * Reap the child exit status. + */ + do { + if (saved_waitpid_fn != 0) + pid = saved_waitpid_fn(saved_pid, &wait_status, 0); + else + pid = waitpid(saved_pid, &wait_status, 0); + } while (pid == -1 && errno == EINTR); + return (pid == -1 ? -1 : + WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) : + WEXITSTATUS(wait_status)); +} + +#ifdef TEST + +#include <fcntl.h> +#include <vstring.h> +#include <vstring_vstream.h> + + /* + * Test program. Run a command and copy lines one by one. + */ +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + VSTREAM *stream; + int status; + + /* + * Sanity check. + */ + if (argc < 2) + msg_fatal("usage: %s 'command'", argv[0]); + + /* + * Open stream to child process. + */ + if ((stream = vstream_popen(O_RDWR, + VSTREAM_POPEN_ARGV, argv + 1, + VSTREAM_POPEN_END)) == 0) + msg_fatal("vstream_popen: %m"); + + /* + * Copy loop, one line at a time. + */ + while (vstring_fgets(buf, stream) != 0) { + if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("vstream_fflush: %m"); + if (vstring_fgets(buf, VSTREAM_IN) == 0) + break; + if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + } + + /* + * Cleanup. + */ + vstring_free(buf); + if ((status = vstream_pclose(stream)) != 0) + msg_warn("exit status: %d", status); + + exit(status); +} + +#endif |