From 845a6caf05062abc61cbb0b91451182891a4b305 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Mon, 7 Jun 2010 17:28:05 -0400 Subject: [PATCH] Split exec.c into exec.c and exec_pty.c 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 | 2 + configure | 2 +- configure.in | 4 +- exec.c | 1099 +++++--------------------------------------------- exec_pty.c | 1081 +++++++++++++++++++++++++++++++++++++++++++++++++ iolog.c | 2 +- selinux.c | 4 +- sudo.c | 23 +- sudo.h | 14 +- sudo_edit.c | 11 +- 10 files changed, 1226 insertions(+), 1016 deletions(-) create mode 100644 exec_pty.c diff --git a/Makefile.in b/Makefile.in index e18900a70..b0bb0474c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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) diff --git a/configure b/configure index 766fed94f..c1f80a423 100755 --- 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="" diff --git a/configure.in b/configure.in index 3b1afc169..46e085ad0 100644 --- a/configure.in +++ b/configure.in @@ -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 dc8e96d49..cb38b7dd7 100644 --- a/exec.c +++ b/exec.c @@ -58,13 +58,12 @@ #endif #include #include -#include -#include #include #ifdef HAVE_SELINUX # include #endif +/* XXX - move to compat */ #if !defined(NSIG) # if defined(_NSIG) # define NSIG _NSIG @@ -77,167 +76,15 @@ #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 index 000000000..c97abbec4 --- /dev/null +++ b/exec_pty.c @@ -0,0 +1,1081 @@ +/* + * Copyright (c) 2009-2010 Todd C. Miller + * + * 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 + +#include +#include +#include +#include +#include +#ifdef HAVE_TERMIOS_H +# include +#else +# include +#endif /* HAVE_TERMIOS_H */ +#include +#ifdef HAVE_SYS_SELECT_H +# include +#endif /* HAVE_SYS_SELECT_H */ +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) +# include +# endif +# include +#else +# ifdef HAVE_STRINGS_H +# include +# endif +#endif /* HAVE_STRING_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif +#include +#include +#include + +/* 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 ba40393dd..33a4f6c96 100644 --- 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++; diff --git a/selinux.c b/selinux.c index 22416f46e..12c8d3e07 100644 --- 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 5e0fe25ed..3e883f4a6 100644 --- a/sudo.c +++ b/sudo.c @@ -102,6 +102,7 @@ #include "sudo.h" #include "lbuf.h" #include "interfaces.h" +#include #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 598931931..b9aad60b5 100644 --- 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; diff --git a/sudo_edit.c b/sudo_edit.c index 3413344c7..80e053567 100644 --- a/sudo_edit.c +++ b/sudo_edit.c @@ -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 */ -- 2.40.0