]> granicus.if.org Git - sudo/commitdiff
Split exec.c into exec.c and exec_pty.c
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Jun 2010 21:28:05 +0000 (17:28 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Jun 2010 21:28:05 +0000 (17:28 -0400)
Pass a flag in to sudo_execve to indicate whether we need to
    wait for the command to finish (fork + execve vs. execve).

--HG--
branch : 1.7

Makefile.in
configure
configure.in
exec.c
exec_pty.c [new file with mode: 0644]
iolog.c
selinux.c
sudo.c
sudo.h
sudo_edit.c

index e18900a7013acb679ad5c7c46ec750727f8481d7..b0bb0474cdf9e5ee84be198983b9ad7dd4c6b3a8 100644 (file)
@@ -275,6 +275,8 @@ error.o: $(srcdir)/error.c $(srcdir)/compat.h $(srcdir)/error.h config.h
        $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/error.c
 exec.o: $(srcdir)/exec.c $(SUDODEP)
        $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/exec.c
+exec_pty.o: $(srcdir)/exec.c $(SUDODEP)
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/exec_pty.c
 fileops.o: $(srcdir)/fileops.c $(SUDODEP)
        $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/fileops.c
 find_path.o: $(srcdir)/find_path.c $(SUDODEP)
index 766fed94fac7a11dadebbbff76bfd97ad7f0152d..c1f80a423bf23da4e991759e51f44a78e2ee0352 100755 (executable)
--- a/configure
+++ b/configure
@@ -17987,7 +17987,7 @@ if test "x$ac_cv_func_tcsetpgrp" = x""yes; then :
 #define HAVE_TCSETPGRP 1
 _ACEOF
 
-       SUDO_OBJS="${SUDO_OBJS} get_pty.o iolog.o:
+       SUDO_OBJS="${SUDO_OBJS} exec_pty.o get_pty.o iolog.o"
        PROGS="$PROGS sudoreplay"
        REPLAY=""
 
index 3b1afc169a90acc71a94be20386f35be5b33a1c2..46e085ad0f8d50033585bbac0442acc1d4fc933b 100644 (file)
@@ -2638,12 +2638,12 @@ SUDO_TIMEDIR
 SUDO_IO_LOGDIR
 
 dnl
-dnl If I/O logging is enabled, build sudoreplay and add get_pty.o iolog.o
+dnl If I/O logging is enabled, build sudoreplay and exec_pty get_pty.o iolog.o
 dnl
 if test "${with_iologdir-yes}" != "no"; then
     # Require POSIX job control for I/O log support
     AC_CHECK_FUNCS(tcsetpgrp, [
-       SUDO_OBJS="${SUDO_OBJS} get_pty.o iolog.o:
+       SUDO_OBJS="${SUDO_OBJS} exec_pty.o get_pty.o iolog.o"
        PROGS="$PROGS sudoreplay"
        REPLAY=""
 
diff --git a/exec.c b/exec.c
index dc8e96d495fe80da7225a1935563f56b3fdd30b9..cb38b7dd7f659a89a709b79501dbcfbf4da8d0ce 100644 (file)
--- a/exec.c
+++ b/exec.c
 #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.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 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 exec_monitor(const char *path, char *argv[],
-    char *envp[], int, int);
-static void exec_pty(const char *path, 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);
-
-static void
-pty_setup(uid)
-    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");
-    }
-}
-
-static void
-check_foreground()
-{
-    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(signo, iobufs)
-    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);
-       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}
-        */
-       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 __P((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(path, argv, envp)
     const char *path;
     char *argv[];
@@ -253,155 +100,103 @@ my_execve(path, argv, envp)
     return -1;
 }
 
-static void
-terminate_child(pid, use_pgrp)
-    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(rfd, wfd, action, head)
-    int rfd;
-    int wfd;
-    int (*action) __P((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(iobufs, fdsr, fdsw)
-    struct io_buffer *iobufs;
-    fd_set *fdsr;
-    fd_set *fdsw;
+static int fork_cmnd(path, argv, envp, sv, rbac_enabled)
+    const char *path;
+    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;
-           }
-       }
-       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;
-           }
+    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(user_role, user_type, user_ttypath, -1);
+#endif
+       if (exec_setup() == TRUE) {
+           /* headed for execve() */
+           closefrom(def_closefrom);
+#ifdef HAVE_SELINUX
+           if (rbac_enabled)
+               selinux_execve(path, argv, envp);
+           else
+#endif
+               my_execve(path, 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(path, argv, envp, uid, cstat)
+sudo_execve(path, argv, envp, uid, cstat, dowait)
     const char *path;
     char *argv[];
     char *envp[];
     uid_t uid;
     struct command_status *cstat;
+    int dowait;
 {
     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;
 
+#ifdef _PATH_SUDO_IO_LOGDIR
     log_io = def_log_output || def_log_input;
     if (log_io) {
        pty_setup(uid);
        io_log_open();
+       dowait = TRUE;
     }
+#endif /* _PATH_SUDO_IO_LOGDIR */
 
 #ifdef HAVE_SELINUX
     rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
-    if (rbac_enabled) {
-       /* Must do SELinux setup before changing uid. */
-       selinux_setup(user_role, user_type,
-           log_io ? slavename : user_ttypath, io_fds[SFD_SLAVE]);
-    }
+    if (rbac_enabled)
+       dowait = TRUE;
 #endif
 
-    ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+    /*
+     * If we don't need to wait for the command to finish, just exec it.
+     */
+    if (!dowait) {
+       exec_setup();
+       closefrom(def_closefrom);
+       my_execve(path, argv, envp);
+       cstat->type = CMD_ERRNO;
+       cstat->val = errno;
+       return(127);
+    }
 
     /*
-     * 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)
@@ -413,7 +208,6 @@ sudo_execve(path, argv, envp, uid, cstat)
     /* 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);
@@ -421,160 +215,21 @@ sudo_execve(path, argv, envp, uid, cstat)
     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);
-       }
-
-       /*
-        * 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() == 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(path, argv, envp, sv[1], rbac_enabled);
-           } else {
-               closefrom(def_closefrom);
-#ifdef HAVE_SELINUX
-               if (rbac_enabled)
-                   selinux_execve(path, argv, envp);
-               else
+#ifdef _PATH_SUDO_IO_LOGDIR
+    if (log_io)
+       child = fork_pty(path, argv, envp, sv, rbac_enabled, &maxfd);
+    else
 #endif
-                   my_execve(path, argv, envp);
-           }
-       }
-       cstat->type = CMD_ERRNO;
-       cstat->val = errno;
-       send(sv[1], cstat, sizeof(*cstat), 0);
-       _exit(1);
-    }
+       child = fork_cmnd(path, argv, envp, sv, rbac_enabled);
     close(sv[1]);
 
-    /* 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.
@@ -591,45 +246,36 @@ sudo_execve(path, argv, envp, uid, cstat)
             */
            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;
+#ifdef sudo_waitpid
+               pid = sudo_waitpid(child, &status, WUNTRACED|WNOHANG);
+#else
+               pid = wait(&status);
+#endif
+               if (pid == child) {
+                   if (!log_io) {
+                       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. */
                }
-           }
+           } while (pid != -1 || errno == EINTR);
        }
 
        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);
