Refactor script_execve() a bit so that it can be used in non-script

mode.  Needs more cleanup.
This commit is contained in:
Todd C. Miller
2010-02-27 16:53:56 -05:00
parent 157b7805cf
commit f145264ee0
2 changed files with 123 additions and 165 deletions

View File

@@ -87,7 +87,7 @@ struct script_buf {
char buf[16 * 1024]; char buf[16 * 1024];
}; };
static int script_fds[3]; static int script_fds[3] = { -1, -1, -1 };
static int ttyout; static int ttyout;
/* XXX - use an array of signals instead of just a single variable */ /* XXX - use an array of signals instead of just a single variable */
@@ -279,40 +279,38 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
int relaysig = 0, sv[2]; int relaysig = 0, sv[2];
fd_set *fdsr, *fdsw; fd_set *fdsr, *fdsw;
int rbac_enabled = 0; int rbac_enabled = 0;
int maxfd; int log_io, maxfd;
cstat->type = 0; /* XXX */ cstat->type = 0; /* XXX */
log_io = !tq_empty(&io_plugins);
#ifdef HAVE_SELINUX #ifdef HAVE_SELINUX
rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL; rbac_enabled = is_selinux_enabled() > 0 && user_role != NULL;
if (rbac_enabled) { if (rbac_enabled) {
selinux_prefork(user_role, user_type, script_fds[SFD_SLAVE]); selinux_prefork(user_role, user_type, script_fds[SFD_SLAVE]);
/* Re-open slave fd after it has been relabeled */ if (log_io) {
close(script_fds[SFD_SLAVE]); /* Re-open slave fd after it has been relabeled */
script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0); close(script_fds[SFD_SLAVE]);
if (script_fds[SFD_SLAVE] == -1) script_fds[SFD_SLAVE] = open(slavename, O_RDWR|O_NOCTTY, 0);
if (script_fds[SFD_SLAVE] == -1)
error(1, "cannot open %s", slavename); error(1, "cannot open %s", slavename);
}
} }
#endif #endif
/* Are we the foreground process? */
parent = getpid(); /* so child can pass signals back to us */ parent = getpid(); /* so child can pass signals back to us */
foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == parent;
/* So we can block tty-generated signals */ /*
sigemptyset(&ttyblock); * We communicate with the child over a bi-directional pipe.
sigaddset(&ttyblock, SIGINT); * Parent sends signal info to child and child sends back wait status.
sigaddset(&ttyblock, SIGQUIT); */
sigaddset(&ttyblock, SIGTSTP); if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
sigaddset(&ttyblock, SIGTTIN); error(1, "cannot create sockets");
sigaddset(&ttyblock, SIGTTOU);
/* Setup signal handlers window size changes and child stop/exit */
zero_bytes(&sa, sizeof(sa)); zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;
sa.sa_handler = sigwinch;
sigaction(SIGWINCH, &sa, NULL);
/* XXX - now get command status via sv (still need to detect child death) */ /* XXX - now get command status via sv (still need to detect child death) */
sa.sa_handler = sigchild; sa.sa_handler = sigchild;
@@ -322,6 +320,25 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
sa.sa_handler = SIG_IGN; sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL); sigaction(SIGPIPE, &sa, NULL);
if (log_io) {
sa.sa_handler = sigwinch;
sigaction(SIGWINCH, &sa, NULL);
/* So we can block tty-generated signals */
sigemptyset(&ttyblock);
sigaddset(&ttyblock, SIGINT);
sigaddset(&ttyblock, SIGQUIT);
sigaddset(&ttyblock, SIGTSTP);
sigaddset(&ttyblock, SIGTTIN);
sigaddset(&ttyblock, SIGTTOU);
/* Are we the foreground process? */
foreground = tcgetpgrp(script_fds[SFD_USERTTY]) == parent;
/* If stdout is not a tty we handle post-processing differently. */
ttyout = isatty(STDOUT_FILENO);
}
/* Signals to relay from parent to child. */ /* Signals to relay from parent to child. */
sa.sa_flags = 0; /* do not restart syscalls for these */ sa.sa_flags = 0; /* do not restart syscalls for these */
sa.sa_handler = handler; sa.sa_handler = handler;
@@ -330,22 +347,12 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
sigaction(SIGINT, &sa, NULL); sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTSTP, &sa, NULL); sigaction(SIGTSTP, &sa, NULL);
#if 0 /* XXX - keep? */ #if 0 /* XXX - keep these? */
sigaction(SIGTTIN, &sa, NULL); sigaction(SIGTTIN, &sa, NULL);
sigaction(SIGTTOU, &sa, NULL); sigaction(SIGTTOU, &sa, NULL);
#endif #endif
/* If stdout is not a tty we handle post-processing differently. */ if (log_io && foreground) {
ttyout = isatty(STDOUT_FILENO);
/*
* We communicate with the child over a bi-directional pipe.
* Parent sends signal info to child and child sends back wait status.
*/
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
error(1, "cannot create sockets");
if (foreground) {
/* Copy terminal attrs from user tty -> pty slave. */ /* Copy terminal attrs from user tty -> pty slave. */
if (term_copy(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE], ttyout)) { if (term_copy(script_fds[SFD_USERTTY], script_fds[SFD_SLAVE], ttyout)) {
tty_initialized = 1; tty_initialized = 1;
@@ -376,7 +383,17 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
close(sv[0]); close(sv[0]);
if (exec_setup(details) == 0) { if (exec_setup(details) == 0) {
/* headed for execve() */ /* headed for execve() */
script_child(details->command, argv, envp, sv[1], rbac_enabled); if (log_io)
script_child(details->command, argv, envp, sv[1], rbac_enabled);
else {
#ifdef HAVE_SELINUX
if (rbac_enabled)
selinux_execve(details->command, argv, envp);
else
#endif
execve(details->command, argv, envp);
}
/* XXX - fallback to sh */
} }
cstat->type = CMD_ERRNO; cstat->type = CMD_ERRNO;
cstat->val = errno; cstat->val = errno;
@@ -385,28 +402,31 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
} }
close(sv[1]); close(sv[1]);
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);
}
/* Max fd we will be selecting on. */ /* Max fd we will be selecting on. */
maxfd = sv[0]; maxfd = sv[0];
if (maxfd < script_fds[SFD_MASTER])
maxfd = script_fds[SFD_MASTER]; if (log_io) {
if (maxfd < script_fds[SFD_USERTTY]) if (maxfd < script_fds[SFD_MASTER])
maxfd = script_fds[SFD_USERTTY]; 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);
}
}
/* /*
* In the event loop we pass input from user tty to master * In the event loop we pass input from user tty to master
@@ -423,22 +443,31 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
recvsig = 0; recvsig = 0;
} }
if (input.off == input.len) /* If not logging I/O and child has exited we are done. */
input.off = input.len = 0; if (!log_io && recvsig == SIGCHLD) {
if (output.off == output.len) cstat->type = CMD_WSTATUS;
output.off = output.len = 0; cstat->val = child_status;
break;
}
zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
if (ttymode == TERM_RAW && input.len != sizeof(input.buf)) if (log_io) {
FD_SET(script_fds[SFD_USERTTY], fdsr); if (input.off == input.len)
if (output.len != sizeof(output.buf)) input.off = input.len = 0;
FD_SET(script_fds[SFD_MASTER], fdsr); if (output.off == output.len)
if (output.len > output.off) output.off = output.len = 0;
FD_SET(STDOUT_FILENO, fdsw);
if (input.len > input.off) if (ttymode == TERM_RAW && input.len != sizeof(input.buf))
FD_SET(script_fds[SFD_MASTER], fdsw); 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); FD_SET(sv[0], fdsr);
if (relaysig) if (relaysig)
FD_SET(sv[0], fdsw); FD_SET(sv[0], fdsw);
@@ -490,6 +519,9 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
break; /* should not happen */ break; /* should not happen */
} }
} }
if (!log_io)
continue;
if (FD_ISSET(script_fds[SFD_USERTTY], fdsr)) { if (FD_ISSET(script_fds[SFD_USERTTY], fdsr)) {
n = read(script_fds[SFD_USERTTY], input.buf + input.len, n = read(script_fds[SFD_USERTTY], input.buf + input.len,
sizeof(input.buf) - input.len); sizeof(input.buf) - input.len);
@@ -546,30 +578,31 @@ script_execve(struct command_details *details, char *argv[], char *envp[],
} }
} }
/* Flush any remaining output to stdout (already sent to I/O modules). */ if (log_io) {
n = fcntl(STDOUT_FILENO, F_GETFL, 0); /* Flush any remaining output to stdout (plugin already got it) */
if (n != -1) { n = fcntl(STDOUT_FILENO, F_GETFL, 0);
n &= ~O_NONBLOCK; if (n != -1) {
(void) fcntl(STDOUT_FILENO, F_SETFL, n); n &= ~O_NONBLOCK;
} (void) fcntl(STDOUT_FILENO, F_SETFL, n);
flush_output(&output); }
flush_output(&output);
#ifdef HAVE_STRSIGNAL #ifdef HAVE_STRSIGNAL
if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) { if (cstat->type == CMD_WSTATUS && WIFSIGNALED(cstat->val)) {
int signo = WTERMSIG(cstat->val); int signo = WTERMSIG(cstat->val);
if (signo && signo != SIGINT && signo != SIGPIPE) { if (signo && signo != SIGINT && signo != SIGPIPE) {
char *reason = strsignal(signo); char *reason = strsignal(signo);
write(STDOUT_FILENO, reason, strlen(reason)); write(STDOUT_FILENO, reason, strlen(reason));
if (WCOREDUMP(cstat->val)) if (WCOREDUMP(cstat->val))
write(STDOUT_FILENO, " (core dumped)", 14); write(STDOUT_FILENO, " (core dumped)", 14);
write(STDOUT_FILENO, "\n", 1); write(STDOUT_FILENO, "\n", 1);
}
} }
}
#endif #endif
do {
do { n = term_restore(script_fds[SFD_USERTTY], 0);
n = term_restore(script_fds[SFD_USERTTY], 0); } while (!n && errno == EINTR);
} while (!n && errno == EINTR); }
return cstat->type == CMD_ERRNO ? -1 : 0; return cstat->type == CMD_ERRNO ? -1 : 0;
} }
@@ -862,10 +895,15 @@ static void
sigchild(int s) sigchild(int s)
{ {
pid_t pid; pid_t pid;
int flags = WNOHANG;
int serrno = errno; int serrno = errno;
if (!tq_empty(&io_plugins))
flags |= WUNTRACED;
else
recvsig = SIGCHLD;
do { do {
pid = waitpid(child, &child_status, WNOHANG | WUNTRACED); pid = waitpid(child, &child_status, flags);
} while (pid == -1 && errno == EINTR); } while (pid == -1 && errno == EINTR);
if (pid == child) { if (pid == child) {
if (WIFSTOPPED(child_status)) if (WIFSTOPPED(child_status))

View File

@@ -746,94 +746,14 @@ run_command(struct command_details *details, char *argv[], char *envp[])
/* /*
* XXX - missing closefrom(), may not be possible in new scheme * XXX - missing closefrom(), may not be possible in new scheme
* also no background support * also no background support
* or selinux...
*/ */
/* If there are I/O plugins, allocate a pty and exec */ /* If there are I/O plugins, allocate a pty and exec */
if (!tq_empty(&io_plugins)) { if (!tq_empty(&io_plugins)) {
sudo_debug(8, "script mode"); sudo_debug(8, "script mode");
script_setup(details->euid); script_setup(details->euid);
script_execve(details, argv, envp, &cstat);
} else {
pid_t child, pid;
int nready, sv[2];
ssize_t nread;
sigaction_t sa;
fd_set *fdsr;
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
/* Want select() to be interrupted when child dies. */
sa.sa_handler = sigchild;
sigaction(SIGCHLD, &sa, NULL);
/* Ignore SIGPIPE from other end of socketpair. */
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0)
error(1, "cannot create sockets");
child = fork();
if (child == -1)
error(1, "unable to fork");
if (child == 0) {
/* child */
close(sv[0]);
if (exec_setup(details) == 0) {
/* XXX - fallback via /bin/sh */
execve(details->command, argv, envp);
}
cstat.type = CMD_ERRNO;
cstat.val = errno;
write(sv[1], &cstat, sizeof(cstat));
_exit(1);
}
close(sv[1]);
sudo_debug(9, "waiting for child");
/* wait for child to complete or for data on sv[0] */
fdsr = (fd_set *)emalloc2(howmany(sv[0] + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(fdsr, howmany(sv[0] + 1, NFDBITS) * sizeof(fd_mask));
FD_SET(sv[0], fdsr);
for (;;) {
/* XXX - we may get SIGCILD before the wait status from the child */
if (sigchld) {
/* Note: this is the child, not the command we are waiting on */
sigchld = 0;
do {
pid = waitpid(child, &cstat.val, WNOHANG);
if (pid == child)
cstat.type = CMD_WSTATUS;
} while (pid == -1 && errno == EINTR);
if (cstat.type == CMD_WSTATUS) {
/* command terminated, we're done */
break;
}
}
nready = select(sv[0] + 1, fdsr, NULL, NULL, NULL);
if (nready == -1) {
if (errno == EINTR)
continue;
error(1, "select failed");
}
if (FD_ISSET(sv[0], fdsr)) {
/* read child status */
nread = recv(sv[0], &cstat, sizeof(cstat), 0);
if (nread == -1) {
/* XXX - could be interrupted by SIGCHLD */
if (errno == EINTR)
continue;
/* XXX - init cstat for failure case */
}
sudo_debug(9, "cmdtype %d, val %d", cstat.type, cstat.val);
break; /* XXX */
}
}
} }
script_execve(details, argv, envp, &cstat);
switch (cstat.type) { switch (cstat.type) {
case CMD_ERRNO: case CMD_ERRNO: