Use pipes to the sudo process if stdout or stderr is not a tty.

Still needs some polishing and a decision as to whether it is
desirable to add additonal entry points for logging stdout/stderr/stdin
when they are not ttys.  That would allow a replay program to keep
things separate and to know whether the terminal needs to be in raw
mode at replay time.
This commit is contained in:
Todd C. Miller
2010-05-03 10:12:54 -04:00
parent 5475020561
commit dd256f25ca

View File

@@ -79,9 +79,12 @@
#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
@@ -94,14 +97,18 @@
# 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;
@@ -115,8 +122,8 @@ static int foreground;
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);
@@ -202,7 +209,7 @@ check_foreground(void)
* 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;
@@ -230,8 +237,8 @@ suspend_parent(int signo, struct script_buf *output)
/* 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) {
@@ -264,7 +271,7 @@ suspend_parent(int signo, struct script_buf *output)
ttymode == TERM_CBREAK);
} while (!n && errno == EINTR);
} else {
/* background process, no access to tty. */
/* Background process, no access to tty. */
ttymode = TERM_COOKED;
}
}
@@ -320,6 +327,21 @@ terminate_child(pid_t pid, int use_pgrp)
}
}
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.
@@ -339,9 +361,9 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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;
@@ -376,7 +398,7 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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);
@@ -407,8 +429,41 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
/* 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 */
@@ -478,25 +533,26 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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];
/* 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]);
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);
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);
}
}
@@ -506,8 +562,6 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
*/
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;
@@ -533,22 +587,20 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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) {
@@ -587,7 +639,7 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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 {
@@ -599,8 +651,6 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
break;
}
}
if (!log_io)
continue;
if (FD_ISSET(sv[0], fdsw)) {
for (n = 0; n < NSIG; n++) {
@@ -619,72 +669,49 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
}
}
}
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;
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(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;
}
}
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);
@@ -694,10 +721,10 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
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);
}
}
}
@@ -979,34 +1006,38 @@ bad:
}
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);
}
}
@@ -1018,21 +1049,20 @@ script_run(const char *path, char *argv[], char *envp[], int rbac_enabled)
/* 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);