]> granicus.if.org Git - sudo/commitdiff
Do I/O in parent instead of child since the parent can have both
authorTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 22 Oct 2009 00:58:41 +0000 (00:58 +0000)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Thu, 22 Oct 2009 00:58:41 +0000 (00:58 +0000)
/dev/tty as well as the pty fds open.  The child just sets things
up and waits for its grandchild and writes the signal description
to the pty master if the command was killed by a signal.

script.c

index 125f1d909e5ec2cb50d7cec4fbc01ddeaa585226..5fa303bbcdde7abe3a733a503056ff3c7c95bf64 100644 (file)
--- a/script.c
+++ b/script.c
@@ -79,18 +79,17 @@ static int script_fds[6];
 
 static sig_atomic_t alive = 1;
 static sig_atomic_t suspended = 0;
-static sig_atomic_t signo;
 
-static pid_t parent, grandchild;
-static int grandchild_status;
+static pid_t parent, child;
+static int child_status;
 
 static char slavename[PATH_MAX];
 
-static void script_child __P((char *path, char *argv[]));
-static void script_grandchild __P((char *path, char *argv[], int));
+static void script_child __P((char *path, char *argv[], int));
+static void script_run __P((char *path, char *argv[], int));
 static void sync_winsize __P((int src, int dst));
-static void handler __P((int s));
 static void sigchild __P((int s));
+static void sigtstp __P((int s));
 static void sigwinch __P((int s));
 static void flush_output __P((struct script_buf *output, struct timeval *then,
     struct timeval *now, FILE *ofile, FILE *tfile));
