summaryrefslogtreecommitdiffstats
path: root/src/util/vstream_popen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/vstream_popen.c')
-rw-r--r--src/util/vstream_popen.c363
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