-           }
-       }
+#ifdef _PATH_SUDO_IO_LOGDIR
+       if (log_io)
+           fd_set_iobs(fdsr, fdsw); /* XXX - better name */
+#endif
        for (n = 0; n < NSIG; n++) {
            if (recvsig[n] && n != SIGCHLD) {
                if (log_io) {
@@ -637,11 +283,7 @@ sudo_execve(path, argv, envp, uid, cstat)
                    break;
                } else {
                    /* nothing listening on sv[0], send directly */
-                   if (n == SIGALRM) {
-                       terminate_child(child, FALSE);
-                   } else {
-                       kill(child, n);
-                   }
+                   kill(child, n);
                }
            }
        }
@@ -670,22 +312,27 @@ sudo_execve(path, argv, envp, uid, cstat)
                    break;
                }
            }
+#ifdef _PATH_SUDO_IO_LOGDIR /* XXX */
            if (cstat->type == CMD_WSTATUS) {
                if (WIFSTOPPED(cstat->val)) {
                    /* Suspend parent and tell child how to resume on return. */
-                   n = suspend_parent(WSTOPSIG(cstat->val), iobufs);
+                   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) {
+           } else
+#endif /* _PATH_SUDO_IO_LOGDIR */
+           if (cstat->type == CMD_ERRNO) {
                /* Child was unable to execute command or broken pipe. */
                break;
            }
        }
 