@@ -221,11 +220,10 @@ void
 script_setup()
 {
     char pathbuf[PATH_MAX];
-    int len;
+    int len, ok;
 
-    /* XXX - can't use _PATH_TTY here since it won't be valid in new session */
-    if (!user_ttypath ||
-       (script_fds[SFD_USERTTY] = open(user_ttypath, O_RDWR|O_NOCTTY, 0) == -1))
+    script_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
+    if (script_fds[SFD_USERTTY] == -1)
        log_error(0, "tty required for transcript support"); /* XXX */
 
     if (!get_pty(&script_fds[SFD_MASTER], &script_fds[SFD_SLAVE],
@@ -237,7 +235,10 @@ script_setup()
        log_error(USE_ERRNO, "Can't copy terminal attributes");
     sync_winsize(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE]);
 
-    if (!term_raw(script_fds[SFD_USERTTY], 1))
+    do {
+       ok = term_raw(script_fds[SFD_USERTTY], 1);
+    } while (!ok && errno == EINTR);
+    if (!ok)
        log_error(USE_ERRNO, "Can't set terminal to raw mode");
 
     /*
@@ -319,14 +320,14 @@ log_output(buf, n, then, now, ofile, tfile)
  * 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 waits for its child and handles signals send by the child
- *     allow the child to be suspended in the parent session with the
- *     parent controlling tty.  Acts as the "glue" between the two controlling
- *     ttys from a job-control standpoint.
- *  2) child, creates a new session.  Does all the I/O passing data to and
- *     from the pty and signals parent when it receives job control-related
- *     signals on behalf of the grandchild.
- *  3) grandchild, runs the actual command with the pty slave as its
+ *  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
@@ -334,154 +335,59 @@ script_execv(path, argv)
     char *path;
     char *argv[];
 {
-    sigaction_t sa, saveint, savehup, saveterm;
-    sigaction_t savequit, savetstp, savettin, savettou;
-    int status, exitcode = 1;
-    pid_t child, pid;
+    sigaction_t sa, osa;
+    struct script_buf input, output;
+    struct timeval now, then;
+    int n, nready, exitcode = 1;
+    fd_set *fdsr, *fdsw;
+    FILE *idfile, *ofile, *tfile;
+    int rbac_enabled = 0;
 
-    parent = getpid(); /* so child can pass signals back to us */
+#ifdef HAVE_SELINUX
+    rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
+    if (rbac_enabled) {
+       selinux_prefork(user_role, user_type, script_fds[SFD_SLAVE]);
+       /* Re-open slave fd after it has been relabeled */
+       close(script_fds[SFD_SLAVE]);
+       script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0);
+       if (script_fds[SFD_SLAVE] == -1)
+           log_error(USE_ERRNO, "cannot open %s", slavename);
+    }
+#endif
 
-    /* Handle window change events in the parent for simplicity. */
+    /* Setup signal handlers window size changes and child exit */
     zero_bytes(&sa, sizeof(sa));
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_RESTART;
     sa.sa_handler = sigwinch;
-    (void) sigaction(SIGWINCH, &sa, NULL);
+    sigaction(SIGWINCH, &sa, NULL);
+    sa.sa_handler = sigchild;
+    sigaction(SIGCHLD, &sa, NULL);
 
-    /*
-     * Catch signals that would otherwise cause the user to end up
-     * with the tty in raw mode.  Do this early to avoid a race.
-     * NOTE: must not modify sa after this point (it is used later).
-     */
-    sa.sa_flags = SA_INTERRUPT; /* don't restart system calls */
-    sa.sa_handler = handler;
-    (void) sigaction(SIGINT, &sa, &saveint);
-    (void) sigaction(SIGHUP, &sa, &savehup);
-    (void) sigaction(SIGQUIT, &sa, &savequit);
-    (void) sigaction(SIGTERM, &sa, &saveterm);
-    (void) sigaction(SIGTSTP, &sa, &savetstp);
-    (void) sigaction(SIGTTIN, &sa, &savettin);
-    (void) sigaction(SIGTTOU, &sa, &savettou);
+    /* Handler for tty stop signals */
+    sa.sa_handler = sigtstp;
+    sigaction(SIGTSTP, &sa, NULL);
+    sigaction(SIGTTIN, &sa, NULL);
+    sigaction(SIGTTOU, &sa, NULL);
+
+    /* XXX - should also catch terminal signals and kill child */
 
     /*
-     * We have to fork or we cannot create a new session with a new
-     * controlling tty.  The parent acts as glue betweem the two sessions.
+     * Child will run the command in the pty, parent will pass data
+     * to and from pty.
      */
+    parent = getpid(); /* so child can pass signals back to us */
     child = fork();
     switch (child) {
     case -1:
        log_error(USE_ERRNO, "fork");
+       break;
     case 0:
-       (void) sigaction(SIGINT, &saveint, NULL);
-       (void) sigaction(SIGHUP, &savehup, NULL);
-       (void) sigaction(SIGQUIT, &savequit, NULL);
-       (void) sigaction(SIGTERM, &saveterm, NULL);
-       (void) sigaction(SIGTSTP, &savetstp, NULL);
-       (void) sigaction(SIGTTIN, &savettin, NULL);
-       (void) sigaction(SIGTTOU, &savettou, NULL);
-       script_child(path, argv);
+       script_child(path, argv, rbac_enabled);
        /* NOTREACHED */
        break;
     }
 
-    /* Wait for signal from child or for child to exit. */
-    for (;;) {
-       pid = waitpid(child, &status, 0);
-       if (pid != -1 || errno != EINTR) {
-           /* headed for exit */
-           if (pid == child) {
-               if (WIFEXITED(status))
-                   exitcode = WEXITSTATUS(status);
-               if (WIFSIGNALED(status))
-                   exitcode = WTERMSIG(status) | 128;
-           }
-           break;
-       }
-
-       /* Restore old tty settings and signal handler. */
-       term_restore(script_fds[SFD_USERTTY]);
-      check_sig:
-       switch (signo) {
-       case SIGINT:
-           (void) sigaction(SIGINT, &saveint, NULL);
-           break;
-       case SIGHUP:
-           (void) sigaction(SIGHUP, &savehup, NULL);
-           break;
-       case SIGQUIT:
-           (void) sigaction(SIGQUIT, &savequit, NULL);
-           break;
-       case SIGTERM:
-           (void) sigaction(SIGTERM, &saveterm, NULL);
-           break;
-       case SIGTSTP:
-           (void) sigaction(SIGTSTP, &savetstp, NULL);
-           break;
-       case SIGTTIN:
-           (void) sigaction(SIGTTIN, &savettin, NULL);
-           break;
-       case SIGTTOU:
-           (void) sigaction(SIGTTOU, &savettou, NULL);
-           break;
-       default:
-           /* should not happen */
-           break;
-       }
-       kill(parent, signo); /* re-send signal with handler disabled */
-       /* Reinstall signal handler, reset raw mode and continue child */
-       (void) sigaction(signo, &sa, NULL);
-       if (!term_raw(script_fds[SFD_USERTTY], 1) && errno == EINTR)
-           goto check_sig;
-       kill(child, SIGCONT);
-    }
-
-    term_restore(script_fds[SFD_USERTTY]);
-
-    exit(exitcode);
-}
-
-void
-script_child(path, argv)
-    char *path;
-    char *argv[];
-{
-    int n, nready;
-    fd_set *fdsr, *fdsw;
-    struct script_buf input, output;
-    struct timeval now, then;
-    sigaction_t sa;
-    FILE *idfile, *ofile, *tfile;
-    int rbac_enabled = 0;
-
-    /*
-     * 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)
-       log_error(USE_ERRNO, "setsid");
-#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
-#ifdef TIOCSCTTY
-    if (ioctl(script_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
-       log_error(USE_ERRNO, "unable to set controlling tty");
-#else
-    /* Set controlling tty by reopening slave. */
-    if ((n = open(slavename, O_RDWR)) >= 0)
-       close(n);
-#endif
-
     if ((idfile = fdopen(script_fds[SFD_LOG], "w")) == NULL)
        log_error(USE_ERRNO, "fdopen");
     if ((ofile = fdopen(script_fds[SFD_OUTPUT], "w")) == NULL)
@@ -489,43 +395,9 @@ script_child(path, argv)
     if ((tfile = fdopen(script_fds[SFD_TIMING], "w")) == NULL)
        log_error(USE_ERRNO, "fdopen");
 
-#ifdef HAVE_SELINUX
-    rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
-    if (rbac_enabled) {
-       selinux_prefork(user_role, user_type, script_fds[SFD_SLAVE]);
-       /* Re-open slave fd after it has been relabeled */
-       close(script_fds[SFD_SLAVE]);
-       script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0);
-       if (script_fds[SFD_SLAVE] == -1)
-           log_error(USE_ERRNO, "cannot open %s", slavename);
-    }
-#endif
-
-    /* Setup signal handler for child stop/exit */
-    zero_bytes(&sa, sizeof(sa));
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
-    sa.sa_handler = sigchild;
-    sigaction(SIGCHLD, &sa, NULL);
-
-    /* Start grandchild and begin reading from pty master. */
-    grandchild = fork();
-    if (grandchild == -1)
-       log_error(USE_ERRNO, "Can't fork");
-    if (grandchild == 0) {
-       /* setup tty and exec command */
-       script_grandchild(path, argv, rbac_enabled);
-       warning("unable to execute %s", path);
-       _exit(127);
-    }
-    /* Set grandchild process group and grant it the controlling tty. */
-    setpgid(grandchild, grandchild);
-    if (tcsetpgrp(script_fds[SFD_SLAVE], grandchild) != 0)
-       warning("tcsetpgrp");
-
     gettimeofday(&then, NULL);
 
