Split exec.c into exec.c and exec_pty.c
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Jun 2010 22:06:22 +0000 (18:06 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Jun 2010 22:06:22 +0000 (18:06 -0400)
MANIFEST
src/Makefile.in
src/exec.c
src/exec_pty.c [new file with mode: 0644]
src/sudo.h

index a4e86f0b4e814adfa46e9310460e384eb6275d1b..d18d4b158c2951cd763639879e33f614b62bcfdd 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -182,6 +182,7 @@ src/aix.c
 src/conversation.c
 src/error.c
 src/exec.c
+src/exec_pty.c
 src/get_pty.c
 src/load_plugins.c
 src/parse_args.c
index a8374816af07a412b26a25700f42ab40e6efb2eb..c67ab8eb91a6b2af7136eec7f2664499f142e934 100644 (file)
@@ -72,8 +72,8 @@ SHELL = @SHELL@
 
 PROGS = @PROGS@
 
-OBJS = conversation.o error.o exec.o get_pty.o load_plugins.o parse_args.o \
-       sudo.o sudo_edit.o tgetpass.o ttysize.o @SUDO_OBJS@
+OBJS = conversation.o error.o exec.o exec_pty.o get_pty.o load_plugins.o \
+       parse_args.o sudo.o sudo_edit.o tgetpass.o ttysize.o @SUDO_OBJS@
 
 LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
 
@@ -109,6 +109,7 @@ aix.o: $(srcdir)/aix.c $(top_builddir)/config.h $(incdir)/compat.h
 conversation.o: $(srcdir)/conversation.c $(SUDODEP)
 error.o: $(srcdir)/error.c $(incdir)/compat.h $(incdir)/error.h $(top_builddir)/config.h
 exec.o: $(srcdir)/exec.c $(SUDODEP)
+exec_pty.o: $(srcdir)/exec_pty.c $(SUDODEP)
 get_pty.o: $(srcdir)/get_pty.c $(SUDODEP)
 load_plugins.o: $(srcdir)/load_plugins.c $(SUDODEP)
 parse_args.o: $(srcdir)/parse_args.c sudo_usage.h $(SUDODEP)
index f42755e40deff14053fa7bd1067cc7397abff803..c704abe7a4cc103fa22ed4570ab3187eb500d227 100644 (file)
 #endif
 #include <errno.h>
 #include <fcntl.h>
-#include <grp.h>
-#include <pwd.h>
 #include <signal.h>
 #ifdef HAVE_SELINUX
 # include <selinux/selinux.h>
 #endif
 
+/* XXX - move to compat */
 #if !defined(NSIG)
 # if defined(_NSIG)
 #  define NSIG _NSIG
 #include "sudo_plugin.h"
 #include "sudo_plugin_int.h"
 
-#define SFD_STDIN      0
-#define SFD_STDOUT     1
-#define SFD_STDERR     2
-#define SFD_MASTER     3
-#define SFD_SLAVE      4
-#define SFD_USERTTY    5
-
-#define TERM_COOKED    0
-#define TERM_RAW       1
-
-/* Compatibility with older tty systems. */
-#if !defined(TIOCGSIZE) && defined(TIOCGWINSZ)
-# define TIOCGSIZE     TIOCGWINSZ
-# define TIOCSSIZE     TIOCSWINSZ
-# define ttysize       winsize
-# define ts_cols       ws_col
-#endif
-
-struct io_buffer {
-    struct io_buffer *next;
-    int len; /* buffer length (how much produced) */
-    int off; /* write position (how much already consumed) */
-    int rfd;  /* reader (producer) */
-    int wfd; /* writer (consumer) */
-    int (*action)(const char *buf, unsigned int len);
-    char buf[16 * 1024];
-};
-
-static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
-static int pipeline = FALSE;
-
-static sig_atomic_t recvsig[NSIG];
-
-static sigset_t ttyblock;
-
-static pid_t ppgrp, child;
-static int foreground;
-static int ttymode = TERM_COOKED;
-static int tty_initialized;
-
-static char slavename[PATH_MAX];
-
-static int suspend_parent(int signo, struct io_buffer *iobufs);
-static void flush_output(struct io_buffer *iobufs);
-static int perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw);
-static void handler(int s);
-static int my_execve(const char *path, char *const argv[],
-    char *const envp[]);
-static int exec_monitor(struct command_details *details, char *argv[],
-    char *envp[], int, int);
-static void exec_pty(struct command_details *detail, char *argv[],
-    char *envp[], int);
-static void sigwinch(int s);
-static void sync_ttysize(int src, int dst);
-static void deliver_signal(pid_t pid, int signo);
-static int safe_close(int fd);
-
 extern struct user_details user_details; /* XXX need tty name for SELinux */
 
-static void
-pty_setup(uid_t uid)
-{
-    io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
-    if (io_fds[SFD_USERTTY] != -1) {
-       if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE],
-           slavename, sizeof(slavename), uid))
-           error(1, "Can't get pty");
-    }
-}
-
-/*
- * Cleanup hook for error()/errorx()
- */
-void
-cleanup(int gotsignal)
-{
-    if (!tq_empty(&io_plugins))
-       term_restore(io_fds[SFD_USERTTY], 0);
-}
-
-/* Call I/O plugin tty input log method. */
-static int
-log_ttyin(char *buf, unsigned int n)
-{
-    struct plugin_container *plugin;
-    sigset_t omask;
-    int rval = TRUE;
-
-    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
-
-    tq_foreach_fwd(&io_plugins, plugin) {
-       if (plugin->u.io->log_ttyin) {
-           if (!plugin->u.io->log_ttyin(buf, n)) {
-               rval = FALSE;
-               break;
-           }
-       }
-    }
-
-    sigprocmask(SIG_SETMASK, &omask, NULL);
-    return rval;
-}
-
-/* Call I/O plugin stdin log method. */
-static int
-log_stdin(char *buf, unsigned int n)
-{
-    struct plugin_container *plugin;
-    sigset_t omask;
-    int rval = TRUE;
-
-    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
-
-    tq_foreach_fwd(&io_plugins, plugin) {
-       if (plugin->u.io->log_stdin) {
-           if (!plugin->u.io->log_stdin(buf, n)) {
-               rval = FALSE;
-               break;
-           }
-       }
-    }
-
-    sigprocmask(SIG_SETMASK, &omask, NULL);
-    return rval;
-}
-
-/* Call I/O plugin tty output log method. */
-static int
-log_ttyout(char *buf, unsigned int n)
-{
-    struct plugin_container *plugin;
-    sigset_t omask;
-    int rval = TRUE;
-
-    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
-
-    tq_foreach_fwd(&io_plugins, plugin) {
-       if (plugin->u.io->log_ttyout) {
-           if (!plugin->u.io->log_ttyout(buf, n)) {
-               rval = FALSE;
-               break;
-           }
-       }
-    }
-
-    sigprocmask(SIG_SETMASK, &omask, NULL);
-    return rval;
-}
-
-/* Call I/O plugin stdout log method. */
-static int
-log_stdout(char *buf, unsigned int n)
-{
-    struct plugin_container *plugin;
-    sigset_t omask;
-    int rval = TRUE;
-
-    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
-
-    tq_foreach_fwd(&io_plugins, plugin) {
-       if (plugin->u.io->log_stdout) {
-           if (!plugin->u.io->log_stdout(buf, n)) {
-               rval = FALSE;
-               break;
-           }
-       }
-    }
-
-    sigprocmask(SIG_SETMASK, &omask, NULL);
-    return rval;
-}
-
-/* Call I/O plugin stderr log method. */
-static int
-log_stderr(char *buf, unsigned int n)
-{
-    struct plugin_container *plugin;
-    sigset_t omask;
-    int rval = TRUE;
-
-    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
-
-    tq_foreach_fwd(&io_plugins, plugin) {
-       if (plugin->u.io->log_stderr) {
-           if (!plugin->u.io->log_stderr(buf, n)) {
-               rval = FALSE;
-               break;
-           }
-       }
-    }
-
-    sigprocmask(SIG_SETMASK, &omask, NULL);
-    return rval;
-}
-
-static void
-check_foreground(void)
-{
-    if (io_fds[SFD_USERTTY] != -1) {
-       foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
-       if (foreground && !tty_initialized) {
-           if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
-               tty_initialized = 1;
-               sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
-           }
-       }
-    }
-}
-
-/*
- * Suspend sudo if the underlying command is suspended.
- * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
- */
-static int
-suspend_parent(int signo, struct io_buffer *iobufs)
-{
-    sigaction_t sa, osa;
-    int n, oldmode = ttymode, rval = 0;
-
-    switch (signo) {
-    case SIGTTOU:
-    case SIGTTIN:
-       /*
-        * If we are the foreground process, just resume the child.
-        * Otherwise, re-send the signal with the handler disabled.
-        */
-       if (!foreground)
-           check_foreground();
-       if (foreground) {
-           if (ttymode != TERM_RAW) {
-               do {
-                   n = term_raw(io_fds[SFD_USERTTY], 0);
-               } while (!n && errno == EINTR);
-               ttymode = TERM_RAW;
-           }
-           rval = SIGUSR1; /* resume child in foreground */
-           break;
-       }
-       ttymode = TERM_RAW;
-       /* FALLTHROUGH */
-    case SIGSTOP:
-    case SIGTSTP:
-       /* Flush any remaining output before suspending. */
-       flush_output(iobufs);
-
-       /* Restore original tty mode before suspending. */
-       if (oldmode != TERM_COOKED) {
-           do {
-               n = term_restore(io_fds[SFD_USERTTY], 0);
-           } while (!n && errno == EINTR);
-       }
-
-       /* Suspend self and continue child when we resume. */
-       sa.sa_handler = SIG_DFL;
-       sigaction(signo, &sa, &osa);
-       sudo_debug(8, "kill parent %d", signo);
-       killpg(ppgrp, signo);
-
-       /* Check foreground/background status on resume. */
-       check_foreground();
-
-       /*
-        * Only modify term if we are foreground process and either
-        * the old tty mode was not cooked or child got SIGTT{IN,OU}
-        */
-       sudo_debug(8, "parent is in %sground, ttymode %d -> %d",
-           foreground ? "fore" : "back", oldmode, ttymode);
-
-       if (ttymode != TERM_COOKED) {
-           if (foreground) {
-               /* Set raw mode. */
-               do {
-                   n = term_raw(io_fds[SFD_USERTTY], 0);
-               } while (!n && errno == EINTR);
-           } else {
-               /* Background process, no access to tty. */
-               ttymode = TERM_COOKED;
-           }
-       }
-
-       sigaction(signo, &osa, NULL);
-       rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2;
-       break;
-    }
-
-    return(rval);
-}
+/* shared with exec_pty.c */
+sig_atomic_t recvsig[NSIG];
+void handler(int s);
 
 /*
  * Like execve(2) but falls back to running through /bin/sh
  * ala execvp(3) if we get ENOEXEC.
  */
-static int
+int
 my_execve(const char *path, char *const argv[], char *const envp[])
 {
     execve(path, argv, envp);
@@ -391,121 +108,65 @@ my_execve(const char *path, char *const argv[], char *const envp[])
     return -1;
 }
 