+#ifdef _PATH_SUDO_IO_LOGDIR
+       /* XXX - move this too */
        if (FD_ISSET(sv[0], fdsw)) {
            for (n = 0; n < NSIG; n++) {
                if (!recvsig[n])
@@ -702,43 +349,17 @@ sudo_execve(path, argv, envp, uid, cstat)
                }
            }
        }
-       if (perform_io(iobufs, fdsr, fdsw) != 0) {
-           cstat->type = CMD_ERRNO;
-           cstat->val = errno;
+       if (perform_io(fdsr, fdsw, cstat) != 0)
            break;
-       }
+#endif /* _PATH_SUDO_IO_LOGDIR */
     }
 
+#ifdef _PATH_SUDO_IO_LOGDIR
     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);
     }
+#endif /* _PATH_SUDO_IO_LOGDIR */
 
 #ifdef HAVE_SELINUX
     if (rbac_enabled) {
@@ -750,515 +371,17 @@ sudo_execve(path, argv, envp, uid, cstat)
 
     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, signo)
-    pid_t pid;
-    int signo;
-{
-    int status;
-
-    /* Handle signal from parent. */
-    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(fd, cstat)
-    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);
-       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(backchannel, cstat)
-    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)) {
-               if (send_status(backchannel, cstat) == -1)
-                   return alive; /* XXX */
-           }
-       }
-       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(path, argv, envp, backchannel, rbac)
-    const char *path;
-    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(path, 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(iobufs)
-    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(path, argv, envp, rbac_enabled)
-    const char *path;
-    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]);
-
-    closefrom(def_closefrom);
-#ifdef HAVE_SELINUX
-    if (rbac_enabled)
-       selinux_execve(path, argv, envp);
-    else
-#endif
-       my_execve(path, argv, envp);
-}
-
-/*
- * Propagates tty size change signals to pty being used by the command.
- */
-static void
-sync_ttysize(src, dst)
-    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(s)
     int s;
 {
     recvsig[s] = TRUE;
 }