-    /* XXX - log more stuff? environment too? */
+    /* XXX - log more stuff?  window size? environment? */
     fprintf(idfile, "%ld:%s:%s:%s:%s\n", then.tv_sec, user_name,
        runas_pw->pw_name, runas_gr ? runas_gr->gr_name : "", user_tty);
     fprintf(idfile, "%s\n", user_cwd);
@@ -581,11 +453,23 @@ script_child(path, argv)
            /* Flush any remaining output to master tty. */
            flush_output(&output, &then, &now, ofile, tfile);
 
-           /* Relay signal back to parent for its tty and suspend ourself. */
+           /* Restore tty mode */
+           do {
+               n = term_restore(script_fds[SFD_USERTTY]);
+           } while (!n && errno == EINTR);
+
+           /* Suspend self and continue child when we resume. */
+           sa.sa_handler = SIG_DFL;
+           sigaction(suspended, &sa, &osa);
            kill(parent, suspended);
-           kill(getpid(), SIGSTOP);
+
+           /* Set tty to raw mode, restore signal handler and resume child. */
+           do {
+               n = term_raw(script_fds[SFD_USERTTY], 1);
+           } while (!n && errno == EINTR);
+           sigaction(suspended, &osa, NULL);
            suspended = 0;
-           killpg(grandchild, SIGCONT);
+           kill(child, SIGCONT);
        }
        nready = select(script_fds[SFD_MASTER] + 1, fdsr, fdsw, NULL, NULL);
        if (nready == -1) {
@@ -660,20 +544,124 @@ script_child(path, argv)
     }
     flush_output(&output, &then, &now, ofile, tfile);
 