-static void
-terminate_child(pid_t pid, int use_pgrp)
-{
-    /*
-     * Kill child with increasing urgency.
-     * Note that SIGCHLD will interrupt the sleep()
-     */
-    if (use_pgrp) {
-       killpg(pid, SIGHUP);
-       killpg(pid, SIGTERM);
-       sleep(2);
-       killpg(pid, SIGKILL);
-    } else {
-       kill(pid, SIGHUP);
-       kill(pid, SIGTERM);
-       sleep(2);
-       kill(pid, SIGKILL);
-    }
-}
-
-static struct io_buffer *
-io_buf_new(int rfd, int wfd, int (*action)(const char *, unsigned int),
-    struct io_buffer *head)
-{
-    struct io_buffer *iob;
-
-    iob = emalloc(sizeof(*iob));
-    zero_bytes(iob, sizeof(*iob));
-    iob->rfd = rfd;
-    iob->wfd = wfd;
-    iob->action = action;
-    iob->next = head;
-    return iob;
-}
-
 /*
- * Read/write iobufs depending on fdsr and fdsw.
- * Returns the number of errors.
+ * Fork and execute a command, returns the child's pid.
+ * Sends errno back on sv[1] if execve() fails.
  */
-static int
-perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw)
+static int fork_cmnd(struct command_details *details, char *argv[],
+    char *envp[], int sv[2], int rbac_enabled)
 {
-    struct io_buffer *iob;
-    int n, errors = 0;
+    struct command_status cstat;
+    int pid;
 
-    for (iob = iobufs; iob; iob = iob->next) {
-       if (iob->rfd != -1 && FD_ISSET(iob->rfd, fdsr)) {
-           do {
-               n = read(iob->rfd, iob->buf + iob->len,
-                   sizeof(iob->buf) - iob->len);
-           } while (n == -1 && errno == EINTR);
-           if (n == -1) {
-               if (errno != EAGAIN)
-                   break;
-           } else if (n == 0) {
-               /* got EOF */
-               safe_close(iob->rfd);
-               iob->rfd = -1;
-           } else {
-               if (!iob->action(iob->buf + iob->len, n))
-                   terminate_child(child, TRUE);
-               iob->len += n;
-           }
+    pid = fork();
+    switch (pid) {
+    case -1:
+       error(1, "fork");
+       break;
+    case 0:
+       /* child */
+       close(sv[0]);
+       fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+#ifdef HAVE_SELINUX
+       if (rbac_enabled) {
+           selinux_setup(details->selinux_role, details->selinux_type,
+               user_details.tty, -1);
        }
-       if (iob->wfd != -1 && FD_ISSET(iob->wfd, fdsw)) {
-           do {
-               n = write(iob->wfd, iob->buf + iob->off,
-                   iob->len - iob->off);
-           } while (n == -1 && errno == EINTR);
-           if (n == -1) {
-               if (errno == EPIPE) {
-                   /* other end of pipe closed */
-                   if (iob->rfd != -1) {
-                       safe_close(iob->rfd);
-                       iob->rfd = -1;
-                   }
-                   safe_close(iob->wfd);
-                   iob->wfd = -1;
-                   continue;
-               }
-               if (errno != EAGAIN)
-                   errors++;
-           } else {
-               iob->off += n;
-           }
+#endif
+       if (exec_setup(details) == TRUE) {
+           /* headed for execve() */
+           if (details->closefrom >= 0)
+               closefrom(details->closefrom);
+#ifdef HAVE_SELINUX
+           if (rbac_enabled)
+               selinux_execve(details->command, argv, envp);
+           else
+#endif
+               my_execve(details->command, argv, envp);
        }
+       cstat.type = CMD_ERRNO;
+       cstat.val = errno;
+       send(sv[1], &cstat, sizeof(cstat), 0);
+       _exit(1);
     }
-    return errors;
+    return pid;
 }
 
 /*
+ * Execute a command, potentially in a pty with I/O loggging.
  * This is a little bit tricky due to how POSIX job control works and
  * we fact that we have two different controlling terminals to deal with.
- * There are three processes:
- *  1) parent, which forks a child and does all the I/O passing.
- *     Handles job control signals send by its child to bridge the
- *     two sessions (and ttys).
- *  2) child, creates a new session so it can receive notification of
- *     tty stop signals (SIGTSTP, SIGTTIN, SIGTTOU).  Waits for the
- *     command to stop or die and passes back tty stop signals to parent
- *     so job control works in the user's shell.
- *  3) grandchild, executes the actual command with the pty slave as its
- *     controlling tty, belongs to child's session but has its own pgrp.
  */
 int
 sudo_execve(struct command_details *details, char *argv[], char *envp[],
     struct command_status *cstat)
 {
     sigaction_t sa;
-    struct io_buffer *iob, *iobufs = NULL;
-    int n, nready;
-    int io_pipe[3][2], sv[2];
     fd_set *fdsr, *fdsw;
+    int maxfd, n, nready, status, sv[2];
     int rbac_enabled = 0;
-    int log_io, maxfd, status;
+    int log_io = 0;
+    pid_t child;
 
     cstat->type = CMD_INVALID;
 
@@ -517,17 +178,10 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
 
 #ifdef HAVE_SELINUX
     rbac_enabled = is_selinux_enabled() > 0 && details->selinux_role != NULL;
-    if (rbac_enabled) {
-       /* Must do SELinux setup before changing uid. */
-       selinux_setup(details->selinux_role, details->selinux_type,
-           log_io ? slavename : user_details.tty, io_fds[SFD_SLAVE]);
-    }
 #endif
 
-    ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
-
     /*
-     * We communicate with the child over a bi-directional pipe.
+     * We communicate with the child over a bi-directional pair of sockets.
      * Parent sends signal info to child and child sends back wait status.
      */
     if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
@@ -547,173 +201,23 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
     sigaction(SIGQUIT, &sa, NULL);
     sigaction(SIGTERM, &sa, NULL);
 
-    if (log_io) {
-       if (io_fds[SFD_USERTTY] != -1) {
-           sa.sa_flags = SA_RESTART;
-           sa.sa_handler = sigwinch;
-           sigaction(SIGWINCH, &sa, NULL);
-       }
-
-       /* So we can block tty-generated signals */
-       sigemptyset(&ttyblock);
-       sigaddset(&ttyblock, SIGINT);
-       sigaddset(&ttyblock, SIGQUIT);
-       sigaddset(&ttyblock, SIGTSTP);
-       sigaddset(&ttyblock, SIGTTIN);
-       sigaddset(&ttyblock, SIGTTOU);
-
-       /*
-        * Setup stdin/stdout/stderr for child, to be duped after forking.
-        */
-       io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
-       io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
-       io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
-
-       /* Copy /dev/tty -> pty master */
-       if (io_fds[SFD_USERTTY] != -1) {
-           iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
-               log_ttyin, iobufs);
-
-           /* Copy pty master -> /dev/tty */
-           iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
-               log_ttyout, iobufs);
-
-           /* Are we the foreground process? */
-           foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
-       }
-
-       /*
-        * If either stdin, stdout or stderr is not a tty we use a pipe
-        * to interpose ourselves instead of duping the pty fd.
-        */
-       memset(io_pipe, 0, sizeof(io_pipe));
-       if (!isatty(STDIN_FILENO)) {
-           pipeline = TRUE;
-           if (pipe(io_pipe[STDIN_FILENO]) != 0)
-               error(1, "unable to create pipe");
-           iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
-               log_stdin, iobufs);
-           io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
-       }
-       if (!isatty(STDOUT_FILENO)) {
-           pipeline = TRUE;
-           if (pipe(io_pipe[STDOUT_FILENO]) != 0)
-               error(1, "unable to create pipe");
-           iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
-               log_stdout, iobufs);
-           io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
-       }
-       if (!isatty(STDERR_FILENO)) {
-           if (pipe(io_pipe[STDERR_FILENO]) != 0)
-               error(1, "unable to create pipe");
-           iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
-               log_stderr, iobufs);
-           io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
-       }
-
-       /* Job control signals to relay from parent to child. */
-       sa.sa_flags = 0; /* do not restart syscalls */
-       sa.sa_handler = handler;
-       sigaction(SIGTSTP, &sa, NULL);
-#if 0 /* XXX - add these? */
-       sigaction(SIGTTIN, &sa, NULL);
-       sigaction(SIGTTOU, &sa, NULL);
-#endif
-
-       if (foreground) {
-           /* Copy terminal attrs from user tty -> pty slave. */
-           if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
-               tty_initialized = 1;
-               sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
-           }
-
-           /* Start out in raw mode if we are not part of a pipeline. */
-           if (!pipeline) {
-               ttymode = TERM_RAW;
-               do {
-                   n = term_raw(io_fds[SFD_USERTTY], 0);
-               } while (!n && errno == EINTR);
-               if (!n)
-                   error(1, "Can't set terminal to raw mode");
-           }
-       }
-    }
+    /* Max fd we will be selecting on. */
+    maxfd = sv[0];
 
     /*
      * Child will run the command in the pty, parent will pass data
-     * to and from pty.
+     * to and from pty.  Adjusts maxfd as needed.
      */
-    child = fork();
-    switch (child) {
-    case -1:
-       error(1, "fork");
-       break;
-    case 0:
-       /* child */
-       close(sv[0]);
-       fcntl(sv[1], F_SETFD, FD_CLOEXEC);
-       if (exec_setup(details) == TRUE) {
-           /* headed for execve() */
-           if (log_io) {
-               /* Close the other end of the stdin/stdout/stderr pipes. */
-               if (io_pipe[STDIN_FILENO][1])
-                   close(io_pipe[STDIN_FILENO][1]);
-               if (io_pipe[STDOUT_FILENO][0])
-                   close(io_pipe[STDOUT_FILENO][0]);
-               if (io_pipe[STDERR_FILENO][0])
-                   close(io_pipe[STDERR_FILENO][0]);
-               exec_monitor(details, argv, envp, sv[1], rbac_enabled);
-           } else {
-               if (details->closefrom >= 0)
-                   closefrom(details->closefrom);
-#ifdef HAVE_SELINUX
-               if (rbac_enabled)
-                   selinux_execve(details->command, argv, envp);
-               else
-#endif
-                   my_execve(details->command, argv, envp);
-           }
-       }
-       cstat->type = CMD_ERRNO;
-       cstat->val = errno;
-       send(sv[1], cstat, sizeof(*cstat), 0);
-       _exit(1);
-    }
+    if (log_io)
+       child = fork_pty(details, argv, envp, sv, rbac_enabled, &maxfd);
+    else
+       child = fork_cmnd(details, argv, envp, sv, rbac_enabled);
     close(sv[1]);
 
     /* Set command timeout if specified. */
     if (ISSET(details->flags, CD_SET_TIMEOUT))
        alarm(details->timeout);
 
-    /* Max fd we will be selecting on. */
-    maxfd = sv[0];
-
-    if (log_io) {
-       /* Close the other end of the stdin/stdout/stderr pipes. */
-       if (io_pipe[STDIN_FILENO][0])
-           close(io_pipe[STDIN_FILENO][0]);
-       if (io_pipe[STDOUT_FILENO][1])
-           close(io_pipe[STDOUT_FILENO][1]);
-       if (io_pipe[STDERR_FILENO][1])
-           close(io_pipe[STDERR_FILENO][1]);
-
-       for (iob = iobufs; iob; iob = iob->next) {
-           /* Determine maxfd */
-           if (iob->rfd > maxfd)
-               maxfd = iob->rfd;
-           if (iob->wfd > maxfd)
-               maxfd = iob->wfd;
-
-           /* Set non-blocking mode. */
-           n = fcntl(iob->rfd, F_GETFL, 0);
-           if (n != -1 && !ISSET(n, O_NONBLOCK))
-               (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK);
-           n = fcntl(iob->wfd, F_GETFL, 0);
-           if (n != -1 && !ISSET(n, O_NONBLOCK))
-               (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK);
-       }
-    }
-
     /*
      * In the event loop we pass input from user tty to master
      * and pass output from master to stdout and IO plugin.
@@ -730,15 +234,22 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
             */
            recvsig[SIGCHLD] = FALSE;
            do {
-               pid = waitpid(child, &status, WNOHANG);
+               pid = waitpid(child, &status, WUNTRACED|WNOHANG);
            } while (pid == -1 && errno == EINTR);
            if (pid == child) {
                /* If not logging I/O and child has exited we are done. */
                if (!log_io) {
-                   cstat->type = CMD_WSTATUS;
-                   cstat->val = status;
-                   return 0;
+                   if (WIFSTOPPED(status)) {
+                       /* Child may not have privs to suspend us itself. */
+                       kill(getpid(), WSTOPSIG(status));
+                   } else {
+                       /* Child has exited, we are done. */
+                       cstat->type = CMD_WSTATUS;
+                       cstat->val = status;
+                       return 0;
+                   }
                }
+               /* Else we get ECONNRESET on sv[0] if child dies. */
            }
        }
 
@@ -746,29 +257,8 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
        zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
 
        FD_SET(sv[0], fdsr);
-       for (iob = iobufs; iob; iob = iob->next) {
-           if (iob->rfd == -1 && iob->wfd == -1)
-               continue;
-           if (iob->off == iob->len) {
-               iob->off = iob->len = 0;
-               /* Forward the EOF from reader to writer. */
-               if (iob->rfd == -1) {
-                   safe_close(iob->wfd);
-                   iob->wfd = -1;
-               }
-           }
-           /* Don't read/write /dev/tty if we are not in the foreground. */
-           if (iob->rfd != -1 &&
-               (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) {
-               if (iob->len != sizeof(iob->buf))
-                   FD_SET(iob->rfd, fdsr);
-           }
-           if (iob->wfd != -1 &&
-               (foreground || iob->wfd != io_fds[SFD_USERTTY])) {
-               if (iob->len > iob->off)
-                   FD_SET(iob->wfd, fdsw);
-           }
-       }
+       if (log_io)
+           fd_set_iobs(fdsr, fdsw); /* XXX - better name */
        for (n = 0; n < NSIG; n++) {
            if (recvsig[n] && n != SIGCHLD) {
                if (log_io) {
@@ -813,7 +303,7 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
                if (WIFSTOPPED(cstat->val)) {
                    /* Suspend parent and tell child how to resume on return. */
                    sudo_debug(8, "child stopped, suspending parent");
-                   n = suspend_parent(WSTOPSIG(cstat->val), iobufs);
+                   n = suspend_parent(WSTOPSIG(cstat->val));
                    recvsig[n] = TRUE;
                    continue;
                } else {
@@ -843,43 +333,14 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
                }
            }
        }
-       if (perform_io(iobufs, fdsr, fdsw) != 0) {
-           cstat->type = CMD_ERRNO;
-           cstat->val = errno;
+       if (perform_io(fdsr, fdsw, cstat) != 0)
            break;
-       }
     }
 
     if (log_io) {
-       /* Flush any remaining output (the plugin already got it) */
-       if (io_fds[SFD_USERTTY] != -1) {
-           n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0);
-           if (n != -1 && ISSET(n, O_NONBLOCK)) {
-               CLR(n, O_NONBLOCK);
-               (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n);
-           }
-       }
-       flush_output(iobufs);
-
-       if (io_fds[SFD_USERTTY] != -1) {
-           do {
-               n = term_restore(io_fds[SFD_USERTTY], 0);
-           } while (!n && errno == EINTR);
-       }
-
-       if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
-           int signo = WTERMSIG(cstat->val);
-           if (signo && signo != SIGINT && signo != SIGPIPE) {
-               char *reason = strsignal(signo);
-               n = io_fds[SFD_USERTTY] != -1 ?
-                   io_fds[SFD_USERTTY] : STDOUT_FILENO;
-               write(n, reason, strlen(reason));
-               if (WCOREDUMP(cstat->val))
-                   write(n, " (core dumped)", 14);
-               write(n, "\n", 1);
-           }
-       }
-    }
+       /* Flush any remaining output and free pty-related memory. */
+       pty_close(cstat);
+   }
 
 #ifdef HAVE_SELINUX
     if (rbac_enabled) {
@@ -891,511 +352,16 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[],
 
     efree(fdsr);
     efree(fdsw);
-    while ((iob = iobufs) != NULL) {
-       iobufs = iobufs->next;
-       efree(iob);
-    }
 
     return cstat->type == CMD_ERRNO ? -1 : 0;
 }
 
-static void
-deliver_signal(pid_t pid, int signo)
-{
-    int status;
-
-    /* Handle signal from parent. */
-    sudo_debug(8, "signal %d from parent", signo);
-    switch (signo) {
-    case SIGKILL:
-       _exit(1); /* XXX */
-       /* NOTREACHED */
-    case SIGPIPE:
-    case SIGHUP:
-    case SIGTERM:
-    case SIGINT:
-    case SIGQUIT:
-    case SIGTSTP:
-       /* relay signal to child */
-       killpg(pid, signo);
-       break;
-    case SIGALRM:
-       terminate_child(pid, TRUE);
-       break;
-    case SIGUSR1:
-       /* foreground process, grant it controlling tty. */
-       do {
-           status = tcsetpgrp(io_fds[SFD_SLAVE], pid);
-       } while (status == -1 && errno == EINTR);
-       killpg(pid, SIGCONT);
-       break;
-    case SIGUSR2:
-       /* background process, I take controlling tty. */
-       do {
-           status = tcsetpgrp(io_fds[SFD_SLAVE], getpid());
-       } while (status == -1 && errno == EINTR);
-       killpg(pid, SIGCONT);
-       break;
-    default:
-       warningx("unexpected signal from child: %d", signo);
-       break;
-    }
-}
-
-/*
- * Send status to parent over socketpair.
- * Return value is the same as send(2).
- */
-static int
-send_status(int fd, struct command_status *cstat)
-{
-    int n = -1;
-
-    if (cstat->type != CMD_INVALID) {
-       do {
-           n = send(fd, cstat, sizeof(*cstat), 0);
-       } while (n == -1 && errno == EINTR);
-       if (n != sizeof(*cstat)) {
-           sudo_debug(8, "unable to send status to parent: %s", strerror(errno));
-       } else {
-           sudo_debug(8, "sent status to parent");
-       }
-       cstat->type = CMD_INVALID; /* prevent re-sending */
-    }
-    return n;
-}
-
-/*
- * Wait for child status after receiving SIGCHLD.
- * If the child was stopped, the status is send back to the parent.
- * Otherwise, cstat is filled in but not sent.
- * Returns TRUE if child is still alive, else FALSE.
- */
-static int
-handle_sigchld(int backchannel, struct command_status *cstat)
-{
-    int status, alive = TRUE;
-    pid_t pid;
-
-    /* read child status */
-    do {
-       pid = waitpid(child, &status, WUNTRACED|WNOHANG);
-    } while (pid == -1 && errno == EINTR);
-    if (pid == child) {
-       if (cstat->type != CMD_ERRNO) {
-           cstat->type = CMD_WSTATUS;
-           cstat->val = status;
-           if (WIFSTOPPED(status)) {
-               sudo_debug(8, "command stopped, signal %d",
-                   WSTOPSIG(status));
-               if (send_status(backchannel, cstat) == -1)
-                   return alive; /* XXX */
-           } else if (WIFSIGNALED(status)) {
-               sudo_debug(8, "command killed, signal %d",
-                   WTERMSIG(status));
-           } else {
-               sudo_debug(8, "command exited: %d",
-                   WEXITSTATUS(status));
-           }
-       }
-       if (!WIFSTOPPED(status))
-           alive = FALSE;
-    }
-    return alive;
-}
-
-/*
- * Monitor process that creates a new session with the controlling tty,
- * resets signal handlers and forks a child to call exec_pty().
- * Waits for status changes from the command and relays them to the
- * parent and relays signals from the parent to the command.
- * Returns an error if fork(2) fails, else calls _exit(2).
- */
-int
-exec_monitor(struct command_details *details, char *argv[], char *envp[],
-    int backchannel, int rbac)
-{
-    struct command_status cstat;
-    struct timeval tv;
-    fd_set *fdsr;
-    sigaction_t sa;
-    int errpipe[2], maxfd, n, status;
-    int alive = TRUE;
-
-    /* Close unused fds. */
-    if (io_fds[SFD_MASTER] != -1)
-       close(io_fds[SFD_MASTER]);
-    if (io_fds[SFD_USERTTY] != -1)
-       close(io_fds[SFD_USERTTY]);
-
-    /* Reset SIGWINCH and SIGALRM. */
-    zero_bytes(&sa, sizeof(sa));
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
-    sa.sa_handler = SIG_DFL;
-    sigaction(SIGWINCH, &sa, NULL);
-    sigaction(SIGALRM, &sa, NULL);
-
-    /* Ignore any SIGTTIN or SIGTTOU we get. */
-    sa.sa_handler = SIG_IGN;
-    sigaction(SIGTTIN, &sa, NULL);
-    sigaction(SIGTTOU, &sa, NULL);
-
-    /* Note: HP-UX select() will not be interrupted if SA_RESTART set */
-    sa.sa_flags = 0;
-    sa.sa_handler = handler;
-    sigaction(SIGCHLD, &sa, NULL);
-
-    /*
-     * Start a new session with the parent as the session leader
-     * and the slave pty as the controlling terminal.
-     * This allows us to be notified when the child has been suspended.
-     */
-#ifdef HAVE_SETSID
-    if (setsid() == -1) {
-       warning("setsid");
-       goto bad;
-    }
-#else
-# ifdef TIOCNOTTY
-    n = open(_PATH_TTY, O_RDWR|O_NOCTTY);
-    if (n >= 0) {
-       /* Disconnect from old controlling tty. */
-       if (ioctl(n, TIOCNOTTY, NULL) == -1)
-           warning("cannot disconnect controlling tty");
-       close(n);
-    }
-# endif
-    setpgrp(0, 0);
-#endif
-    if (io_fds[SFD_SLAVE] != -1) {
-#ifdef TIOCSCTTY
-       if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
-           error(1, "unable to set controlling tty");
-#else
-       /* Set controlling tty by reopening slave. */
-       if ((n = open(slavename, O_RDWR)) >= 0)
-           close(n);
-#endif
-    }
-
-    /*
-     * If stdin/stdout is not a tty, start command in the background
-     * since it might be part of a pipeline that reads from /dev/tty.
-     * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
-     * when it needs access to the controlling tty.
-     */
-    if (pipeline)
-       foreground = 0;
-
-    /* Start command and wait for it to stop or exit */
-    if (pipe(errpipe) == -1)
-       error(1, "unable to create pipe");
-    child = fork();
-    if (child == -1) {
-       warning("Can't fork");
-       goto bad;
-    }
-    if (child == 0) {
-       /* We pass errno back to our parent via pipe on exec failure. */
-       close(backchannel);
-       close(errpipe[0]);
-       fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
-
-       /* setup tty and exec command */
-       exec_pty(details, argv, envp, rbac);
-       cstat.type = CMD_ERRNO;
-       cstat.val = errno;
-       write(errpipe[1], &cstat, sizeof(cstat));
-       _exit(1);
-    }
-    close(errpipe[1]);
-
-    /* If any of stdin/stdout/stderr are pipes, close them in parent. */
-    if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDIN]);
-    if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDOUT]);
-    if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDERR]);
-
-    /*
-     * Put child in its own process group.  If we are starting the command
-     * in the foreground, assign its pgrp to the tty.
-     */
-    setpgid(child, child);
-    if (foreground) {
-       do {
-           status = tcsetpgrp(io_fds[SFD_SLAVE], child);
-       } while (status == -1 && errno == EINTR);
-    }
-
-    /* Wait for errno on pipe, signal on backchannel or for SIGCHLD */
-    maxfd = MAX(errpipe[0], backchannel);
-    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
-    zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
-    zero_bytes(&cstat, sizeof(cstat));
-    tv.tv_sec = 0;
-    tv.tv_usec = 0;
-    for (;;) {
-       /* Read child status. */
-       if (recvsig[SIGCHLD]) {
-           recvsig[SIGCHLD] = FALSE;
-           alive = handle_sigchld(backchannel, &cstat);
-       }
-
-       /* Check for signal on backchannel or errno on errpipe. */
-       FD_SET(backchannel, fdsr);
-       if (errpipe[0] != -1)
-           FD_SET(errpipe[0], fdsr);
-       maxfd = MAX(errpipe[0], backchannel);
-
-       if (recvsig[SIGCHLD])
-           continue;
-       /* If command exited we just poll, there may be data on errpipe. */
-       n = select(maxfd + 1, fdsr, NULL, NULL, alive ? NULL : &tv);
-       if (n <= 0) {
-           if (n == 0)
-               goto done;
-           if (errno == EINTR)
-               continue;
-           error(1, "select failed");
-       }
-
-       if (errpipe[0] != -1 && FD_ISSET(errpipe[0], fdsr)) {
-           /* read errno or EOF from command pipe */
-           n = read(errpipe[0], &cstat, sizeof(cstat));
-           if (n == -1) {
-               if (errno == EINTR)
-                   continue;
-               warning("error reading from pipe");
-               goto done;
-           }
-           /* Got errno or EOF, either way we are done with errpipe. */
-           FD_CLR(errpipe[0], fdsr);
-           close(errpipe[0]);
-           errpipe[0] = -1;
-       }
-       if (FD_ISSET(backchannel, fdsr)) {
-           struct command_status cstmp;
-
-           /* read command from backchannel, should be a signal */
-           n = recv(backchannel, &cstmp, sizeof(cstmp), 0);
-           if (n == -1) {
-               if (errno == EINTR)
-                   continue;
-               warning("error reading from socketpair");
-               goto done;
-           }
-           if (cstmp.type != CMD_SIGNO) {
-               warningx("unexpected reply type on backchannel: %d", cstmp.type);
-               continue;
-           }
-           deliver_signal(child, cstmp.val);
-       }
-    }
-
-done:
-    if (alive) {
-       /* XXX An error occurred, should send an error back. */
-       kill(child, SIGKILL);
-    } else {
-       /* Send parent status. */
-       send_status(backchannel, &cstat);
-    }
-    _exit(1);
-
-bad:
-    return errno;
-}
-
-/*
- * Flush any output buffered in iobufs or readable from the fds.
- * Does not read from /dev/tty.
- */
-static void
-flush_output(struct io_buffer *iobufs)
-{
-    struct io_buffer *iob;
-    struct timeval tv;
-    fd_set *fdsr, *fdsw;
-    int nready, nwriters, maxfd = -1;
-
-    /* Determine maxfd */
-    for (iob = iobufs; iob; iob = iob->next) {
-       if (iob->rfd > maxfd)
-           maxfd = iob->rfd;
-       if (iob->wfd > maxfd)
-           maxfd = iob->wfd;
-    }
-    if (maxfd == -1)
-       return;
-
-    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
-    fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
-    for (;;) {
-       zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
-       zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
-
-       nwriters = 0;
-       for (iob = iobufs; iob; iob = iob->next) {
-           /* Don't read from /dev/tty while flushing. */
-           if (io_fds[SFD_USERTTY] != -1 && iob->rfd == io_fds[SFD_USERTTY])
-               continue;
-           if (iob->rfd == -1 && iob->wfd == -1)
-               continue;
-           if (iob->off == iob->len) {
-               iob->off = iob->len = 0;
-               /* Forward the EOF from reader to writer. */
-               if (iob->rfd == -1) {
-                   safe_close(iob->wfd);
-                   iob->wfd = -1;
-               }
-           }
-           if (iob->rfd != -1) {
-               if (iob->len != sizeof(iob->buf))
-                   FD_SET(iob->rfd, fdsr);
-           }
-           if (iob->wfd != -1) {
-               if (iob->len > iob->off) {
-                   nwriters++;
-                   FD_SET(iob->wfd, fdsw);
-               }
-           }
-       }
-
-       /* Don't sleep in select if there are no buffers that need writing. */
-       tv.tv_sec = 0;
-       tv.tv_usec = 0;
-       nready = select(maxfd + 1, fdsr, fdsw, NULL, nwriters ? NULL : &tv);
-       if (nready <= 0) {
-           if (nready == 0)
-               break; /* all I/O flushed */
-           if (errno == EINTR)
-               continue;
-           error(1, "select failed");
-       }
-       if (perform_io(iobufs, fdsr, fdsw) != 0)
-           break;
-    }
-    efree(fdsr);
-    efree(fdsw);
-}
-
-/*
- * Sets up std{in,out,err} and executes the actual command.
- * Returns only if execve() fails.
- */
-static void
-exec_pty(struct command_details *details, char *argv[], char *envp[],
-    int rbac_enabled)
-{
-    sigaction_t sa;
-    pid_t self = getpid();
-
-    /* Reset signal handlers. */
-    zero_bytes(&sa, sizeof(sa));
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
-    sa.sa_handler = SIG_DFL;
-    sigaction(SIGHUP, &sa, NULL);
-    sigaction(SIGTERM, &sa, NULL);
-    sigaction(SIGINT, &sa, NULL);
-    sigaction(SIGQUIT, &sa, NULL);
-    sigaction(SIGTSTP, &sa, NULL);
-    sigaction(SIGTTIN, &sa, NULL);
-    sigaction(SIGTTOU, &sa, NULL);
-    sigaction(SIGUSR1, &sa, NULL);
-    sigaction(SIGUSR2, &sa, NULL);
-    sigaction(SIGCHLD, &sa, NULL);
-
-    /* Set child process group here too to avoid a race. */
-    setpgid(0, self);
-
-    /* Wire up standard fds, note that stdout/stderr may be pipes. */
-    dup2(io_fds[SFD_STDIN], STDIN_FILENO);
-    dup2(io_fds[SFD_STDOUT], STDOUT_FILENO);
-    dup2(io_fds[SFD_STDERR], STDERR_FILENO);
-
-    /* Wait for parent to grant us the tty if we are foreground. */
-    if (foreground) {
-       while (tcgetpgrp(io_fds[SFD_SLAVE]) != self)
-           ; /* spin */
-    }
-
-    /* We have guaranteed that the slave fd is > 2 */
-    if (io_fds[SFD_SLAVE] != -1)
-       close(io_fds[SFD_SLAVE]);
-    if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDIN]);
-    if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDOUT]);
-    if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
-       close(io_fds[SFD_STDERR]);
-
-    if (details->closefrom >= 0)
-       closefrom(details->closefrom);
-#ifdef HAVE_SELINUX
-    if (rbac_enabled)
-       selinux_execve(details->command, argv, envp);
-    else
-#endif
-       my_execve(details->command, argv, envp);
-}
-
-/*
- * Propagates tty size change signals to pty being used by the command.
- */
-static void
-sync_ttysize(int src, int dst)
-{
-#ifdef TIOCGSIZE
-    struct ttysize tsize;
-    pid_t pgrp;
-
-    if (ioctl(src, TIOCGSIZE, &tsize) == 0) {
-           ioctl(dst, TIOCSSIZE, &tsize);
-#ifdef TIOCGPGRP
-           if (ioctl(dst, TIOCGPGRP, &pgrp) == 0)
-                   killpg(pgrp, SIGWINCH);
-#endif
-    }
-#endif
-}
-
 /*
  * Generic handler for signals passed from parent -> child.
  * The recvsig[] array is checked in the main event loop.
  */
-static void
+void
 handler(int s)
 {
     recvsig[s] = TRUE;
 }