-
-/*
- * Handler for SIGWINCH in parent.
- */
-static void
-sigwinch(s)
-    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(fd)
-    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/exec_pty.c b/exec_pty.c
new file mode 100644 (file)
index 0000000..c97abbe
--- /dev/null
@@ -0,0 +1,1081 @@
+/*
+ * 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"
+
+#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) __P((const char *buf, unsigned int len));
+    char buf[16 * 1024];
+};
+
+/* shared with pty.c */
+extern sig_atomic_t recvsig[NSIG]; 
+extern void handler __P((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 struct io_buffer *iobufs;
+
+static void flush_output __P((void));
+static int exec_monitor __P((const char *path, char *argv[],
+    char *envp[], int, int));
+static void exec_pty __P((const char *path, char *argv[],
+    char *envp[], int));
+static void sigwinch __P((int s));
+static void sync_ttysize __P((int src, int dst));
+static void deliver_signal __P((pid_t pid, int signo));
+static int safe_close __P((int fd));
+
+/*
+ * 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)
+    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");
+    }
+}
+
+/*
+ * 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()
+{
+    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(signo)
+    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);
+       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}
+        */
+       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.
+ */
+static void
+terminate_child(pid, use_pgrp)
+    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);
+    }
+}
+
+/*
+ * Allocate a new io_buffer struct and insert it at the head of the list.
+ * Returns the new head element.
+ */
+static struct io_buffer *
+io_buf_new(rfd, wfd, action, head)
+    int rfd;
+    int wfd;
+    int (*action) __P((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.
+ * Fills in cstat on error.
+ * Returns the number of errors.
+ */
+int
+perform_io(fdsr, fdsw, cstat)
+    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(path, argv, envp, sv, rbac_enabled, maxfd)
+    const char *path;
+    char *argv[];
+    char *envp[];
+    int sv[2];
+    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);
+    }
+
+    /*
+     * 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() == 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(path, 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) {
+       /* Adjust 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;
+}
+
+/*
+ * Flush any remaining output and restore /dev/tty to the way we found it.
+ * If the command died due to a signal, writes the reason to stdout.
+ */
+void
+pty_close(cstat)
+    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(fdsr, fdsw)
+    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);
+       }
+    }
+}
+
+/*
+ * Deliver a relayed signal to the command.
+ */
+static void
+deliver_signal(pid, signo)
+    pid_t pid;
+    int signo;
+{
+    int status;
+
+    /* Handle signal from parent. */
+    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(fd, cstat)
+    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);
+       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(backchannel, cstat)
+    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)) {
+               if (send_status(backchannel, cstat) == -1)
+                   return alive; /* XXX */
+           }
+       }
+       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(path, argv, envp, backchannel, rbac)
+    const char *path;
+    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(path, 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 *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(path, argv, envp, rbac_enabled)
+    const char *path;
+    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]);
+
+    closefrom(def_closefrom);
+#ifdef HAVE_SELINUX
+    if (rbac_enabled)
+       selinux_execve(path, argv, envp);
+    else
+#endif
+       my_execve(path, argv, envp);
+}
+
+/*
+ * Propagates tty size change signals to pty being used by the command.
+ */
+static void
+sync_ttysize(src, dst)
+    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(s)
+    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(fd)
+    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/iolog.c b/iolog.c
index ba40393ddfb5a2a4c4539e38b9d7315786d64b8a..33a4f6c964595589e5410f1072487c76bbe92a03 100644 (file)
--- a/iolog.c
+++ b/iolog.c
@@ -120,7 +120,7 @@ io_nextid()
        if (nread == -1)
            log_error(USE_ERRNO, "cannot read %s", pathbuf);
        id = strtoul(buf, &ep, 36);
-       if (buf == ep || id >= 2176782336U)
+       if (buf == ep || id >= (unsigned long)2176782336)
            log_error(0, "invalid sequence number %s", pathbuf);
     }
     id++;
index 22416f46ec8e85bd167ff7c59a46914633871b0b..12c8d3e07124ef203b85e2a9325aee4ea202f71c 100644 (file)
--- a/selinux.c
+++ b/selinux.c
@@ -298,8 +298,6 @@ selinux_setup(const char *role, const char *type, const char *ttyn,
 void
 selinux_execve(const char *path, char *argv[], char *envp[])
 {
-    int serrno;
-
     if (setexeccon(se_state.new_context)) {
        warning("unable to set exec context to %s", se_state.new_context);
        if (se_state.enforcing)
@@ -320,7 +318,7 @@ selinux_execve(const char *path, char *argv[], char *envp[])
     /* We use the "spare" slot in argv to store sesh. */
     --argv;
     argv[0] = *argv[1] == '-' ? "-sesh" : "sesh";
-    argv[1] = path;
+    argv[1] = (char *)path;
 
     execve(_PATH_SUDO_SESH, argv, envp);
 }
diff --git a/sudo.c b/sudo.c
index 5e0fe25edfcc338c3d524c5e16a35335f3b0599b..3e883f4a6fadba9463bf87ecff121450e9423148 100644 (file)
--- a/sudo.c
+++ b/sudo.c
 #include "sudo.h"
 #include "lbuf.h"
 #include "interfaces.h"
+#include <sudo_usage.h>
 
 #ifdef USING_NONUNIX_GROUPS
 # include "nonunix.h"
@@ -123,7 +124,7 @@ extern int sudo_edit                        __P((int, char **, char **));
 extern void rebuild_env                        __P((int, int));
 void validate_env_vars                 __P((struct list_member *));
 void insert_env_vars                   __P((struct list_member *));
-int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid)); /* XXX should be in sudo.h */
+int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid, int dowait)); /* XXX should be in sudo.h */
 
 /*
  * Globals
@@ -512,15 +513,10 @@ main(argc, argv, envp)
        (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
        (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
 
-       /* Close the password and group files and free up memory. */
-       /* XXX - too early, pty code uses sudo_getgrnam */
-       sudo_endpwent();
-       sudo_endgrent();
-
        if (ISSET(sudo_mode, MODE_EDIT))
            exit(sudo_edit(NewArgc, NewArgv, envp));
        else