-    if (WIFEXITED(grandchild_status))
-       exit(WEXITSTATUS(grandchild_status));
-    if (WIFSIGNALED(grandchild_status)) {
+    do {
+       n = term_restore(script_fds[SFD_USERTTY]);
+    } while (!n && errno == EINTR);
+
+    if (WIFEXITED(child_status))
+       exitcode = WEXITSTATUS(child_status);
+    else if (WIFSIGNALED(child_status))
+       exitcode = WTERMSIG(child_status) | 128;
+    exit(exitcode);
+}
+
+void
+script_child(path, argv, rbac_enabled)
+    char *path;
+    char *argv[];
+    int rbac_enabled;
+{
+    sigaction_t sa;
+    pid_t pid;
+    int status;
+    int exitcode = 1;
+
+    /* Reset signal handlers. */
+    zero_bytes(&sa, sizeof(sa));
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = SIG_DFL;
+    sigaction(SIGWINCH, &sa, NULL);
+    sigaction(SIGCHLD, &sa, NULL);
+    sigaction(SIGTSTP, &sa, NULL);
+    sigaction(SIGTTIN, &sa, NULL);
+    sigaction(SIGTTOU, &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)
+       log_error(USE_ERRNO, "setsid");
+#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
+#ifdef TIOCSCTTY
+    if (ioctl(script_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
+       log_error(USE_ERRNO, "unable to set controlling tty");
+#else
+    /* Set controlling tty by reopening slave. */
+    if ((n = open(slavename, O_RDWR)) >= 0)
+       close(n);
+#endif
+
+    /* Start command and wait for it to stop or exit */
+    child = fork();
+    if (child == -1)
+       log_error(USE_ERRNO, "Can't fork");
+    if (child == 0) {
+       /* setup tty and exec command */
+       script_run(path, argv, rbac_enabled);
+       warning("unable to execute %s", path);
+       _exit(127);
+    }
+    /* Set child process group and grant it the controlling tty. */
+    setpgid(child, child);
+    if (tcsetpgrp(script_fds[SFD_SLAVE], child) != 0)
+       warning("tcsetpgrp");
+
+    /* Wait for signal from child or for child to exit. */
+    for (;;) {
+       pid = waitpid(child, &status, WUNTRACED);
+       if (pid != child) {
+           if (pid == -1 && errno == EINTR)
+               continue;
+           /* no child?  just exit */
+           break;
+       }
+
+       /*
+        * If child stopped, signal parent and stop self.
+        * On resume, restart child and continue waiting.
+        */
+       if (WIFSTOPPED(status)) {
+           /* Stop parent, self and then resume child when we come back */
+           kill(parent, WSTOPSIG(status));
+           kill(getpid(), SIGSTOP);
+           kill(child, SIGCONT);
+       } else {
+           /* Child died, set exit code accordingly. */
+           if (WIFEXITED(status))
+               exitcode = WEXITSTATUS(status);
+           else if (WIFSIGNALED(status)) {
 #ifdef HAVE_STRSIGNAL
-       char *reason = strsignal(WTERMSIG(grandchild_status));
-       write(STDOUT_FILENO, reason, strlen(reason));
-       if (WCOREDUMP(grandchild_status))
-           write(STDOUT_FILENO, " (core dumped)", 14);
-       write(STDOUT_FILENO, "\n", 1);
+               int signo = WTERMSIG(status);
+               if (signo != SIGINT && signo != SIGPIPE) {
+                   char *reason = strsignal(signo);
+                   write(script_fds[SFD_MASTER], reason, strlen(reason));
+                   if (WCOREDUMP(status))
+                       write(script_fds[SFD_MASTER], " (core dumped)", 14);
+                   write(script_fds[SFD_MASTER], "\n", 1);
+               }
 #endif
-       exit(WTERMSIG(grandchild_status) | 128);
+               exitcode = WTERMSIG(status) | 128;
+           }
+           break;
+       }
     }
-    /* NOTREACHED */
-    exit(255);
+
+    _exit(exitcode);
 }
 
 static void
@@ -713,7 +701,7 @@ flush_output(output, then, now, ofile, tfile)
 }
 
 static void
-script_grandchild(path, argv, rbac_enabled)
+script_run(path, argv, rbac_enabled)
     char *path;
     char *argv[];
     int rbac_enabled;
@@ -770,17 +758,7 @@ sync_winsize(src, dst)
 }
 
 /*
- * Generic signal handler for parent, interrupts waitpid() and sets a flag.
- */
-static void
-handler(s)
-    int s;
-{
-    signo = s;
-}
-
-/*
- * Signal handler for grandchild and its descendents.
+ * Signal handler for child
  */
 static void
 sigchild(s)
@@ -791,18 +769,18 @@ sigchild(s)
 
 #ifdef sudo_waitpid
     do {
-       pid = sudo_waitpid(grandchild, &grandchild_status, WNOHANG | WUNTRACED);
-       if (pid == grandchild)
+       pid = sudo_waitpid(child, &child_status, WNOHANG | WUNTRACED);
+       if (pid == child)
            break;
     } while (pid > 0 || (pid == -1 && errno == EINTR));
 #else
     do {
-       pid = wait(&grandchild_status);
+       pid = wait(&child_status);
     } while (pid == -1 && errno == EINTR);
 #endif
-    if (pid == grandchild) {
-       if (WIFSTOPPED(grandchild_status))
-           suspended = WSTOPSIG(grandchild_status);
+    if (pid == child) {
+       if (WIFSTOPPED(child_status))
+           suspended = WSTOPSIG(child_status);
        else
            alive = 0;
     }
@@ -810,6 +788,16 @@ sigchild(s)
     errno = serrno;
 }
 
+/*
+ * Signal handler for SIG{TSTP,TTOU,TTIN}
+ */
+static void
+sigtstp(s)
+    int s;
+{
+    suspended = s;
+}
+
 static void
 sigwinch(s)
     int s;