+++ /dev/null
-/*
- * 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>
-#endif /* HAVE_STRING_H */
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif /* HAVE_STRINGS_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>
-
-#include "sudo.h"
-#include "sudo_exec.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];
-};
-
-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);
- if (killpg(ppgrp, signo) != 0)
- warning("killpg(%d, %d)", 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);
- switch (n) {
- case -1:
- if (errno == EAGAIN)
- break;
- if (errno != ENXIO && errno != EBADF) {
- errors++;
- break;
- }
- /* FALLTHROUGH */
- case 0:
- /* got EOF or pty has gone away */
- safe_close(iob->rfd);
- iob->rfd = -1;
- break;
- default:
- if (!iob->action(iob->buf + iob->len, n))
- terminate_child(child, TRUE);
- iob->len += n;
- break;
- }
- }
- 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 || errno == ENXIO || errno == EBADF) {
- /* other end of pipe closed or pty revoked */
- 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 (io_fds[SFD_STDIN] == -1 || !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 (io_fds[SFD_STDOUT] == -1 || !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 (io_fds[SFD_STDERR] == -1 || !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 = SA_INTERRUPT; /* do not restart syscalls */
- sa.sa_handler = handler;
- sigaction(SIGTSTP, &sa, NULL);
-
- 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);
- if (exec_setup(rbac_enabled, slavename, io_fds[SFD_SLAVE]) == 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) {
- const 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 {
-#ifdef sudo_waitpid
- pid = sudo_waitpid(child, &status, WUNTRACED|WNOHANG);
-#else
- pid = wait(&status);
-#endif
- } 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).
- */
-static 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 = SA_INTERRUPT;
- 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.
- */
- if (setsid() == -1) {
- warning("setsid");
- goto bad;
- }
- 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. */
- if (dup2(io_fds[SFD_STDIN], STDIN_FILENO) == -1 ||
- dup2(io_fds[SFD_STDOUT], STDOUT_FILENO) == -1 ||
- dup2(io_fds[SFD_STDERR], STDERR_FILENO) == -1)
- error(1, "dup2");
-
- /* 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);
- if ((pgrp = tcgetpgrp(dst)) != -1)
- killpg(pgrp, SIGWINCH);
- }
-#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);
-}