-           exit(run_command(safe_cmnd, NewArgv, environ, runas_pw->pw_uid));
+           exit(run_command(safe_cmnd, NewArgv, environ, runas_pw->pw_uid, FALSE));
     } else if (ISSET(validated, FLAG_NO_USER | FLAG_NO_HOST)) {
        audit_failure(NewArgv, "No user or host");
        log_denial(validated, 1);
@@ -816,6 +812,10 @@ exec_setup()
 {
     int rval = FALSE;
 
+    /* Close the password and group files and free up memory. */
+    sudo_endpwent();
+    sudo_endgrent();
+
     /*
      * For sudoedit, the command runas a the user with no additional setup.
      */
@@ -880,7 +880,12 @@ done:
  * Run the command and wait for it to complete.
  */
 int
-run_command(const char *path, char *argv[], char *envp[], uid_t uid)
+run_command(path, argv, envp, uid, dowait)
+    const char *path;
+    char *argv[];
+    char *envp[];
+    uid_t uid;
+    int dowait;
 {
     struct command_status cstat;
     int exitcode = 1;
@@ -892,7 +897,7 @@ run_command(const char *path, char *argv[], char *envp[], uid_t uid)
     cstat.type = CMD_INVALID;
     cstat.val = 0;
 
-    sudo_execve(path, argv, envp, uid, &cstat);
+    sudo_execve(path, argv, envp, uid, &cstat, dowait);
 
     switch (cstat.type) {
     case CMD_ERRNO:
diff --git a/sudo.h b/sudo.h
index 59893193170aebafce1e4ef66ec2bbf6f4d5e51e..b9aad60b53e65e8d1027d70b7ed54b297f9ee21c 100644 (file)
--- a/sudo.h
+++ b/sudo.h
@@ -226,7 +226,17 @@ void read_env_file __P((const char *, int));
 
 /* exec.c */
 int sudo_execve __P((const char *path, char *argv[], char *envp[], uid_t uid,
-    struct command_status *cstat));
+    struct command_status *cstat, int dowait));
+int my_execve __P((const char *path, char *argv[], char *envp[]));
+
+/* exec_pty.c */
+int fork_pty __P((const char *path, char *argv[], char *envp[], int sv[],
+    int rbac_enabled, int *maxfd));
+int perform_io __P((fd_set *fdsr, fd_set *fdsw, struct command_status *cstat));
+void pty_close __P((struct command_status *cstat));
+void fd_set_iobs __P((fd_set *fdsr, fd_set *fdsw));
+void pty_setup __P((uid_t uid));
+int suspend_parent __P((int signo));
 
 /* fileops.c */
 char *sudo_parseln     __P((FILE *));
@@ -358,7 +368,7 @@ extern int tgetpass_flags;
 extern int long_list;
 extern uid_t timestamp_uid;
 /* XXX - conflicts with the one in visudo */
-int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid));
+int run_command __P((const char *path, char *argv[], char *envp[], uid_t uid, int dowait));
 #endif
 #ifndef errno
 extern int errno;
index 3413344c7cfdd1c65b2f8f5c76b9e91a7b2693b6..80e053567b536418d6a0b7ac8fc9b4e9ad1e0147 100644 (file)
@@ -104,15 +104,6 @@ sudo_edit(argc, argv, envp)
     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
        tmplen--;
 
-#if 0 /* XXX - already done */
-    /*
-     * Close password, shadow, and group files before we try to open
-     * user-specified files to prevent the opening of things like /dev/fd/4
-     */
-    sudo_endpwent();
-    sudo_endgrent();
-#endif
-
     /*
      * For each file specified by the user, make a temporary version
      * and copy the contents of the original to it.
@@ -214,7 +205,7 @@ sudo_edit(argc, argv, envp)
      * keeping track of the time spent in the editor.
      */
     gettime(&tv1);
-    rval = run_command(editor, nargv, envp, user_uid);
+    rval = run_command(editor, nargv, envp, user_uid, TRUE);
     gettime(&tv2);
 
     /* Copy contents of temp files to real ones */