#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
-#define SFD_MASTER 0
-#define SFD_SLAVE 1
-#define SFD_USERTTY 2
+#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_CBREAK 1
# define ts_cols ws_col
#endif
-struct script_buf {
- int len; /* buffer length (how much read in) */
+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)(char *buf, unsigned int len);
char buf[16 * 1024];
};
-static int script_fds[3] = { -1, -1, -1 };
-static int ttyout;
+static int script_fds[6] = { -1, -1, -1, -1, -1, -1};
+static int ttyout = TRUE;
static sig_atomic_t recvsig[NSIG];
static sig_atomic_t ttymode = TERM_COOKED;
static char slavename[PATH_MAX];
-static int suspend_parent(int signo, struct script_buf *output);
-static void flush_output(struct script_buf *output);
+static int suspend_parent(int signo, int fd, struct io_buffer *output);
+static void flush_output(struct io_buffer *iobufs);
static void handler(int s);
static int script_child(const char *path, char *argv[], char *envp[], int, int);
static void script_run(const char *path, char *argv[], char *envp[], int);
* Returns SIGUSR1 if the child should be resume in foreground else SIGUSR2.
*/
static int
-suspend_parent(int signo, struct script_buf *output)
+suspend_parent(int signo, int fd, struct io_buffer *iobufs)
{
sigaction_t sa, osa;
int n, oldmode = ttymode, rval = 0;
/* FALLTHROUGH */
case SIGSTOP:
case SIGTSTP:
- /* Flush any remaining output to master tty. */
- flush_output(output);
+ /* Flush any remaining output before suspending. */
+ flush_output(iobufs);
/* Restore original tty mode before suspending. */
if (oldmode != TERM_COOKED) {
ttymode == TERM_CBREAK);
} while (!n && errno == EINTR);
} else {
- /* background process, no access to tty. */
+ /* Background process, no access to tty. */
ttymode = TERM_COOKED;
}
}
}
}
+static struct io_buffer *
+io_buf_new(int rfd, int wfd, int (*action)(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;
+}
+
/*
* 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.
struct command_status *cstat)
{
sigaction_t sa;
- struct script_buf input, output;
+ struct io_buffer *iob, *iobufs = NULL;
int n, nready;
- int sv[2];
+ int pv[2], sv[2];
fd_set *fdsr, *fdsw;
int rbac_enabled = 0;
int log_io, maxfd;
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
- /* Ignore SIGPIPE from other end of socketpair. */
+ /* Ignore SIGPIPE, check errno instead... */
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
/* Are we the foreground process? */
foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == ppgrp;
- /* If stdout is not a tty we handle post-processing differently. */
- ttyout = isatty(STDOUT_FILENO);
+ /*
+ * Setup stdin/stdout/stderr for child, to be duped after forking.
+ */
+ /* XXX - use a pipe for stdin if not a tty? */
+ script_fds[SFD_STDIN] = isatty(STDIN_FILENO) ?
+ script_fds[SFD_SLAVE] : STDIN_FILENO;
+ script_fds[SFD_STDOUT] = script_fds[SFD_SLAVE];
+ script_fds[SFD_STDERR] = script_fds[SFD_SLAVE];
+
+ /* Copy /dev/tty -> pty master */
+ iobufs = io_buf_new(script_fds[SFD_USERTTY], script_fds[SFD_MASTER],
+ log_input, iobufs);
+
+ /* Copy pty master -> /dev/tty */
+ iobufs = io_buf_new(script_fds[SFD_MASTER], script_fds[SFD_USERTTY],
+ log_output, iobufs);
+
+ /*
+ * If either stdout or stderr is not a tty we use a pipe
+ * to interpose ourselves instead of duping the pty fd.
+ * NOTE: we don't currently log tty/stdout/stderr separately.
+ */
+ if (!isatty(STDOUT_FILENO)) {
+ ttyout = FALSE;
+ if (pipe(pv) != 0)
+ error(1, "unable to create pipe");
+ iobufs = io_buf_new(pv[0], STDOUT_FILENO, log_output, iobufs);
+ script_fds[SFD_STDOUT] = pv[1];
+ }
+ if (!isatty(STDERR_FILENO)) {
+ if (pipe(pv) != 0)
+ error(1, "unable to create pipe");
+ iobufs = io_buf_new(pv[0], STDERR_FILENO, log_output, iobufs);
+ script_fds[SFD_STDERR] = pv[1];
+ }
/* Job control signals to relay from parent to child. */
sa.sa_flags = 0; /* do not restart syscalls */
maxfd = sv[0];
if (log_io) {
- if (maxfd < script_fds[SFD_MASTER])
- maxfd = script_fds[SFD_MASTER];
- if (maxfd < script_fds[SFD_USERTTY])
- maxfd = script_fds[SFD_USERTTY];
-
- n = fcntl(script_fds[SFD_MASTER], F_GETFL, 0);
- if (n != -1) {
- n |= O_NONBLOCK;
- (void) fcntl(script_fds[SFD_MASTER], F_SETFL, n);
- }
- n = fcntl(script_fds[SFD_USERTTY], F_GETFL, 0);
- if (n != -1) {
- n |= O_NONBLOCK;
- (void) fcntl(script_fds[SFD_USERTTY], F_SETFL, n);
- }
- n = fcntl(STDOUT_FILENO, F_GETFL, 0);
- if (n != -1) {
- n |= O_NONBLOCK;
- (void) fcntl(STDOUT_FILENO, F_SETFL, n);
+ /* Close the writer end of the stdout/stderr pipes. */
+ if (script_fds[SFD_STDOUT] != script_fds[SFD_SLAVE])
+ close(script_fds[SFD_STDOUT]);
+ if (script_fds[SFD_STDERR] != script_fds[SFD_SLAVE])
+ close(script_fds[SFD_STDERR]);
+
+ 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);
}
}
*/
fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
- zero_bytes(&input, sizeof(input));
- zero_bytes(&output, sizeof(output));
for (;;) {
if (recvsig[SIGCHLD]) {
pid_t pid;
zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
- if (log_io) {
- if (input.off == input.len)
- input.off = input.len = 0;
- if (output.off == output.len)
- output.off = output.len = 0;
-
- if (ttymode == TERM_RAW && input.len != sizeof(input.buf))
- FD_SET(script_fds[SFD_USERTTY], fdsr);
- if (output.len != sizeof(output.buf))
- FD_SET(script_fds[SFD_MASTER], fdsr);
- if (output.len > output.off)
- FD_SET(STDOUT_FILENO, fdsw);
- if (input.len > input.off)
- FD_SET(script_fds[SFD_MASTER], fdsw);
- }
FD_SET(sv[0], fdsr);
+ for (iob = iobufs; iob; iob = iob->next) {
+ if (iob->off == iob->len)
+ iob->off = iob->len = 0;
+ /* Don't read/write /dev/tty if we are not in the foreground. */
+ if (ttymode == TERM_RAW || iob->rfd != script_fds[SFD_USERTTY]) {
+ if (iob->len != sizeof(iob->buf))
+ FD_SET(iob->rfd, fdsr);
+ }
+ if (ttymode == TERM_RAW || iob->wfd != script_fds[SFD_USERTTY]) {
+ if (iob->len > iob->off)
+ FD_SET(iob->wfd, fdsw);
+ }
+ }
for (n = 0; n < NSIG; n++) {
if (recvsig[n] && n != SIGCHLD) {
if (log_io) {
if (WIFSTOPPED(cstat->val)) {
/* Suspend parent and tell child how to resume on return. */
sudo_debug(8, "child stopped, suspending parent");
- n = suspend_parent(WSTOPSIG(cstat->val), &output);
+ n = suspend_parent(WSTOPSIG(cstat->val), script_fds[SFD_USERTTY], iobufs);
recvsig[n] = TRUE;
continue;
} else {
break;
}
}
- if (!log_io)
- continue;
if (FD_ISSET(sv[0], fdsw)) {
for (n = 0; n < NSIG; n++) {
}
}
}
- if (FD_ISSET(script_fds[SFD_USERTTY], fdsr)) {
- n = read(script_fds[SFD_USERTTY], input.buf + input.len,
- sizeof(input.buf) - input.len);
- if (n == -1) {
- if (errno == EINTR)
- continue;
- if (errno != EAGAIN)
- break;
- } else {
- if (n == 0)
- break; /* got EOF */
- if (!log_input(input.buf + input.len, n))
- terminate_child(child, TRUE);
- input.len += n;
- }
- }
- if (FD_ISSET(script_fds[SFD_MASTER], fdsw)) {
- n = write(script_fds[SFD_MASTER], input.buf + input.off,
- input.len - input.off);
- if (n == -1) {
- if (errno == EINTR)
- continue;
- if (errno != EAGAIN)
- break;
- } else {
- input.off += n;
- }
- }
- if (FD_ISSET(script_fds[SFD_MASTER], fdsr)) {
- n = read(script_fds[SFD_MASTER], output.buf + output.len,
- sizeof(output.buf) - output.len);
- if (n == -1) {
- if (errno == EINTR)
- continue;
- if (errno != EAGAIN)
- break;
- } else {
- if (n == 0)
- break; /* got EOF */
- if (!log_output(output.buf + output.len, n))
- terminate_child(child, TRUE);
- output.len += n;
+
+ for (iob = iobufs; iob; iob = iob->next) {
+ if (FD_ISSET(iob->rfd, fdsr)) {
+ n = read(iob->rfd, iob->buf + iob->len,
+ sizeof(iob->buf) - iob->len);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN)
+ break;
+ } else {
+ if (n == 0)
+ break; /* got EOF */
+ if (!iob->action(iob->buf + iob->len, n))
+ terminate_child(child, TRUE);
+ iob->len += n;
+ }
}
- }
- if (FD_ISSET(STDOUT_FILENO, fdsw)) {
- n = write(STDOUT_FILENO, output.buf + output.off,
- output.len - output.off);
- if (n == -1) {
- if (errno == EINTR)
- continue;
- if (errno != EAGAIN)
- break;
- } else {
- output.off += n;
+ if (FD_ISSET(iob->wfd, fdsw)) {
+ n = write(iob->wfd, iob->buf + iob->off,
+ iob->len - iob->off);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN)
+ break;
+ } else {
+ iob->off += n;
+ }
}
}
}
if (log_io) {
- /* Flush any remaining output to stdout (plugin already got it) */
- n = fcntl(STDOUT_FILENO, F_GETFL, 0);
- if (n != -1) {
- n &= ~O_NONBLOCK;
- (void) fcntl(STDOUT_FILENO, F_SETFL, n);
+ /* Flush any remaining output (the plugin already got it) */
+ for (iob = iobufs; iob; iob = iob->next) {
+ n = fcntl(iob->wfd, F_GETFL, 0);
+ if (n != -1 && ISSET(n, O_NONBLOCK)) {
+ CLR(n, O_NONBLOCK);
+ (void) fcntl(iob->wfd, F_SETFL, n);
+ }
}
- flush_output(&output);
+ flush_output(iobufs);
do {
n = term_restore(script_fds[SFD_USERTTY], 0);
int signo = WTERMSIG(cstat->val);
if (signo && signo != SIGINT && signo != SIGPIPE) {
char *reason = strsignal(signo);
- write(STDOUT_FILENO, reason, strlen(reason));
+ write(script_fds[SFD_USERTTY], reason, strlen(reason));
if (WCOREDUMP(cstat->val))
- write(STDOUT_FILENO, " (core dumped)", 14);
- write(STDOUT_FILENO, "\n", 1);
+ write(script_fds[SFD_USERTTY], " (core dumped)", 14);
+ write(script_fds[SFD_USERTTY], "\n", 1);
}
}
}
}
static void
-flush_output(struct script_buf *output)
+flush_output(struct io_buffer *iobufs)
{
+ struct io_buffer *iob, output;
int n;
- while (output->len > output->off) {
- n = write(STDOUT_FILENO, output->buf + output->off,
- output->len - output->off);
- if (n <= 0)
- break;
- output->off += n;
+ /* XXX - really only want to flush output buffers, does it matter? */
+ for (iob = iobufs; iob; iob = iob->next) {
+ while (iob->len > iob->off) {
+ n = write(iob->wfd, iob->buf + iob->off, iob->len - iob->off);
+ if (n <= 0)
+ break;
+ iob->off += n;
+ }
}
/* Make sure there is no output remaining on the master pty. */
+ /* XXX - pipes too? */
for (;;) {
- n = read(script_fds[SFD_MASTER], output->buf, sizeof(output->buf));
+ n = read(script_fds[SFD_MASTER], output.buf, sizeof(output.buf));
if (n <= 0)
break;
/* XXX */
- log_output(output->buf, n);
- output->off = 0;
- output->len = n;
+ log_output(output.buf, n);
+ output.off = 0;
+ output.len = n;
do {
- n = write(STDOUT_FILENO, output->buf + output->off,
- output->len - output->off);
+ n = write(script_fds[SFD_USERTTY], output.buf + output.off,
+ output.len - output.off);
if (n <= 0)
break;
- output->off += n;
- } while (output->len > output->off);
+ output.off += n;
+ } while (output.len > output.off);
}
}
/* Set child process group here too to avoid a race. */
setpgid(0, self);
- /*
- * We have guaranteed that the slave fd > 3
- */
- if (isatty(STDIN_FILENO))
- dup2(script_fds[SFD_SLAVE], STDIN_FILENO);
- dup2(script_fds[SFD_SLAVE], STDOUT_FILENO);
- dup2(script_fds[SFD_SLAVE], STDERR_FILENO);
- close(script_fds[SFD_SLAVE]);
+ /* Wire up standard fds, note that stdout/stderr may be pipes. */
+ dup2(script_fds[SFD_STDIN], STDIN_FILENO);
+ dup2(script_fds[SFD_STDOUT], STDOUT_FILENO);
+ dup2(script_fds[SFD_STDERR], STDERR_FILENO);
/* Wait for parent to grant us the tty if we are foreground. */
if (foreground) {
- while (tcgetpgrp(STDOUT_FILENO) != self)
+ while (tcgetpgrp(script_fds[SFD_SLAVE]) != self)
; /* spin */
}
+ /* We have guaranteed that the slave fd > 3 */
+ close(script_fds[SFD_SLAVE]);
+
#ifdef HAVE_SELINUX
if (rbac_enabled)
selinux_execve(path, argv, envp);