-
-/*
- * Handler for SIGWINCH in parent.
- */
-static void
-sigwinch(int s)
-{
-    int serrno = errno;
-
-    sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
-    errno = serrno;
-}
-
-/*
- * Only close the fd if it is not /dev/tty or std{in,out,err}.
- * Return value is the same as send(2).
- */
-static int
-safe_close(int fd)
-{
-    /* Avoid closing /dev/tty or std{in,out,err}. */
-    if (fd < 3 || fd == io_fds[SFD_USERTTY]) {
-       errno = EINVAL;
-       return -1;
-    }
-    return close(fd);
-}
diff --git a/src/exec_pty.c b/src/exec_pty.c
new file mode 100644 (file)
index 0000000..857cbae
--- /dev/null
@@ -0,0 +1,1607 @@
+/*
+ * Copyright (c) 2009-2010 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#ifdef HAVE_TERMIOS_H
+# include <termios.h>
+#else
+# include <termio.h>
+#endif /* HAVE_TERMIOS_H */
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif /* HAVE_SYS_SELECT_H */
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+/* XXX - move to compat.h */
+#if !defined(NSIG)
+# if defined(_NSIG)
+#  define NSIG _NSIG
+# elif defined(__NSIG)
+#  define NSIG __NSIG
+# else
+#  define NSIG 64
+# endif
+#endif
+
+#include "sudo.h" /* XXX? */
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+#define SFD_STDIN      0
+#define SFD_STDOUT     1
+#define SFD_STDERR     2
+#define SFD_MASTER     3
+#define SFD_SLAVE      4
+#define SFD_USERTTY    5
+
+#define TERM_COOKED    0
+#define TERM_RAW       1
+
+/* Compatibility with older tty systems. */
+#if !defined(TIOCGSIZE) && defined(TIOCGWINSZ)
+# define TIOCGSIZE     TIOCGWINSZ
+# define TIOCSSIZE     TIOCSWINSZ
+# define ttysize       winsize
+# define ts_cols       ws_col
+#endif
+
+struct io_buffer {
+    struct io_buffer *next;
+    int len; /* buffer length (how much produced) */
+    int off; /* write position (how much already consumed) */
+    int rfd;  /* reader (producer) */
+    int wfd; /* writer (consumer) */
+    int (*action)(const char *buf, unsigned int len);
+    char buf[16 * 1024];
+};
+
+/* shared with pty.c */  
+extern sig_atomic_t recvsig[NSIG];
+extern void handler(int s);
+
+static char slavename[PATH_MAX];
+static int foreground;
+static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
+static int pipeline = FALSE;
+static int tty_initialized;
+static int ttymode = TERM_COOKED;
+static pid_t ppgrp, child;
+static sigset_t ttyblock;
+static struct io_buffer *iobufs;
+
+static void flush_output(void);
+static int exec_monitor(struct command_details *details, char *argv[],
+    char *envp[], int, int);
+static void exec_pty(struct command_details *detail, char *argv[],
+    char *envp[], int);
+static void sigwinch(int s);
+static void sync_ttysize(int src, int dst);
+static void deliver_signal(pid_t pid, int signo);
+static int safe_close(int fd);
+
+extern struct user_details user_details; /* XXX need tty name for SELinux */
+
+/*
+ * Cleanup hook for error()/errorx()
+ */
+void
+cleanup(int gotsignal)
+{
+    if (!tq_empty(&io_plugins))
+       term_restore(io_fds[SFD_USERTTY], 0);
+}
+
+/*
+ * Allocate a pty if /dev/tty is a tty.
+ * Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
+ * and slavename globals.
+ */
+void
+pty_setup(uid_t uid)
+{
+    io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
+    if (io_fds[SFD_USERTTY] != -1) {
+       if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE],
+           slavename, sizeof(slavename), uid))
+           error(1, "Can't get pty");
+    }
+}
+
+/* Call I/O plugin tty input log method. */
+static int
+log_ttyin(const char *buf, unsigned int n)
+{
+    struct plugin_container *plugin;
+    sigset_t omask;
+    int rval = TRUE;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       if (plugin->u.io->log_ttyin) {
+           if (!plugin->u.io->log_ttyin(buf, n)) {
+               rval = FALSE;
+               break;
+           }
+       }
+    }
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return rval;
+}
+
+/* Call I/O plugin stdin log method. */
+static int
+log_stdin(const char *buf, unsigned int n)
+{
+    struct plugin_container *plugin;
+    sigset_t omask;
+    int rval = TRUE;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       if (plugin->u.io->log_stdin) {
+           if (!plugin->u.io->log_stdin(buf, n)) {
+               rval = FALSE;
+               break;
+           }
+       }
+    }
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return rval;
+}
+
+/* Call I/O plugin tty output log method. */
+static int
+log_ttyout(const char *buf, unsigned int n)
+{
+    struct plugin_container *plugin;
+    sigset_t omask;
+    int rval = TRUE;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       if (plugin->u.io->log_ttyout) {
+           if (!plugin->u.io->log_ttyout(buf, n)) {
+               rval = FALSE;
+               break;
+           }
+       }
+    }
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return rval;
+}
+
+/* Call I/O plugin stdout log method. */
+static int
+log_stdout(const char *buf, unsigned int n)
+{
+    struct plugin_container *plugin;
+    sigset_t omask;
+    int rval = TRUE;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       if (plugin->u.io->log_stdout) {
+           if (!plugin->u.io->log_stdout(buf, n)) {
+               rval = FALSE;
+               break;
+           }
+       }
+    }
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return rval;
+}
+
+/* Call I/O plugin stderr log method. */
+static int
+log_stderr(const char *buf, unsigned int n)
+{
+    struct plugin_container *plugin;
+    sigset_t omask;
+    int rval = TRUE;
+
+    sigprocmask(SIG_BLOCK, &ttyblock, &omask);
+
+    tq_foreach_fwd(&io_plugins, plugin) {
+       if (plugin->u.io->log_stderr) {
+           if (!plugin->u.io->log_stderr(buf, n)) {
+               rval = FALSE;
+               break;
+           }
+       }
+    }
+
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return rval;
+}
+
+/*
+ * Check whether we are running in the foregroup.
+ * Updates the foreground global and does lazy init of the
+ * the pty slave as needed.
+ */
+static void
+check_foreground(void)
+{
+    if (io_fds[SFD_USERTTY] != -1) {
+       foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+       if (foreground && !tty_initialized) {
+           if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+               tty_initialized = 1;
+               sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+           }
+       }
+    }
+}
+
+/*
+ * Suspend sudo if the underlying command is suspended.
+ * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
+ */
+int
+suspend_parent(int signo)
+{
+    sigaction_t sa, osa;
+    int n, oldmode = ttymode, rval = 0;
+
+    switch (signo) {
+    case SIGTTOU:
+    case SIGTTIN:
+       /*
+        * If we are the foreground process, just resume the child.
+        * Otherwise, re-send the signal with the handler disabled.
+        */
+       if (!foreground)
+           check_foreground();
+       if (foreground) {
+           if (ttymode != TERM_RAW) {
+               do {
+                   n = term_raw(io_fds[SFD_USERTTY], 0);
+               } while (!n && errno == EINTR);
+               ttymode = TERM_RAW;
+           }
+           rval = SIGUSR1; /* resume child in foreground */
+           break;
+       }
+       ttymode = TERM_RAW;
+       /* FALLTHROUGH */
+    case SIGSTOP:
+    case SIGTSTP:
+       /* Flush any remaining output before suspending. */
+       flush_output();
+
+       /* Restore original tty mode before suspending. */
+       if (oldmode != TERM_COOKED) {
+           do {
+               n = term_restore(io_fds[SFD_USERTTY], 0);
+           } while (!n && errno == EINTR);
+       }
+
+       /* Suspend self and continue child when we resume. */
+       sa.sa_handler = SIG_DFL;
+       sigaction(signo, &sa, &osa);
+       sudo_debug(8, "kill parent %d", signo);
+       killpg(ppgrp, signo);
+
+       /* Check foreground/background status on resume. */
+       check_foreground();
+
+       /*
+        * Only modify term if we are foreground process and either
+        * the old tty mode was not cooked or child got SIGTT{IN,OU}
+        */
+       sudo_debug(8, "parent is in %sground, ttymode %d -> %d",
+           foreground ? "fore" : "back", oldmode, ttymode);
+
+       if (ttymode != TERM_COOKED) {
+           if (foreground) {
+               /* Set raw mode. */
+               do {
+                   n = term_raw(io_fds[SFD_USERTTY], 0);
+               } while (!n && errno == EINTR);
+           } else {
+               /* Background process, no access to tty. */
+               ttymode = TERM_COOKED;
+           }
+       }
+
+       sigaction(signo, &osa, NULL);
+       rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2;
+       break;
+    }
+
+    return(rval);
+}
+
+/*
+ * Kill child with increasing urgency.
+ */
+void
+terminate_child(pid_t pid, int use_pgrp)
+{
+    /*
+     * Note that SIGCHLD will interrupt the sleep()
+     */
+    if (use_pgrp) {
+       killpg(pid, SIGHUP);
+       killpg(pid, SIGTERM);
+       sleep(2);
+       killpg(pid, SIGKILL);
+    } else {
+       kill(pid, SIGHUP);
+       kill(pid, SIGTERM);
+       sleep(2);
+       kill(pid, SIGKILL);
+    }
+}
+
+static struct io_buffer *
+io_buf_new(int rfd, int wfd, int (*action)(const char *, unsigned int),
+    struct io_buffer *head)
+{
+    struct io_buffer *iob;
+
+    iob = emalloc(sizeof(*iob));
+    zero_bytes(iob, sizeof(*iob));
+    iob->rfd = rfd;
+    iob->wfd = wfd;
+    iob->action = action;
+    iob->next = head;
+    return iob;
+}
+
+/*
+ * Read/write iobufs depending on fdsr and fdsw.
+ * Returns the number of errors.
+ */
+int
+perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat)
+{
+    struct io_buffer *iob;
+    int n, errors = 0;
+
+    for (iob = iobufs; iob; iob = iob->next) {
+       if (iob->rfd != -1 && FD_ISSET(iob->rfd, fdsr)) {
+           do {
+               n = read(iob->rfd, iob->buf + iob->len,
+                   sizeof(iob->buf) - iob->len);
+           } while (n == -1 && errno == EINTR);
+           if (n == -1) {
+               if (errno != EAGAIN)
+                   break;
+           } else if (n == 0) {
+               /* got EOF */
+               safe_close(iob->rfd);
+               iob->rfd = -1;
+           } else {
+               if (!iob->action(iob->buf + iob->len, n))
+                   terminate_child(child, TRUE);
+               iob->len += n;
+           }
+       }
+       if (iob->wfd != -1 && FD_ISSET(iob->wfd, fdsw)) {
+           do {
+               n = write(iob->wfd, iob->buf + iob->off,
+                   iob->len - iob->off);
+           } while (n == -1 && errno == EINTR);
+           if (n == -1) {
+               if (errno == EPIPE) {
+                   /* other end of pipe closed */
+                   if (iob->rfd != -1) {
+                       safe_close(iob->rfd);
+                       iob->rfd = -1;
+                   }
+                   safe_close(iob->wfd);
+                   iob->wfd = -1;
+                   continue;
+               }
+               if (errno != EAGAIN)
+                   errors++;
+           } else {
+               iob->off += n;
+           }
+       }
+    }
+    if (errors && cstat != NULL) {
+       cstat->type = CMD_ERRNO;
+       cstat->val = errno;
+    }
+    return errors;
+}
+
+/*
+ * Fork a monitor process which runs the actual command as its own child
+ * process with std{in,out,err} hooked up to the pty or pipes as appropriate.
+ * Returns the child pid.
+ */
+int
+fork_pty(struct command_details *details, char *argv[], char *envp[],
+    int sv[], int rbac_enabled, int *maxfd)
+{
+    struct command_status cstat;
+    struct io_buffer *iob;
+    int io_pipe[3][2], n;
+    sigaction_t sa;
+        
+    ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+     
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    if (io_fds[SFD_USERTTY] != -1) {
+       sa.sa_flags = SA_RESTART;
+       sa.sa_handler = sigwinch;
+       sigaction(SIGWINCH, &sa, NULL);
+    }
+
+    /* So we can block tty-generated signals */
+    sigemptyset(&ttyblock);
+    sigaddset(&ttyblock, SIGINT);
+    sigaddset(&ttyblock, SIGQUIT);
+    sigaddset(&ttyblock, SIGTSTP);
+    sigaddset(&ttyblock, SIGTTIN);
+    sigaddset(&ttyblock, SIGTTOU);
+
+    /*
+     * Setup stdin/stdout/stderr for child, to be duped after forking.
+     */
+    io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
+    io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
+    io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
+
+    /* Copy /dev/tty -> pty master */
+    if (io_fds[SFD_USERTTY] != -1) {
+       iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+           log_ttyin, iobufs);
+
+       /* Copy pty master -> /dev/tty */
+       iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
+           log_ttyout, iobufs);
+
+       /* Are we the foreground process? */
+       foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+    }
+
+    /*
+     * If either stdin, stdout or stderr is not a tty we use a pipe
+     * to interpose ourselves instead of duping the pty fd.
+     */
+    memset(io_pipe, 0, sizeof(io_pipe));
+    if (!isatty(STDIN_FILENO)) {
+       pipeline = TRUE;
+       if (pipe(io_pipe[STDIN_FILENO]) != 0)
+           error(1, "unable to create pipe");
+       iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
+           log_stdin, iobufs);
+       io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
+    }
+    if (!isatty(STDOUT_FILENO)) {
+       pipeline = TRUE;
+       if (pipe(io_pipe[STDOUT_FILENO]) != 0)
+           error(1, "unable to create pipe");
+       iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
+           log_stdout, iobufs);
+       io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
+    }
+    if (!isatty(STDERR_FILENO)) {
+       if (pipe(io_pipe[STDERR_FILENO]) != 0)
+           error(1, "unable to create pipe");
+       iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
+           log_stderr, iobufs);
+       io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
+    }
+
+    /* Job control signals to relay from parent to child. */
+    sa.sa_flags = 0; /* do not restart syscalls */
+    sa.sa_handler = handler;
+    sigaction(SIGTSTP, &sa, NULL);
+#if 0 /* XXX - add these? */
+    sigaction(SIGTTIN, &sa, NULL);
+    sigaction(SIGTTOU, &sa, NULL);
+#endif
+
+    if (foreground) {
+       /* Copy terminal attrs from user tty -> pty slave. */
+       if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+           tty_initialized = 1;
+           sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+       }
+
+       /* Start out in raw mode if we are not part of a pipeline. */
+       if (!pipeline) {
+           ttymode = TERM_RAW;
+           do {
+               n = term_raw(io_fds[SFD_USERTTY], 0);
+           } while (!n && errno == EINTR);
+           if (!n)
+               error(1, "Can't set terminal to raw mode");
+       }
+    }
+
+    child = fork();
+    switch (child) {
+    case -1:
+       error(1, "fork");
+       break;
+    case 0:
+       /* child */
+       close(sv[0]);
+       fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+#ifdef HAVE_SELINUX
+        if (rbac_enabled)
+           selinux_setup(user_role, user_type, slavename, io_fds[SFD_SLAVE]);
+#endif
+       if (exec_setup(details) == TRUE) {
+           /* Close the other end of the stdin/stdout/stderr pipes and exec. */
+           if (io_pipe[STDIN_FILENO][1])
+               close(io_pipe[STDIN_FILENO][1]);
+           if (io_pipe[STDOUT_FILENO][0])
+               close(io_pipe[STDOUT_FILENO][0]);
+           if (io_pipe[STDERR_FILENO][0])
+               close(io_pipe[STDERR_FILENO][0]);
+           exec_monitor(details, argv, envp, sv[1], rbac_enabled);
+       }
+       cstat.type = CMD_ERRNO;
+       cstat.val = errno;
+       send(sv[1], &cstat, sizeof(cstat), 0);
+       _exit(1);
+    }
+
+    /* Close the other end of the stdin/stdout/stderr pipes. */
+    if (io_pipe[STDIN_FILENO][0])
+       close(io_pipe[STDIN_FILENO][0]);
+    if (io_pipe[STDOUT_FILENO][1])
+       close(io_pipe[STDOUT_FILENO][1]);
+    if (io_pipe[STDERR_FILENO][1]) 
+       close(io_pipe[STDERR_FILENO][1]);
+
+    for (iob = iobufs; iob; iob = iob->next) {
+       /* Determine maxfd */
+       if (iob->rfd > *maxfd)
+           *maxfd = iob->rfd;
+       if (iob->wfd > *maxfd)
+           *maxfd = iob->wfd;
+
+       /* Set non-blocking mode. */
+       n = fcntl(iob->rfd, F_GETFL, 0);
+       if (n != -1 && !ISSET(n, O_NONBLOCK))
+           (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK);
+       n = fcntl(iob->wfd, F_GETFL, 0);
+       if (n != -1 && !ISSET(n, O_NONBLOCK))
+           (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK);
+    }
+
+    return child;
+}
+
+void
+pty_close(struct command_status *cstat)
+{
+    int n;
+
+    /* Flush any remaining output (the plugin already got it) */
+    if (io_fds[SFD_USERTTY] != -1) {
+       n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0);
+       if (n != -1 && ISSET(n, O_NONBLOCK)) {
+           CLR(n, O_NONBLOCK);
+           (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n);
+       }
+    }
+    flush_output();
+
+    if (io_fds[SFD_USERTTY] != -1) {
+       do {
+           n = term_restore(io_fds[SFD_USERTTY], 0);
+       } while (!n && errno == EINTR);
+    }
+
+    /* If child was signalled, write the reason to stdout like the shell. */
+    if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
+       int signo = WTERMSIG(cstat->val);
+       if (signo && signo != SIGINT && signo != SIGPIPE) {
+           char *reason = strsignal(signo);
+           n = io_fds[SFD_USERTTY] != -1 ?
+               io_fds[SFD_USERTTY] : STDOUT_FILENO;
+           write(n, reason, strlen(reason));
+           if (WCOREDUMP(cstat->val))
+               write(n, " (core dumped)", 14);
+           write(n, "\n", 1);
+       }
+    }
+}
+
+/*
+ * Fill in fdsr and fdsw based on the io buffers list.
+ * Called prior to select().
+ */
+void
+fd_set_iobs(fd_set *fdsr, fd_set *fdsw)
+{
+    struct io_buffer *iob;
+
+    for (iob = iobufs; iob; iob = iob->next) {
+       if (iob->rfd == -1 && iob->wfd == -1)
+           continue;
+       if (iob->off == iob->len) {
+           iob->off = iob->len = 0;
+           /* Forward the EOF from reader to writer. */
+           if (iob->rfd == -1) {
+               safe_close(iob->wfd);
+               iob->wfd = -1;
+           }
+       }
+       /* Don't read/write /dev/tty if we are not in the foreground. */
+       if (iob->rfd != -1 &&
+           (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) {
+           if (iob->len != sizeof(iob->buf))
+               FD_SET(iob->rfd, fdsr);
+       }
+       if (iob->wfd != -1 &&
+           (foreground || iob->wfd != io_fds[SFD_USERTTY])) {
+           if (iob->len > iob->off)
+               FD_SET(iob->wfd, fdsw);
+       }
+    }
+}
+
+#if 0
+/*
+ * This is a little bit tricky due to how POSIX job control works and
+ * we fact that we have two different controlling terminals to deal with.
+ * There are three processes:
+ *  1) parent, which forks a child and does all the I/O passing.
+ *     Handles job control signals send by its child to bridge the
+ *     two sessions (and ttys).
+ *  2) child, creates a new session so it can receive notification of
+ *     tty stop signals (SIGTSTP, SIGTTIN, SIGTTOU).  Waits for the
+ *     command to stop or die and passes back tty stop signals to parent
+ *     so job control works in the user's shell.
+ *  3) grandchild, executes the actual command with the pty slave as its
+ *     controlling tty, belongs to child's session but has its own pgrp.
+ */
+int
+sudo_execve(struct command_details *details, char *argv[], char *envp[],
+    struct command_status *cstat)
+{
+    sigaction_t sa;
+    struct io_buffer *iob, *iobufs = NULL;
+    int n, nready;
+    int io_pipe[3][2], sv[2];
+    fd_set *fdsr, *fdsw;
+    int rbac_enabled = 0;
+    int log_io, maxfd, status;
+
+    cstat->type = CMD_INVALID;
+
+    log_io = !tq_empty(&io_plugins);
+    if (log_io) {
+       sudo_debug(8, "allocate pty for I/O logging");
+       pty_setup(details->euid);
+    }
+
+#ifdef HAVE_SELINUX
+    rbac_enabled = is_selinux_enabled() > 0 && details->selinux_role != NULL;
+    if (rbac_enabled) {
+       /* Must do SELinux setup before changing uid. */
+       selinux_setup(details->selinux_role, details->selinux_type,
+           log_io ? slavename : user_details.tty, io_fds[SFD_SLAVE]);
+    }
+#endif
+
+    ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+
+    /*
+     * We communicate with the child over a bi-directional pipe.
+     * Parent sends signal info to child and child sends back wait status.
+     */
+    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
+       error(1, "cannot create sockets");
+
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+
+    /* Note: HP-UX select() will not be interrupted if SA_RESTART set */
+    sa.sa_flags = 0; /* do not restart syscalls */
+    sa.sa_handler = handler;
+    sigaction(SIGALRM, &sa, NULL);
+    sigaction(SIGCHLD, &sa, NULL);
+    sigaction(SIGHUP, &sa, NULL);
+    sigaction(SIGINT, &sa, NULL);
+    sigaction(SIGPIPE, &sa, NULL);
+    sigaction(SIGQUIT, &sa, NULL);
+    sigaction(SIGTERM, &sa, NULL);
+
+    if (log_io) {
+       if (io_fds[SFD_USERTTY] != -1) {
+           sa.sa_flags = SA_RESTART;
+           sa.sa_handler = sigwinch;
+           sigaction(SIGWINCH, &sa, NULL);
+       }
+
+       /* So we can block tty-generated signals */
+       sigemptyset(&ttyblock);
+       sigaddset(&ttyblock, SIGINT);
+       sigaddset(&ttyblock, SIGQUIT);
+       sigaddset(&ttyblock, SIGTSTP);
+       sigaddset(&ttyblock, SIGTTIN);
+       sigaddset(&ttyblock, SIGTTOU);
+
+       /*
+        * Setup stdin/stdout/stderr for child, to be duped after forking.
+        */
+       io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
+       io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
+       io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
+
+       /* Copy /dev/tty -> pty master */
+       if (io_fds[SFD_USERTTY] != -1) {
+           iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+               log_ttyin, iobufs);
+
+           /* Copy pty master -> /dev/tty */
+           iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
+               log_ttyout, iobufs);
+
+           /* Are we the foreground process? */
+           foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+       }
+
+       /*
+        * If either stdin, stdout or stderr is not a tty we use a pipe
+        * to interpose ourselves instead of duping the pty fd.
+        */
+       memset(io_pipe, 0, sizeof(io_pipe));
+       if (!isatty(STDIN_FILENO)) {
+           pipeline = TRUE;
+           if (pipe(io_pipe[STDIN_FILENO]) != 0)
+               error(1, "unable to create pipe");
+           iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
+               log_stdin, iobufs);
+           io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
+       }
+       if (!isatty(STDOUT_FILENO)) {
+           pipeline = TRUE;
+           if (pipe(io_pipe[STDOUT_FILENO]) != 0)
+               error(1, "unable to create pipe");
+           iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
+               log_stdout, iobufs);
+           io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
+       }
+       if (!isatty(STDERR_FILENO)) {
+           if (pipe(io_pipe[STDERR_FILENO]) != 0)
+               error(1, "unable to create pipe");
+           iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
+               log_stderr, iobufs);
+           io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
+       }
+
+       /* Job control signals to relay from parent to child. */
+       sa.sa_flags = 0; /* do not restart syscalls */
+       sa.sa_handler = handler;
+       sigaction(SIGTSTP, &sa, NULL);
+#if 0 /* XXX - add these? */
+       sigaction(SIGTTIN, &sa, NULL);
+       sigaction(SIGTTOU, &sa, NULL);
+#endif
+
+       if (foreground) {
+           /* Copy terminal attrs from user tty -> pty slave. */
+           if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+               tty_initialized = 1;
+               sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+           }
+
+           /* Start out in raw mode if we are not part of a pipeline. */
+           if (!pipeline) {
+               ttymode = TERM_RAW;
+               do {
+                   n = term_raw(io_fds[SFD_USERTTY], 0);
+               } while (!n && errno == EINTR);
+               if (!n)
+                   error(1, "Can't set terminal to raw mode");
+           }
+       }
+    }
+
+    /*
+     * Child will run the command in the pty, parent will pass data
+     * to and from pty.
+     */
+    child = fork();
+    switch (child) {
+    case -1:
+       error(1, "fork");
+       break;
+    case 0:
+       /* child */
+       close(sv[0]);
+       fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+       if (exec_setup(details) == TRUE) {
+           /* headed for execve() */
+           if (log_io) {
+               /* Close the other end of the stdin/stdout/stderr pipes. */
+               if (io_pipe[STDIN_FILENO][1])
+                   close(io_pipe[STDIN_FILENO][1]);
+               if (io_pipe[STDOUT_FILENO][0])
+                   close(io_pipe[STDOUT_FILENO][0]);
+               if (io_pipe[STDERR_FILENO][0])
+                   close(io_pipe[STDERR_FILENO][0]);
+               exec_monitor(details, argv, envp, sv[1], rbac_enabled);
+           } else {
+               if (details->closefrom >= 0)
+                   closefrom(details->closefrom);
+#ifdef HAVE_SELINUX
+               if (rbac_enabled)
+                   selinux_execve(details->command, argv, envp);
+               else
+#endif
+                   my_execve(details->command, argv, envp);
+           }
+       }
+       cstat->type = CMD_ERRNO;
+       cstat->val = errno;
+       send(sv[1], cstat, sizeof(*cstat), 0);
+       _exit(1);
+    }
+    close(sv[1]);
+
+    /* Set command timeout if specified. */
+    if (ISSET(details->flags, CD_SET_TIMEOUT))
+       alarm(details->timeout);
+
+    /* Max fd we will be selecting on. */
+    maxfd = sv[0];
+
+    if (log_io) {
+       /* Close the other end of the stdin/stdout/stderr pipes. */
+       if (io_pipe[STDIN_FILENO][0])
+           close(io_pipe[STDIN_FILENO][0]);
+       if (io_pipe[STDOUT_FILENO][1])
+           close(io_pipe[STDOUT_FILENO][1]);
+       if (io_pipe[STDERR_FILENO][1])
+           close(io_pipe[STDERR_FILENO][1]);
+
+       for (iob = iobufs; iob; iob = iob->next) {
+           /* Determine maxfd */
+           if (iob->rfd > maxfd)
+               maxfd = iob->rfd;
+           if (iob->wfd > maxfd)
+               maxfd = iob->wfd;
+
+           /* Set non-blocking mode. */
+           n = fcntl(iob->rfd, F_GETFL, 0);
+           if (n != -1 && !ISSET(n, O_NONBLOCK))
+               (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK);
+           n = fcntl(iob->wfd, F_GETFL, 0);
+           if (n != -1 && !ISSET(n, O_NONBLOCK))
+               (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK);
+       }
+    }
+
+    /*
+     * In the event loop we pass input from user tty to master
+     * and pass output from master to stdout and IO plugin.
+     */
+    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    for (;;) {
+       if (recvsig[SIGCHLD]) {
+           pid_t pid;
+
+           /*
+            * If logging I/O, child is the intermediate process,
+            * otherwise it is the command itself.
+            */
+           recvsig[SIGCHLD] = FALSE;
+           do {
+               pid = waitpid(child, &status, WNOHANG);
+           } while (pid == -1 && errno == EINTR);
+           if (pid == child) {
+               /* If not logging I/O and child has exited we are done. */
+               if (!log_io) {
+                   cstat->type = CMD_WSTATUS;
+                   cstat->val = status;
+                   return 0;
+               }
+           }
+       }
+
+       zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+       zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+
+       FD_SET(sv[0], fdsr);
+       for (iob = iobufs; iob; iob = iob->next) {
+           if (iob->rfd == -1 && iob->wfd == -1)
+               continue;
+           if (iob->off == iob->len) {
+               iob->off = iob->len = 0;
+               /* Forward the EOF from reader to writer. */
+               if (iob->rfd == -1) {
+                   safe_close(iob->wfd);
+                   iob->wfd = -1;
+               }
+           }
+           /* Don't read/write /dev/tty if we are not in the foreground. */
+           if (iob->rfd != -1 &&
+               (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) {
+               if (iob->len != sizeof(iob->buf))
+                   FD_SET(iob->rfd, fdsr);
+           }
+           if (iob->wfd != -1 &&
+               (foreground || iob->wfd != io_fds[SFD_USERTTY])) {
+               if (iob->len > iob->off)
+                   FD_SET(iob->wfd, fdsw);
+           }
+       }
+       for (n = 0; n < NSIG; n++) {
+           if (recvsig[n] && n != SIGCHLD) {
+               if (log_io) {
+                   FD_SET(sv[0], fdsw);
+                   break;
+               } else {
+                   /* nothing listening on sv[0], send directly */
+                   if (n == SIGALRM) {
+                       terminate_child(child, FALSE);
+                   } else {
+                       kill(child, n);
+                   }
+               }
+           }
+       }
+
+       if (recvsig[SIGCHLD])
+           continue;
+       nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL);
+       if (nready == -1) {
+           if (errno == EINTR)
+               continue;
+           error(1, "select failed");
+       }
+       if (FD_ISSET(sv[0], fdsr)) {
+           /* read child status */
+           n = recv(sv[0], cstat, sizeof(*cstat), 0);
+           if (n == -1) {
+               if (errno == EINTR)
+                   continue;
+               /*
+                * If not logging I/O we will receive ECONNRESET when
+                * the command is executed.  It is safe to ignore this.
+                */
+               if (log_io && errno != EAGAIN) {
+                   cstat->type = CMD_ERRNO;
+                   cstat->val = errno;
+                   break;
+               }
+           }
+           if (cstat->type == CMD_WSTATUS) {
+               if (WIFSTOPPED(cstat->val)) {
+                   /* Suspend parent and tell child how to resume on return. */
+                   sudo_debug(8, "child stopped, suspending parent");
+                   n = suspend_parent(WSTOPSIG(cstat->val));
+                   recvsig[n] = TRUE;
+                   continue;
+               } else {
+                   /* Child exited or was killed, either way we are done. */
+                   break;
+               }
+           } else if (cstat->type == CMD_ERRNO) {
+               /* Child was unable to execute command or broken pipe. */
+               break;
+           }
+       }
+
+       if (FD_ISSET(sv[0], fdsw)) {
+           for (n = 0; n < NSIG; n++) {
+               if (!recvsig[n])
+                   continue;
+               recvsig[n] = FALSE;
+               sudo_debug(9, "sending signal %d to child over backchannel", n);
+               cstat->type = CMD_SIGNO;
+               cstat->val = n;
+               do {
+                   n = send(sv[0], cstat, sizeof(*cstat), 0);
+               } while (n == -1 && errno == EINTR);
+               if (n != sizeof(*cstat)) {
+                   recvsig[n] = TRUE;
+                   break;
+               }
+           }
+       }
+       if (perform_io(fdsr, fdsw, cstat) != 0)
+           break;
+    }
+
+    if (log_io) {
+       /* Flush any remaining output (the plugin already got it) */
+       if (io_fds[SFD_USERTTY] != -1) {
+           n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0);
+           if (n != -1 && ISSET(n, O_NONBLOCK)) {
+               CLR(n, O_NONBLOCK);
+               (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n);
+           }
+       }
+       flush_output();
+
+       if (io_fds[SFD_USERTTY] != -1) {
+           do {
+               n = term_restore(io_fds[SFD_USERTTY], 0);
+           } while (!n && errno == EINTR);
+       }
+
+       if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
+           int signo = WTERMSIG(cstat->val);
+           if (signo && signo != SIGINT && signo != SIGPIPE) {
+               char *reason = strsignal(signo);
+               n = io_fds[SFD_USERTTY] != -1 ?
+                   io_fds[SFD_USERTTY] : STDOUT_FILENO;
+               write(n, reason, strlen(reason));
+               if (WCOREDUMP(cstat->val))
+                   write(n, " (core dumped)", 14);
+               write(n, "\n", 1);
+           }
+       }
+    }
+
+#ifdef HAVE_SELINUX
+    if (rbac_enabled) {
+       /* This is probably not needed in log_io mode. */
+       if (selinux_restore_tty() != 0)
+           warningx("unable to restore tty label");
+    }
+#endif
+
+    efree(fdsr);
+    efree(fdsw);
+    while ((iob = iobufs) != NULL) {
+       iobufs = iobufs->next;
+       efree(iob);
+    }
+
+    return cstat->type == CMD_ERRNO ? -1 : 0;
+}
+#endif
+
+static void
+deliver_signal(pid_t pid, int signo)
+{
+    int status;
+
+    /* Handle signal from parent. */
+    sudo_debug(8, "signal %d from parent", signo);
+    switch (signo) {
+    case SIGKILL:
+       _exit(1); /* XXX */
+       /* NOTREACHED */
+    case SIGPIPE:
+    case SIGHUP:
+    case SIGTERM:
+    case SIGINT:
+    case SIGQUIT:
+    case SIGTSTP:
+       /* relay signal to child */
+       killpg(pid, signo);
+       break;
+    case SIGALRM:
+       terminate_child(pid, TRUE);
+       break;
+    case SIGUSR1:
+       /* foreground process, grant it controlling tty. */
+       do {
+           status = tcsetpgrp(io_fds[SFD_SLAVE], pid);
+       } while (status == -1 && errno == EINTR);
+       killpg(pid, SIGCONT);
+       break;
+    case SIGUSR2:
+       /* background process, I take controlling tty. */
+       do {
+           status = tcsetpgrp(io_fds[SFD_SLAVE], getpid());
+       } while (status == -1 && errno == EINTR);
+       killpg(pid, SIGCONT);
+       break;
+    default:
+       warningx("unexpected signal from child: %d", signo);
+       break;
+    }
+}
+
+/*
+ * Send status to parent over socketpair.
+ * Return value is the same as send(2).
+ */
+static int
+send_status(int fd, struct command_status *cstat)
+{
+    int n = -1;
+
+    if (cstat->type != CMD_INVALID) {
+       do {
+           n = send(fd, cstat, sizeof(*cstat), 0);
+       } while (n == -1 && errno == EINTR);
+       if (n != sizeof(*cstat)) {
+           sudo_debug(8, "unable to send status to parent: %s", strerror(errno));
+       } else {
+           sudo_debug(8, "sent status to parent");
+       }
+       cstat->type = CMD_INVALID; /* prevent re-sending */
+    }
+    return n;
+}
+
+/*
+ * Wait for child status after receiving SIGCHLD.
+ * If the child was stopped, the status is send back to the parent.
+ * Otherwise, cstat is filled in but not sent.
+ * Returns TRUE if child is still alive, else FALSE.
+ */
+static int
+handle_sigchld(int backchannel, struct command_status *cstat)
+{
+    int status, alive = TRUE;
+    pid_t pid;
+
+    /* read child status */
+    do {
+       pid = waitpid(child, &status, WUNTRACED|WNOHANG);
+    } while (pid == -1 && errno == EINTR);
+    if (pid == child) {
+       if (cstat->type != CMD_ERRNO) {
+           cstat->type = CMD_WSTATUS;
+           cstat->val = status;
+           if (WIFSTOPPED(status)) {
+               sudo_debug(8, "command stopped, signal %d",
+                   WSTOPSIG(status));
+               if (send_status(backchannel, cstat) == -1)
+                   return alive; /* XXX */
+           } else if (WIFSIGNALED(status)) {
+               sudo_debug(8, "command killed, signal %d",
+                   WTERMSIG(status));
+           } else {
+               sudo_debug(8, "command exited: %d",
+                   WEXITSTATUS(status));
+           }
+       }
+       if (!WIFSTOPPED(status))
+           alive = FALSE;
+    }
+    return alive;
+}
+
+/*
+ * Monitor process that creates a new session with the controlling tty,
+ * resets signal handlers and forks a child to call exec_pty().
+ * Waits for status changes from the command and relays them to the
+ * parent and relays signals from the parent to the command.
+ * Returns an error if fork(2) fails, else calls _exit(2).
+ */
+int
+exec_monitor(struct command_details *details, char *argv[], char *envp[],
+    int backchannel, int rbac)
+{
+    struct command_status cstat;
+    struct timeval tv;
+    fd_set *fdsr;
+    sigaction_t sa;
+    int errpipe[2], maxfd, n, status;
+    int alive = TRUE;
+
+    /* Close unused fds. */
+    if (io_fds[SFD_MASTER] != -1)
+       close(io_fds[SFD_MASTER]);
+    if (io_fds[SFD_USERTTY] != -1)
+       close(io_fds[SFD_USERTTY]);
+
+    /* Reset SIGWINCH and SIGALRM. */
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = SIG_DFL;
+    sigaction(SIGWINCH, &sa, NULL);
+    sigaction(SIGALRM, &sa, NULL);
+
+    /* Ignore any SIGTTIN or SIGTTOU we get. */
+    sa.sa_handler = SIG_IGN;
+    sigaction(SIGTTIN, &sa, NULL);
+    sigaction(SIGTTOU, &sa, NULL);
+
+    /* Note: HP-UX select() will not be interrupted if SA_RESTART set */
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    sigaction(SIGCHLD, &sa, NULL);
+
+    /*
+     * Start a new session with the parent as the session leader
+     * and the slave pty as the controlling terminal.
+     * This allows us to be notified when the child has been suspended.
+     */
+#ifdef HAVE_SETSID
+    if (setsid() == -1) {
+       warning("setsid");
+       goto bad;
+    }
+#else
+# ifdef TIOCNOTTY
+    n = open(_PATH_TTY, O_RDWR|O_NOCTTY);
+    if (n >= 0) {
+       /* Disconnect from old controlling tty. */
+       if (ioctl(n, TIOCNOTTY, NULL) == -1)
+           warning("cannot disconnect controlling tty");
+       close(n);
+    }
+# endif
+    setpgrp(0, 0);
+#endif
+    if (io_fds[SFD_SLAVE] != -1) {
+#ifdef TIOCSCTTY
+       if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
+           error(1, "unable to set controlling tty");
+#else
+       /* Set controlling tty by reopening slave. */
+       if ((n = open(slavename, O_RDWR)) >= 0)
+           close(n);
+#endif
+    }
+
+    /*
+     * If stdin/stdout is not a tty, start command in the background
+     * since it might be part of a pipeline that reads from /dev/tty.
+     * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
+     * when it needs access to the controlling tty.
+     */
+    if (pipeline)
+       foreground = 0;
+
+    /* Start command and wait for it to stop or exit */
+    if (pipe(errpipe) == -1)
+       error(1, "unable to create pipe");
+    child = fork();
+    if (child == -1) {
+       warning("Can't fork");
+       goto bad;
+    }
+    if (child == 0) {
+       /* We pass errno back to our parent via pipe on exec failure. */
+       close(backchannel);
+       close(errpipe[0]);
+       fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
+
+       /* setup tty and exec command */
+       exec_pty(details, argv, envp, rbac);
+       cstat.type = CMD_ERRNO;
+       cstat.val = errno;
+       write(errpipe[1], &cstat, sizeof(cstat));
+       _exit(1);
+    }
+    close(errpipe[1]);
+
+    /* If any of stdin/stdout/stderr are pipes, close them in parent. */
+    if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDIN]);
+    if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDOUT]);
+    if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDERR]);
+
+    /*
+     * Put child in its own process group.  If we are starting the command
+     * in the foreground, assign its pgrp to the tty.
+     */
+    setpgid(child, child);
+    if (foreground) {
+       do {
+           status = tcsetpgrp(io_fds[SFD_SLAVE], child);
+       } while (status == -1 && errno == EINTR);
+    }
+
+    /* Wait for errno on pipe, signal on backchannel or for SIGCHLD */
+    maxfd = MAX(errpipe[0], backchannel);
+    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+    zero_bytes(&cstat, sizeof(cstat));
+    tv.tv_sec = 0;
+    tv.tv_usec = 0;
+    for (;;) {
+       /* Read child status. */
+       if (recvsig[SIGCHLD]) {
+           recvsig[SIGCHLD] = FALSE;
+           alive = handle_sigchld(backchannel, &cstat);
+       }
+
+       /* Check for signal on backchannel or errno on errpipe. */
+       FD_SET(backchannel, fdsr);
+       if (errpipe[0] != -1)
+           FD_SET(errpipe[0], fdsr);
+       maxfd = MAX(errpipe[0], backchannel);
+
+       if (recvsig[SIGCHLD])
+           continue;
+       /* If command exited we just poll, there may be data on errpipe. */
+       n = select(maxfd + 1, fdsr, NULL, NULL, alive ? NULL : &tv);
+       if (n <= 0) {
+           if (n == 0)
+               goto done;
+           if (errno == EINTR)
+               continue;
+           error(1, "select failed");
+       }
+
+       if (errpipe[0] != -1 && FD_ISSET(errpipe[0], fdsr)) {
+           /* read errno or EOF from command pipe */
+           n = read(errpipe[0], &cstat, sizeof(cstat));
+           if (n == -1) {
+               if (errno == EINTR)
+                   continue;
+               warning("error reading from pipe");
+               goto done;
+           }
+           /* Got errno or EOF, either way we are done with errpipe. */
+           FD_CLR(errpipe[0], fdsr);
+           close(errpipe[0]);
+           errpipe[0] = -1;
+       }
+       if (FD_ISSET(backchannel, fdsr)) {
+           struct command_status cstmp;
+
+           /* read command from backchannel, should be a signal */
+           n = recv(backchannel, &cstmp, sizeof(cstmp), 0);
+           if (n == -1) {
+               if (errno == EINTR)
+                   continue;
+               warning("error reading from socketpair");
+               goto done;
+           }
+           if (cstmp.type != CMD_SIGNO) {
+               warningx("unexpected reply type on backchannel: %d", cstmp.type);
+               continue;
+           }
+           deliver_signal(child, cstmp.val);
+       }
+    }
+
+done:
+    if (alive) {
+       /* XXX An error occurred, should send an error back. */
+       kill(child, SIGKILL);
+    } else {
+       /* Send parent status. */
+       send_status(backchannel, &cstat);
+    }
+    _exit(1);
+
+bad:
+    return errno;
+}
+
+/*
+ * Flush any output buffered in iobufs or readable from the fds.
+ * Does not read from /dev/tty.
+ */
+static void
+flush_output(void)
+{
+    struct io_buffer *iob;
+    struct timeval tv;
+    fd_set *fdsr, *fdsw;
+    int nready, nwriters, maxfd = -1;
+
+    /* Determine maxfd */
+    for (iob = iobufs; iob; iob = iob->next) {
+       if (iob->rfd > maxfd)
+           maxfd = iob->rfd;
+       if (iob->wfd > maxfd)
+           maxfd = iob->wfd;
+    }
+    if (maxfd == -1)
+       return;
+
+    fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+    for (;;) {
+       zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+       zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+
+       nwriters = 0;
+       for (iob = iobufs; iob; iob = iob->next) {
+           /* Don't read from /dev/tty while flushing. */
+           if (io_fds[SFD_USERTTY] != -1 && iob->rfd == io_fds[SFD_USERTTY])
+               continue;
+           if (iob->rfd == -1 && iob->wfd == -1)
+               continue;
+           if (iob->off == iob->len) {
+               iob->off = iob->len = 0;
+               /* Forward the EOF from reader to writer. */
+               if (iob->rfd == -1) {
+                   safe_close(iob->wfd);
+                   iob->wfd = -1;
+               }
+           }
+           if (iob->rfd != -1) {
+               if (iob->len != sizeof(iob->buf))
+                   FD_SET(iob->rfd, fdsr);
+           }
+           if (iob->wfd != -1) {
+               if (iob->len > iob->off) {
+                   nwriters++;
+                   FD_SET(iob->wfd, fdsw);
+               }
+           }
+       }
+
+       /* Don't sleep in select if there are no buffers that need writing. */
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       nready = select(maxfd + 1, fdsr, fdsw, NULL, nwriters ? NULL : &tv);
+       if (nready <= 0) {
+           if (nready == 0)
+               break; /* all I/O flushed */
+           if (errno == EINTR)
+               continue;
+           error(1, "select failed");
+       }
+       if (perform_io(fdsr, fdsw, NULL) != 0)
+           break;
+    }
+    efree(fdsr);
+    efree(fdsw);
+}
+
+/*
+ * Sets up std{in,out,err} and executes the actual command.
+ * Returns only if execve() fails.
+ */
+static void
+exec_pty(struct command_details *details, char *argv[], char *envp[],
+    int rbac_enabled)
+{
+    sigaction_t sa;
+    pid_t self = getpid();
+
+    /* Reset signal handlers. */
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = SIG_DFL;
+    sigaction(SIGHUP, &sa, NULL);
+    sigaction(SIGTERM, &sa, NULL);
+    sigaction(SIGINT, &sa, NULL);
+    sigaction(SIGQUIT, &sa, NULL);
+    sigaction(SIGTSTP, &sa, NULL);
+    sigaction(SIGTTIN, &sa, NULL);
+    sigaction(SIGTTOU, &sa, NULL);
+    sigaction(SIGUSR1, &sa, NULL);
+    sigaction(SIGUSR2, &sa, NULL);
+    sigaction(SIGCHLD, &sa, NULL);
+
+    /* Set child process group here too to avoid a race. */
+    setpgid(0, self);
+
+    /* Wire up standard fds, note that stdout/stderr may be pipes. */
+    dup2(io_fds[SFD_STDIN], STDIN_FILENO);
+    dup2(io_fds[SFD_STDOUT], STDOUT_FILENO);
+    dup2(io_fds[SFD_STDERR], STDERR_FILENO);
+
+    /* Wait for parent to grant us the tty if we are foreground. */
+    if (foreground) {
+       while (tcgetpgrp(io_fds[SFD_SLAVE]) != self)
+           ; /* spin */
+    }
+
+    /* We have guaranteed that the slave fd is > 2 */
+    if (io_fds[SFD_SLAVE] != -1)
+       close(io_fds[SFD_SLAVE]);
+    if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDIN]);
+    if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDOUT]);
+    if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+       close(io_fds[SFD_STDERR]);
+
+    if (details->closefrom >= 0)
+       closefrom(details->closefrom);
+#ifdef HAVE_SELINUX
+    if (rbac_enabled)
+       selinux_execve(details->command, argv, envp);
+    else
+#endif
+       my_execve(details->command, argv, envp);
+}
+
+/*
+ * Propagates tty size change signals to pty being used by the command.
+ */
+static void
+sync_ttysize(int src, int dst)
+{
+#ifdef TIOCGSIZE
+    struct ttysize tsize;
+    pid_t pgrp;
+
+    if (ioctl(src, TIOCGSIZE, &tsize) == 0) {
+           ioctl(dst, TIOCSSIZE, &tsize);
+#ifdef TIOCGPGRP
+           if (ioctl(dst, TIOCGPGRP, &pgrp) == 0)
+                   killpg(pgrp, SIGWINCH);
+#endif
+    }
+#endif
+}
+
+/*
+ * Handler for SIGWINCH in parent.
+ */
+static void
+sigwinch(int s)
+{
+    int serrno = errno;
+
+    sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+    errno = serrno;
+}
+
+/*
+ * Only close the fd if it is not /dev/tty or std{in,out,err}.
+ * Return value is the same as send(2).
+ */
+static int
+safe_close(int fd)
+{
+    /* Avoid closing /dev/tty or std{in,out,err}. */
+    if (fd < 3 || fd == io_fds[SFD_USERTTY]) {
+       errno = EINVAL;
+       return -1;
+    }
+    return close(fd);
+}
index 3f6346c7a5670bca28d5fc7375c50e54dcbccfff..f0112a20a42cf1bd76c8eb0ca86a58498128493e 100644 (file)
@@ -160,6 +160,17 @@ void zero_bytes(volatile void *, size_t);
 /* exec.c */
 int sudo_execve(struct command_details *details, char *argv[], char *envp[],
     struct command_status *cstat);
+int my_execve(const char *path, char *const argv[], char *const envp[]);
+
+/* exec_pty.c */
+int fork_pty(struct command_details *details, char *argv[], char *envp[],
+    int sv[], int rbac_enabled, int *maxfd);
+int perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat);
+int suspend_parent(int signo);
+void fd_set_iobs(fd_set *fdsr, fd_set *fdsw);
+void pty_close(struct command_status *cstat);
+void pty_setup(uid_t uid);
+void terminate_child(pid_t pid, int use_pgrp);
 
 /* term.c */
 int term_cbreak(int);
@@ -178,7 +189,7 @@ int atobool(const char *str);
 int parse_args(int argc, char **argv, int *nargc, char ***nargv,
     char ***settingsp, char ***env_addp);
 
-/* pty.c */
+/* get_pty.c */
 int get_pty(int *master, int *slave, char *name, size_t namesz, uid_t uid);
 
 /* ttysize.c */