$(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)
#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=""
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=""
#endif
#include <errno.h>
#include <fcntl.h>
-#include <grp.h>
-#include <pwd.h>
#include <signal.h>
#ifdef HAVE_SELINUX
# include <selinux/selinux.h>
#endif
+/* XXX - move to compat */
#if !defined(NSIG)
# if defined(_NSIG)
# define NSIG _NSIG
#include "sudo.h"
-#define SFD_STDIN 0
-#define SFD_STDOUT 1
-#define SFD_STDERR 2
-#define SFD_MASTER 3
-#define SFD_SLAVE 4
-#define SFD_USERTTY 5
-
-#define TERM_COOKED 0
-#define TERM_RAW 1
-
-/* Compatibility with older tty systems. */
-#if !defined(TIOCGSIZE) && defined(TIOCGWINSZ)
-# define TIOCGSIZE TIOCGWINSZ
-# define TIOCSSIZE TIOCSWINSZ
-# define ttysize winsize
-# define ts_cols ws_col
-#endif
-
-struct io_buffer {
- struct io_buffer *next;
- int len; /* buffer length (how much produced) */
- int off; /* write position (how much already consumed) */
- int rfd; /* reader (producer) */
- int wfd; /* writer (consumer) */
- int (*action)(const char *buf, unsigned int len);
- char buf[16 * 1024];
-};
-
-static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
-static int pipeline = FALSE;
-
-static sig_atomic_t recvsig[NSIG];
-
-static pid_t ppgrp, child;
-static int foreground;
-static int ttymode = TERM_COOKED;
-static int tty_initialized;
-
-static char slavename[PATH_MAX];
-
-static int suspend_parent(int signo, struct io_buffer *iobufs);
-static void flush_output(struct io_buffer *iobufs);
-static int perform_io(struct io_buffer *iobufs, fd_set *fdsr, fd_set *fdsw);
-static void handler(int s);
-static int exec_monitor(const char *path, char *argv[],
- char *envp[], int, int);
-static void exec_pty(const char *path, char *argv[],
- char *envp[], int);
-static void sigwinch(int s);
-static void sync_ttysize(int src, int dst);
-static void deliver_signal(pid_t pid, int signo);
-static int safe_close(int fd);
-
-static void
-pty_setup(uid)
- uid_t uid;
-{
- io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
- if (io_fds[SFD_USERTTY] != -1) {
- if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE],
- slavename, sizeof(slavename), uid))
- error(1, "Can't get pty");
- }
-}
-
-static void
-check_foreground()
-{
- if (io_fds[SFD_USERTTY] != -1) {
- foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
- if (foreground && !tty_initialized) {
- if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
- tty_initialized = 1;
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
- }
- }
- }
-}
-
-/*
- * Suspend sudo if the underlying command is suspended.
- * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
- */
-static int
-suspend_parent(signo, iobufs)
- int signo;
- struct io_buffer *iobufs;
-{
- sigaction_t sa, osa;
- int n, oldmode = ttymode, rval = 0;
-
- switch (signo) {
- case SIGTTOU:
- case SIGTTIN:
- /*
- * If we are the foreground process, just resume the child.
- * Otherwise, re-send the signal with the handler disabled.
- */
- if (!foreground)
- check_foreground();
- if (foreground) {
- if (ttymode != TERM_RAW) {
- do {
- n = term_raw(io_fds[SFD_USERTTY], 0);
- } while (!n && errno == EINTR);
- ttymode = TERM_RAW;
- }
- rval = SIGUSR1; /* resume child in foreground */
- break;
- }
- ttymode = TERM_RAW;
- /* FALLTHROUGH */
- case SIGSTOP:
- case SIGTSTP:
- /* Flush any remaining output before suspending. */
- flush_output(iobufs);
-
- /* Restore original tty mode before suspending. */
- if (oldmode != TERM_COOKED) {
- do {
- n = term_restore(io_fds[SFD_USERTTY], 0);
- } while (!n && errno == EINTR);
- }
-
- /* Suspend self and continue child when we resume. */
- sa.sa_handler = SIG_DFL;
- sigaction(signo, &sa, &osa);
- killpg(ppgrp, signo);
-
- /* Check foreground/background status on resume. */
- check_foreground();
-
- /*
- * Only modify term if we are foreground process and either
- * the old tty mode was not cooked or child got SIGTT{IN,OU}
- */
- if (ttymode != TERM_COOKED) {
- if (foreground) {
- /* Set raw mode. */
- do {
- n = term_raw(io_fds[SFD_USERTTY], 0);
- } while (!n && errno == EINTR);
- } else {
- /* Background process, no access to tty. */
- ttymode = TERM_COOKED;
- }
- }
-
- sigaction(signo, &osa, NULL);
- rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2;
- break;
- }
-
- return(rval);
-}
+/* shared with exec_pty.c */
+sig_atomic_t recvsig[NSIG];
+void handler __P((int s));
/*
* Like execve(2) but falls back to running through /bin/sh
* ala execvp(3) if we get ENOEXEC.
*/
-static int
+int
my_execve(path, argv, envp)
const char *path;
char *argv[];
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)
/* Note: HP-UX select() will not be interrupted if SA_RESTART set */
sa.sa_flags = 0; /* do not restart syscalls */
sa.sa_handler = handler;
- sigaction(SIGALRM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(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.
*/
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) {
break;
} else {
/* nothing listening on sv[0], send directly */
- if (n == SIGALRM) {
- terminate_child(child, FALSE);
- } else {
- kill(child, n);
- }
+ kill(child, n);
}
}
}
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])
}
}
}
- 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) {
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);
-}
--- /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>
+#else
+# ifdef HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+/* XXX - move to compat.h */
+#if !defined(NSIG)
+# if defined(_NSIG)
+# define NSIG _NSIG
+# elif defined(__NSIG)
+# define NSIG __NSIG
+# else
+# define NSIG 64
+# endif
+#endif
+
+#include "sudo.h"
+
+#define SFD_STDIN 0
+#define SFD_STDOUT 1
+#define SFD_STDERR 2
+#define SFD_MASTER 3
+#define SFD_SLAVE 4
+#define SFD_USERTTY 5
+
+#define TERM_COOKED 0
+#define TERM_RAW 1
+
+/* Compatibility with older tty systems. */
+#if !defined(TIOCGSIZE) && defined(TIOCGWINSZ)
+# define TIOCGSIZE TIOCGWINSZ
+# define TIOCSSIZE TIOCSWINSZ
+# define ttysize winsize
+# define ts_cols ws_col
+#endif
+
+struct io_buffer {
+ struct io_buffer *next;
+ int len; /* buffer length (how much produced) */
+ int off; /* write position (how much already consumed) */
+ int rfd; /* reader (producer) */
+ int wfd; /* writer (consumer) */
+ int (*action) __P((const char *buf, unsigned int len));
+ char buf[16 * 1024];
+};
+
+/* shared with pty.c */
+extern sig_atomic_t recvsig[NSIG];
+extern void handler __P((int s));
+
+static char slavename[PATH_MAX];
+static int foreground;
+static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
+static int pipeline = FALSE;
+static int tty_initialized;
+static int ttymode = TERM_COOKED;
+static pid_t ppgrp, child;
+static struct io_buffer *iobufs;
+
+static void flush_output __P((void));
+static int exec_monitor __P((const char *path, char *argv[],
+ char *envp[], int, int));
+static void exec_pty __P((const char *path, char *argv[],
+ char *envp[], int));
+static void sigwinch __P((int s));
+static void sync_ttysize __P((int src, int dst));
+static void deliver_signal __P((pid_t pid, int signo));
+static int safe_close __P((int fd));
+
+/*
+ * Allocate a pty if /dev/tty is a tty.
+ * Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
+ * and slavename globals.
+ */
+void
+pty_setup(uid)
+ uid_t uid;
+{
+ io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
+ if (io_fds[SFD_USERTTY] != -1) {
+ if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE],
+ slavename, sizeof(slavename), uid))
+ error(1, "Can't get pty");
+ }
+}
+
+/*
+ * Check whether we are running in the foregroup.
+ * Updates the foreground global and does lazy init of the
+ * the pty slave as needed.
+ */
+static void
+check_foreground()
+{
+ if (io_fds[SFD_USERTTY] != -1) {
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+ if (foreground && !tty_initialized) {
+ if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+ tty_initialized = 1;
+ sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ }
+ }
+ }
+}
+
+/*
+ * Suspend sudo if the underlying command is suspended.
+ * Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
+ */
+int
+suspend_parent(signo)
+ int signo;
+{
+ sigaction_t sa, osa;
+ int n, oldmode = ttymode, rval = 0;
+
+ switch (signo) {
+ case SIGTTOU:
+ case SIGTTIN:
+ /*
+ * If we are the foreground process, just resume the child.
+ * Otherwise, re-send the signal with the handler disabled.
+ */
+ if (!foreground)
+ check_foreground();
+ if (foreground) {
+ if (ttymode != TERM_RAW) {
+ do {
+ n = term_raw(io_fds[SFD_USERTTY], 0);
+ } while (!n && errno == EINTR);
+ ttymode = TERM_RAW;
+ }
+ rval = SIGUSR1; /* resume child in foreground */
+ break;
+ }
+ ttymode = TERM_RAW;
+ /* FALLTHROUGH */
+ case SIGSTOP:
+ case SIGTSTP:
+ /* Flush any remaining output before suspending. */
+ flush_output();
+
+ /* Restore original tty mode before suspending. */
+ if (oldmode != TERM_COOKED) {
+ do {
+ n = term_restore(io_fds[SFD_USERTTY], 0);
+ } while (!n && errno == EINTR);
+ }
+
+ /* Suspend self and continue child when we resume. */
+ sa.sa_handler = SIG_DFL;
+ sigaction(signo, &sa, &osa);
+ killpg(ppgrp, signo);
+
+ /* Check foreground/background status on resume. */
+ check_foreground();
+
+ /*
+ * Only modify term if we are foreground process and either
+ * the old tty mode was not cooked or child got SIGTT{IN,OU}
+ */
+ if (ttymode != TERM_COOKED) {
+ if (foreground) {
+ /* Set raw mode. */
+ do {
+ n = term_raw(io_fds[SFD_USERTTY], 0);
+ } while (!n && errno == EINTR);
+ } else {
+ /* Background process, no access to tty. */
+ ttymode = TERM_COOKED;
+ }
+ }
+
+ sigaction(signo, &osa, NULL);
+ rval = ttymode == TERM_RAW ? SIGUSR1 : SIGUSR2;
+ break;
+ }
+
+ return(rval);
+}
+
+/*
+ * Kill child with increasing urgency.
+ */
+static void
+terminate_child(pid, use_pgrp)
+ pid_t pid;
+ int use_pgrp;
+{
+ /*
+ * Note that SIGCHLD will interrupt the sleep()
+ */
+ if (use_pgrp) {
+ killpg(pid, SIGHUP);
+ killpg(pid, SIGTERM);
+ sleep(2);
+ killpg(pid, SIGKILL);
+ } else {
+ kill(pid, SIGHUP);
+ kill(pid, SIGTERM);
+ sleep(2);
+ kill(pid, SIGKILL);
+ }
+}
+
+/*
+ * Allocate a new io_buffer struct and insert it at the head of the list.
+ * Returns the new head element.
+ */
+static struct io_buffer *
+io_buf_new(rfd, wfd, action, head)
+ int rfd;
+ int wfd;
+ int (*action) __P((const char *, unsigned int));
+ struct io_buffer *head;
+{
+ struct io_buffer *iob;
+
+ iob = emalloc(sizeof(*iob));
+ zero_bytes(iob, sizeof(*iob));
+ iob->rfd = rfd;
+ iob->wfd = wfd;
+ iob->action = action;
+ iob->next = head;
+ return iob;
+}
+
+/*
+ * Read/write iobufs depending on fdsr and fdsw.
+ * Fills in cstat on error.
+ * Returns the number of errors.
+ */
+int
+perform_io(fdsr, fdsw, cstat)
+ fd_set *fdsr;
+ fd_set *fdsw;
+ struct command_status *cstat;
+{
+ struct io_buffer *iob;
+ int n, errors = 0;
+
+ for (iob = iobufs; iob; iob = iob->next) {
+ if (iob->rfd != -1 && FD_ISSET(iob->rfd, fdsr)) {
+ do {
+ n = read(iob->rfd, iob->buf + iob->len,
+ sizeof(iob->buf) - iob->len);
+ } while (n == -1 && errno == EINTR);
+ if (n == -1) {
+ if (errno != EAGAIN)
+ break;
+ } else if (n == 0) {
+ /* got EOF */
+ safe_close(iob->rfd);
+ iob->rfd = -1;
+ } else {
+ if (!iob->action(iob->buf + iob->len, n))
+ terminate_child(child, TRUE);
+ iob->len += n;
+ }
+ }
+ if (iob->wfd != -1 && FD_ISSET(iob->wfd, fdsw)) {
+ do {
+ n = write(iob->wfd, iob->buf + iob->off,
+ iob->len - iob->off);
+ } while (n == -1 && errno == EINTR);
+ if (n == -1) {
+ if (errno == EPIPE) {
+ /* other end of pipe closed */
+ if (iob->rfd != -1) {
+ safe_close(iob->rfd);
+ iob->rfd = -1;
+ }
+ safe_close(iob->wfd);
+ iob->wfd = -1;
+ continue;
+ }
+ if (errno != EAGAIN)
+ errors++;
+ } else {
+ iob->off += n;
+ }
+ }
+ }
+ if (errors && cstat != NULL) {
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ }
+ return errors;
+}
+
+/*
+ * Fork a monitor process which runs the actual command as its own child
+ * process with std{in,out,err} hooked up to the pty or pipes as appropriate.
+ * Returns the child pid.
+ */
+int
+fork_pty(path, argv, envp, sv, rbac_enabled, maxfd)
+ const char *path;
+ char *argv[];
+ char *envp[];
+ int sv[2];
+ int rbac_enabled;
+ int *maxfd;
+{
+ struct command_status cstat;
+ struct io_buffer *iob;
+ int io_pipe[3][2], n;
+ sigaction_t sa;
+
+ ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+
+ zero_bytes(&sa, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sigwinch;
+ sigaction(SIGWINCH, &sa, NULL);
+ }
+
+ /*
+ * Setup stdin/stdout/stderr for child, to be duped after forking.
+ */
+ io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
+ io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
+ io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
+
+ /* Copy /dev/tty -> pty master */
+ if (io_fds[SFD_USERTTY] != -1) {
+ iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+ log_ttyin, iobufs);
+
+ /* Copy pty master -> /dev/tty */
+ iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
+ log_ttyout, iobufs);
+
+ /* Are we the foreground process? */
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+ }
+
+ /*
+ * If either stdin, stdout or stderr is not a tty we use a pipe
+ * to interpose ourselves instead of duping the pty fd.
+ */
+ memset(io_pipe, 0, sizeof(io_pipe));
+ if (!isatty(STDIN_FILENO)) {
+ pipeline = TRUE;
+ if (pipe(io_pipe[STDIN_FILENO]) != 0)
+ error(1, "unable to create pipe");
+ iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
+ log_stdin, iobufs);
+ io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
+ }
+ if (!isatty(STDOUT_FILENO)) {
+ pipeline = TRUE;
+ if (pipe(io_pipe[STDOUT_FILENO]) != 0)
+ error(1, "unable to create pipe");
+ iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
+ log_stdout, iobufs);
+ io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
+ }
+ if (!isatty(STDERR_FILENO)) {
+ if (pipe(io_pipe[STDERR_FILENO]) != 0)
+ error(1, "unable to create pipe");
+ iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
+ log_stderr, iobufs);
+ io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
+ }
+
+ /* Job control signals to relay from parent to child. */
+ sa.sa_flags = 0; /* do not restart syscalls */
+ sa.sa_handler = handler;
+ sigaction(SIGTSTP, &sa, NULL);
+#if 0 /* XXX - add these? */
+ sigaction(SIGTTIN, &sa, NULL);
+ sigaction(SIGTTOU, &sa, NULL);
+#endif
+
+ if (foreground) {
+ /* Copy terminal attrs from user tty -> pty slave. */
+ if (term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+ tty_initialized = 1;
+ sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ }
+
+ /* Start out in raw mode if we are not part of a pipeline. */
+ if (!pipeline) {
+ ttymode = TERM_RAW;
+ do {
+ n = term_raw(io_fds[SFD_USERTTY], 0);
+ } while (!n && errno == EINTR);
+ if (!n)
+ error(1, "Can't set terminal to raw mode");
+ }
+ }
+
+ child = fork();
+ switch (child) {
+ case -1:
+ error(1, "fork");
+ break;
+ case 0:
+ /* child */
+ close(sv[0]);
+ fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+#ifdef HAVE_SELINUX
+ if (rbac_enabled)
+ selinux_setup(user_role, user_type, slavename, io_fds[SFD_SLAVE]);
+#endif
+ if (exec_setup() == TRUE) {
+ /* Close the other end of the stdin/stdout/stderr pipes and exec. */
+ if (io_pipe[STDIN_FILENO][1])
+ close(io_pipe[STDIN_FILENO][1]);
+ if (io_pipe[STDOUT_FILENO][0])
+ close(io_pipe[STDOUT_FILENO][0]);
+ if (io_pipe[STDERR_FILENO][0])
+ close(io_pipe[STDERR_FILENO][0]);
+ exec_monitor(path, argv, envp, sv[1], rbac_enabled);
+ }
+ cstat.type = CMD_ERRNO;
+ cstat.val = errno;
+ send(sv[1], &cstat, sizeof(cstat), 0);
+ _exit(1);
+ }
+
+ /* Close the other end of the stdin/stdout/stderr pipes. */
+ if (io_pipe[STDIN_FILENO][0])
+ close(io_pipe[STDIN_FILENO][0]);
+ if (io_pipe[STDOUT_FILENO][1])
+ close(io_pipe[STDOUT_FILENO][1]);
+ if (io_pipe[STDERR_FILENO][1])
+ close(io_pipe[STDERR_FILENO][1]);
+
+ for (iob = iobufs; iob; iob = iob->next) {
+ /* Adjust maxfd. */
+ if (iob->rfd > *maxfd)
+ *maxfd = iob->rfd;
+ if (iob->wfd > *maxfd)
+ *maxfd = iob->wfd;
+
+ /* Set non-blocking mode. */
+ n = fcntl(iob->rfd, F_GETFL, 0);
+ if (n != -1 && !ISSET(n, O_NONBLOCK))
+ (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK);
+ n = fcntl(iob->wfd, F_GETFL, 0);
+ if (n != -1 && !ISSET(n, O_NONBLOCK))
+ (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK);
+ }
+
+ return child;
+}
+
+/*
+ * Flush any remaining output and restore /dev/tty to the way we found it.
+ * If the command died due to a signal, writes the reason to stdout.
+ */
+void
+pty_close(cstat)
+ struct command_status *cstat;
+{
+ int n;
+
+ /* Flush any remaining output (the plugin already got it) */
+ if (io_fds[SFD_USERTTY] != -1) {
+ n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0);
+ if (n != -1 && ISSET(n, O_NONBLOCK)) {
+ CLR(n, O_NONBLOCK);
+ (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n);
+ }
+ }
+ flush_output();
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ do {
+ n = term_restore(io_fds[SFD_USERTTY], 0);
+ } while (!n && errno == EINTR);
+ }
+
+ /* If child was signalled, write the reason to stdout like the shell. */
+ if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
+ int signo = WTERMSIG(cstat->val);
+ if (signo && signo != SIGINT && signo != SIGPIPE) {
+ char *reason = strsignal(signo);
+ n = io_fds[SFD_USERTTY] != -1 ?
+ io_fds[SFD_USERTTY] : STDOUT_FILENO;
+ write(n, reason, strlen(reason));
+ if (WCOREDUMP(cstat->val))
+ write(n, " (core dumped)", 14);
+ write(n, "\n", 1);
+ }
+ }
+}
+
+
+/*
+ * Fill in fdsr and fdsw based on the io buffers list.
+ * Called prior to select().
+ */
+void
+fd_set_iobs(fdsr, fdsw)
+ fd_set *fdsr;
+ fd_set *fdsw;
+{
+ struct io_buffer *iob;
+
+ for (iob = iobufs; iob; iob = iob->next) {
+ if (iob->rfd == -1 && iob->wfd == -1)
+ continue;
+ if (iob->off == iob->len) {
+ iob->off = iob->len = 0;
+ /* Forward the EOF from reader to writer. */
+ if (iob->rfd == -1) {
+ safe_close(iob->wfd);
+ iob->wfd = -1;
+ }
+ }
+ /* Don't read/write /dev/tty if we are not in the foreground. */
+ if (iob->rfd != -1 &&
+ (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) {
+ if (iob->len != sizeof(iob->buf))
+ FD_SET(iob->rfd, fdsr);
+ }
+ if (iob->wfd != -1 &&
+ (foreground || iob->wfd != io_fds[SFD_USERTTY])) {
+ if (iob->len > iob->off)
+ FD_SET(iob->wfd, fdsw);
+ }
+ }
+}
+
+/*
+ * Deliver a relayed signal to the command.
+ */
+static void
+deliver_signal(pid, signo)
+ pid_t pid;
+ int signo;
+{
+ int status;
+
+ /* Handle signal from parent. */
+ switch (signo) {
+ case SIGKILL:
+ _exit(1); /* XXX */
+ /* NOTREACHED */
+ case SIGPIPE:
+ case SIGHUP:
+ case SIGTERM:
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTSTP:
+ /* relay signal to child */
+ killpg(pid, signo);
+ break;
+ case SIGALRM:
+ terminate_child(pid, TRUE);
+ break;
+ case SIGUSR1:
+ /* foreground process, grant it controlling tty. */
+ do {
+ status = tcsetpgrp(io_fds[SFD_SLAVE], pid);
+ } while (status == -1 && errno == EINTR);
+ killpg(pid, SIGCONT);
+ break;
+ case SIGUSR2:
+ /* background process, I take controlling tty. */
+ do {
+ status = tcsetpgrp(io_fds[SFD_SLAVE], getpid());
+ } while (status == -1 && errno == EINTR);
+ killpg(pid, SIGCONT);
+ break;
+ default:
+ warningx("unexpected signal from child: %d", signo);
+ break;
+ }
+}
+
+/*
+ * Send status to parent over socketpair.
+ * Return value is the same as send(2).
+ */
+static int
+send_status(fd, cstat)
+ int fd;
+ struct command_status *cstat;
+{
+ int n = -1;
+
+ if (cstat->type != CMD_INVALID) {
+ do {
+ n = send(fd, cstat, sizeof(*cstat), 0);
+ } while (n == -1 && errno == EINTR);
+ cstat->type = CMD_INVALID; /* prevent re-sending */
+ }
+ return n;
+}
+
+/*
+ * Wait for child status after receiving SIGCHLD.
+ * If the child was stopped, the status is send back to the parent.
+ * Otherwise, cstat is filled in but not sent.
+ * Returns TRUE if child is still alive, else FALSE.
+ */
+static int
+handle_sigchld(backchannel, cstat)
+ int backchannel;
+ struct command_status *cstat;
+{
+ int status, alive = TRUE;
+ pid_t pid;
+
+ /* read child status */
+ do {
+ pid = waitpid(child, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ if (pid == child) {
+ if (cstat->type != CMD_ERRNO) {
+ cstat->type = CMD_WSTATUS;
+ cstat->val = status;
+ if (WIFSTOPPED(status)) {
+ if (send_status(backchannel, cstat) == -1)
+ return alive; /* XXX */
+ }
+ }
+ if (!WIFSTOPPED(status))
+ alive = FALSE;
+ }
+ return alive;
+}
+
+/*
+ * Monitor process that creates a new session with the controlling tty,
+ * resets signal handlers and forks a child to call exec_pty().
+ * Waits for status changes from the command and relays them to the
+ * parent and relays signals from the parent to the command.
+ * Returns an error if fork(2) fails, else calls _exit(2).
+ */
+int
+exec_monitor(path, argv, envp, backchannel, rbac)
+ const char *path;
+ char *argv[];
+ char *envp[];
+ int backchannel;
+ int rbac;
+{
+ struct command_status cstat;
+ struct timeval tv;
+ fd_set *fdsr;
+ sigaction_t sa;
+ int errpipe[2], maxfd, n, status;
+ int alive = TRUE;
+
+ /* Close unused fds. */
+ if (io_fds[SFD_MASTER] != -1)
+ close(io_fds[SFD_MASTER]);
+ if (io_fds[SFD_USERTTY] != -1)
+ close(io_fds[SFD_USERTTY]);
+
+ /* Reset SIGWINCH and SIGALRM. */
+ zero_bytes(&sa, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGWINCH, &sa, NULL);
+ sigaction(SIGALRM, &sa, NULL);
+
+ /* Ignore any SIGTTIN or SIGTTOU we get. */
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGTTIN, &sa, NULL);
+ sigaction(SIGTTOU, &sa, NULL);
+
+ /* Note: HP-UX select() will not be interrupted if SA_RESTART set */
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /*
+ * Start a new session with the parent as the session leader
+ * and the slave pty as the controlling terminal.
+ * This allows us to be notified when the child has been suspended.
+ */
+#ifdef HAVE_SETSID
+ if (setsid() == -1) {
+ warning("setsid");
+ goto bad;
+ }
+#else
+# ifdef TIOCNOTTY
+ n = open(_PATH_TTY, O_RDWR|O_NOCTTY);
+ if (n >= 0) {
+ /* Disconnect from old controlling tty. */
+ if (ioctl(n, TIOCNOTTY, NULL) == -1)
+ warning("cannot disconnect controlling tty");
+ close(n);
+ }
+# endif
+ setpgrp(0, 0);
+#endif
+ if (io_fds[SFD_SLAVE] != -1) {
+#ifdef TIOCSCTTY
+ if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
+ error(1, "unable to set controlling tty");
+#else
+ /* Set controlling tty by reopening slave. */
+ if ((n = open(slavename, O_RDWR)) >= 0)
+ close(n);
+#endif
+ }
+
+ /*
+ * If stdin/stdout is not a tty, start command in the background
+ * since it might be part of a pipeline that reads from /dev/tty.
+ * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
+ * when it needs access to the controlling tty.
+ */
+ if (pipeline)
+ foreground = 0;
+
+ /* Start command and wait for it to stop or exit */
+ if (pipe(errpipe) == -1)
+ error(1, "unable to create pipe");
+ child = fork();
+ if (child == -1) {
+ warning("Can't fork");
+ goto bad;
+ }
+ if (child == 0) {
+ /* We pass errno back to our parent via pipe on exec failure. */
+ close(backchannel);
+ close(errpipe[0]);
+ fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
+
+ /* setup tty and exec command */
+ exec_pty(path, argv, envp, rbac);
+ cstat.type = CMD_ERRNO;
+ cstat.val = errno;
+ write(errpipe[1], &cstat, sizeof(cstat));
+ _exit(1);
+ }
+ close(errpipe[1]);
+
+ /* If any of stdin/stdout/stderr are pipes, close them in parent. */
+ if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDERR]);
+
+ /*
+ * Put child in its own process group. If we are starting the command
+ * in the foreground, assign its pgrp to the tty.
+ */
+ setpgid(child, child);
+ if (foreground) {
+ do {
+ status = tcsetpgrp(io_fds[SFD_SLAVE], child);
+ } while (status == -1 && errno == EINTR);
+ }
+
+ /* Wait for errno on pipe, signal on backchannel or for SIGCHLD */
+ maxfd = MAX(errpipe[0], backchannel);
+ fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+ zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+ zero_bytes(&cstat, sizeof(cstat));
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ for (;;) {
+ /* Read child status. */
+ if (recvsig[SIGCHLD]) {
+ recvsig[SIGCHLD] = FALSE;
+ alive = handle_sigchld(backchannel, &cstat);
+ }
+
+ /* Check for signal on backchannel or errno on errpipe. */
+ FD_SET(backchannel, fdsr);
+ if (errpipe[0] != -1)
+ FD_SET(errpipe[0], fdsr);
+ maxfd = MAX(errpipe[0], backchannel);
+
+ if (recvsig[SIGCHLD])
+ continue;
+ /* If command exited we just poll, there may be data on errpipe. */
+ n = select(maxfd + 1, fdsr, NULL, NULL, alive ? NULL : &tv);
+ if (n <= 0) {
+ if (n == 0)
+ goto done;
+ if (errno == EINTR)
+ continue;
+ error(1, "select failed");
+ }
+
+ if (errpipe[0] != -1 && FD_ISSET(errpipe[0], fdsr)) {
+ /* read errno or EOF from command pipe */
+ n = read(errpipe[0], &cstat, sizeof(cstat));
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ warning("error reading from pipe");
+ goto done;
+ }
+ /* Got errno or EOF, either way we are done with errpipe. */
+ FD_CLR(errpipe[0], fdsr);
+ close(errpipe[0]);
+ errpipe[0] = -1;
+ }
+ if (FD_ISSET(backchannel, fdsr)) {
+ struct command_status cstmp;
+
+ /* read command from backchannel, should be a signal */
+ n = recv(backchannel, &cstmp, sizeof(cstmp), 0);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ warning("error reading from socketpair");
+ goto done;
+ }
+ if (cstmp.type != CMD_SIGNO) {
+ warningx("unexpected reply type on backchannel: %d", cstmp.type);
+ continue;
+ }
+ deliver_signal(child, cstmp.val);
+ }
+ }
+
+done:
+ if (alive) {
+ /* XXX An error occurred, should send an error back. */
+ kill(child, SIGKILL);
+ } else {
+ /* Send parent status. */
+ send_status(backchannel, &cstat);
+ }
+ _exit(1);
+
+bad:
+ return errno;
+}
+
+/*
+ * Flush any output buffered in iobufs or readable from the fds.
+ * Does not read from /dev/tty.
+ */
+static void
+flush_output()
+{
+ struct io_buffer *iob;
+ struct timeval tv;
+ fd_set *fdsr, *fdsw;
+ int nready, nwriters, maxfd = -1;
+
+ /* Determine maxfd */
+ for (iob = iobufs; iob; iob = iob->next) {
+ if (iob->rfd > maxfd)
+ maxfd = iob->rfd;
+ if (iob->wfd > maxfd)
+ maxfd = iob->wfd;
+ }
+ if (maxfd == -1)
+ return;
+
+ fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+ fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
+ for (;;) {
+ zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+ zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
+
+ nwriters = 0;
+ for (iob = iobufs; iob; iob = iob->next) {
+ /* Don't read from /dev/tty while flushing. */
+ if (io_fds[SFD_USERTTY] != -1 && iob->rfd == io_fds[SFD_USERTTY])
+ continue;
+ if (iob->rfd == -1 && iob->wfd == -1)
+ continue;
+ if (iob->off == iob->len) {
+ iob->off = iob->len = 0;
+ /* Forward the EOF from reader to writer. */
+ if (iob->rfd == -1) {
+ safe_close(iob->wfd);
+ iob->wfd = -1;
+ }
+ }
+ if (iob->rfd != -1) {
+ if (iob->len != sizeof(iob->buf))
+ FD_SET(iob->rfd, fdsr);
+ }
+ if (iob->wfd != -1) {
+ if (iob->len > iob->off) {
+ nwriters++;
+ FD_SET(iob->wfd, fdsw);
+ }
+ }
+ }
+
+ /* Don't sleep in select if there are no buffers that need writing. */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ nready = select(maxfd + 1, fdsr, fdsw, NULL, nwriters ? NULL : &tv);
+ if (nready <= 0) {
+ if (nready == 0)
+ break; /* all I/O flushed */
+ if (errno == EINTR)
+ continue;
+ error(1, "select failed");
+ }
+ if (perform_io(fdsr, fdsw, NULL) != 0)
+ break;
+ }
+ efree(fdsr);
+ efree(fdsw);
+}
+
+/*
+ * Sets up std{in,out,err} and executes the actual command.
+ * Returns only if execve() fails.
+ */
+static void
+exec_pty(path, argv, envp, rbac_enabled)
+ const char *path;
+ char *argv[];
+ char *envp[];
+ int rbac_enabled;
+{
+ sigaction_t sa;
+ pid_t self = getpid();
+
+ /* Reset signal handlers. */
+ zero_bytes(&sa, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTSTP, &sa, NULL);
+ sigaction(SIGTTIN, &sa, NULL);
+ sigaction(SIGTTOU, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
+ sigaction(SIGUSR2, &sa, NULL);
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* Set child process group here too to avoid a race. */
+ setpgid(0, self);
+
+ /* Wire up standard fds, note that stdout/stderr may be pipes. */
+ dup2(io_fds[SFD_STDIN], STDIN_FILENO);
+ dup2(io_fds[SFD_STDOUT], STDOUT_FILENO);
+ dup2(io_fds[SFD_STDERR], STDERR_FILENO);
+
+ /* Wait for parent to grant us the tty if we are foreground. */
+ if (foreground) {
+ while (tcgetpgrp(io_fds[SFD_SLAVE]) != self)
+ ; /* spin */
+ }
+
+ /* We have guaranteed that the slave fd is > 2 */
+ if (io_fds[SFD_SLAVE] != -1)
+ close(io_fds[SFD_SLAVE]);
+ if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDERR]);
+
+ closefrom(def_closefrom);
+#ifdef HAVE_SELINUX
+ if (rbac_enabled)
+ selinux_execve(path, argv, envp);
+ else
+#endif
+ my_execve(path, argv, envp);
+}
+
+/*
+ * Propagates tty size change signals to pty being used by the command.
+ */
+static void
+sync_ttysize(src, dst)
+ int src;
+ int dst;
+{
+#ifdef TIOCGSIZE
+ struct ttysize tsize;
+ pid_t pgrp;
+
+ if (ioctl(src, TIOCGSIZE, &tsize) == 0) {
+ ioctl(dst, TIOCSSIZE, &tsize);
+#ifdef TIOCGPGRP
+ if (ioctl(dst, TIOCGPGRP, &pgrp) == 0)
+ killpg(pgrp, SIGWINCH);
+#endif
+ }
+#endif
+}
+
+/*
+ * Handler for SIGWINCH in parent.
+ */
+static void
+sigwinch(s)
+ int s;
+{
+ int serrno = errno;
+
+ sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ errno = serrno;
+}
+
+/*
+ * Only close the fd if it is not /dev/tty or std{in,out,err}.
+ * Return value is the same as send(2).
+ */
+static int
+safe_close(fd)
+ int fd;
+{
+ /* Avoid closing /dev/tty or std{in,out,err}. */
+ if (fd < 3 || fd == io_fds[SFD_USERTTY]) {
+ errno = EINVAL;
+ return -1;
+ }
+ return close(fd);
+}
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++;
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)
/* 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);
}
#include "sudo.h"
#include "lbuf.h"
#include "interfaces.h"
+#include <sudo_usage.h>
#ifdef USING_NONUNIX_GROUPS
# include "nonunix.h"
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
(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);
{
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.
*/
* 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;
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:
/* 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 *));
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;
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.
* 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 */