/*
- * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 2009-2018 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
#include <config.h>
#include <sys/types.h>
+#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
+#include <termios.h>
#include "sudo.h"
#include "sudo_event.h"
struct sudo_event *sigchld_event;
};
+static bool tty_initialized;
+
/*
* Deliver a signal to the running command.
* The signal was either forwarded to us by the parent sudo process
"%s: unable to set foreground pgrp to %d (command)",
__func__, (int)mc->cmnd_pgrp);
}
+ /* Lazily initialize the pty if needed. */
+ if (!tty_initialized) {
+ if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]))
+ tty_initialized = true;
+ }
killpg(mc->cmnd_pid, SIGCONT);
break;
case SIGCONT_BG:
debug_return;
}
+/*
+ * Unpack rows and cols from a CMD_TTYWINCH value, set the new window
+ * size on the pty slave and inform the command of the change.
+ */
+static void
+handle_winch(struct monitor_closure *mc, unsigned int wsize_packed)
+{
+ struct winsize wsize, owsize;
+ debug_decl(handle_winch, SUDO_DEBUG_EXEC);
+
+ /* Rows and colums are stored as two shorts packed into a single int. */
+ wsize.ws_row = wsize_packed & 0xffff;
+ wsize.ws_col = (wsize_packed >> 16) & 0xffff;
+
+ if (ioctl(io_fds[SFD_SLAVE], TIOCGWINSZ, &owsize) == 0 &&
+ (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col)) {
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "window size change %dx%d -> %dx%d",
+ owsize.ws_col, owsize.ws_row, wsize.ws_col, wsize.ws_row);
+
+ (void)ioctl(io_fds[SFD_SLAVE], TIOCSWINSZ, &wsize);
+ deliver_signal(mc, SIGWINCH, true);
+ }
+
+ debug_return;
+}
+
/*
* Send status to parent over socketpair.
* Return value is the same as send(2).
send_status(mc->backchannel, mc->cstat);
}
}
- } else {
+ } else {
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: not overwriting command status %d,%d with %d,%d",
__func__, mc->cstat->type, mc->cstat->val, CMD_WSTATUS, status);
mc->cstat->val = n ? EIO : ECONNRESET;
sudo_ev_loopbreak(mc->evbase);
} else {
- if (cstmp.type == CMD_SIGNO) {
+ switch (cstmp.type) {
+ case CMD_TTYWINCH:
+ handle_winch(mc, cstmp.val);
+ break;
+ case CMD_SIGNO:
deliver_signal(mc, cstmp.val, true);
- } else {
+ break;
+ default:
sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type);
+ break;
}
}
debug_return;
/* Setup event base and events. */
mc->evbase = sudo_ev_base_alloc();
if (mc->evbase == NULL)
- sudo_fatal(NULL);
+ sudo_fatal(NULL);
/* Event for command status via errfd. */
mc->errpipe_event = sudo_ev_alloc(errfd,
int errpipe[2];
debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
- /* Close unused fds. */
+ /* The pty master is not used by the monitor. */
if (io_fds[SFD_MASTER] != -1)
close(io_fds[SFD_MASTER]);
- if (io_fds[SFD_USERTTY] != -1)
- close(io_fds[SFD_USERTTY]);
/* Ignore any SIGTTIN or SIGTTOU we receive (shouldn't be possible). */
memset(&sa, 0, sizeof(sa));
if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU);
+ /* If we are starting in the foreground, the pty was already initialized. */
+ if (foreground)
+ tty_initialized = true;
+
/*
* Start a new session with the parent as the session leader
* and the slave pty as the controlling terminal.
sigprocmask(SIG_SETMASK, oset, NULL);
close(backchannel);
close(errpipe[0]);
+ if (io_fds[SFD_USERTTY] != -1)
+ close(io_fds[SFD_USERTTY]);
restore_signals();
/* setup tty and exec command */
#define TERM_COOKED 0
#define TERM_RAW 1
-/* We keep a tailq of signals to forward to the monitor. */
-struct sigforward {
- TAILQ_ENTRY(sigforward) entries;
- int signo;
+/* Tail queue of messages to send to the monitor. */
+struct monitor_message {
+ TAILQ_ENTRY(monitor_message) entries;
+ struct command_status cstat;
};
-TAILQ_HEAD(sigfwd_list, sigforward);
+TAILQ_HEAD(monitor_message_list, monitor_message);
struct exec_closure_pty {
pid_t monitor_pid;
struct command_details *details;
struct sudo_event_base *evbase;
struct sudo_event *backchannel_event;
+ struct sudo_event *fwdchannel_event;
struct sudo_event *sigint_event;
struct sudo_event *sigquit_event;
struct sudo_event *sigtstp_event;
struct sudo_event *sigusr2_event;
struct sudo_event *sigchld_event;
struct sudo_event *sigwinch_event;
- struct sudo_event *sigfwd_event;
- struct sigfwd_list sigfwd_list;
+ struct monitor_message_list monitor_messages;
};
/*
static char slavename[PATH_MAX];
int io_fds[6] = { -1, -1, -1, -1, -1, -1};
static bool foreground, pipeline;
-static bool tty_initialized;
static int ttymode = TERM_COOKED;
static sigset_t ttyblock;
static struct io_buffer_list iobufs;
static const char *utmp_user;
static void del_io_events(bool nonblocking);
-static void sync_ttysize(int src, int dst);
+static void sync_ttysize(struct exec_closure_pty *ec);
static int safe_close(int fd);
static void ev_free_by_fd(struct sudo_event_base *evbase, int fd);
-static void check_foreground(pid_t ppgrp);
+static void check_foreground(struct exec_closure_pty *ec);
static void add_io_events(struct sudo_event_base *evbase);
static void schedule_signal(struct exec_closure_pty *ec, int signo);
debug_return_bool(true);
}
+/*
+ * Make the tty slave the controlling tty.
+ * This is only used by the monitor but slavename[] is static.
+ */
int
pty_make_controlling(void)
{
- if (io_fds[SFD_USERTTY] != -1) {
+ if (io_fds[SFD_SLAVE] != -1) {
#ifdef TIOCSCTTY
if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
return -1;
* the pty slave as needed.
*/
static void
-check_foreground(pid_t ppgrp)
+check_foreground(struct exec_closure_pty *ec)
{
debug_decl(check_foreground, SUDO_DEBUG_EXEC);
if (io_fds[SFD_USERTTY] != -1) {
- /* Always check for window size changes. */
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ec->ppgrp;
- foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
- if (foreground) {
- if (!tty_initialized) {
- if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]))
- tty_initialized = true;
- }
- }
+ /* Also check for window size changes. */
+ sync_ttysize(ec);
}
debug_return;
* foreground or SIGCONT_BG if it is a background process.
*/
static int
-suspend_sudo(int signo, pid_t ppgrp)
+suspend_sudo(struct exec_closure_pty *ec, int signo)
{
char signame[SIG2STR_MAX];
struct sigaction sa, osa;
* in the foreground. If not, we'll suspend sudo and resume later.
*/
if (!foreground)
- check_foreground(ppgrp);
+ check_foreground(ec);
if (foreground) {
if (ttymode != TERM_RAW) {
if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
sudo_warn(U_("unable to set handler for signal %d"), signo);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "kill parent SIG%s", signame);
- if (killpg(ppgrp, signo) != 0)
- sudo_warn("killpg(%d, SIG%s)", (int)ppgrp, signame);
+ if (killpg(ec->ppgrp, signo) != 0)
+ sudo_warn("killpg(%d, SIG%s)", (int)ec->ppgrp, signame);
/* Check foreground/background status on resume. */
- check_foreground(ppgrp);
+ check_foreground(ec);
/*
* We always resume the command in the foreground if sudo itself
debug_return;
}
+/*
+ * Send command status to the monitor (signal or window size change).
+ */
+static void
+send_command_status(struct exec_closure_pty *ec, int type, int val)
+{
+ struct monitor_message *msg;
+ debug_decl(send_command, SUDO_DEBUG_EXEC)
+
+ if ((msg = calloc(1, sizeof(*msg))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ msg->cstat.type = type;
+ msg->cstat.val = val;
+ TAILQ_INSERT_TAIL(&ec->monitor_messages, msg, entries);
+
+ if (sudo_ev_add(ec->evbase, ec->fwdchannel_event, NULL, true) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ /* Restart event loop to send the command immediately. */
+ sudo_ev_loopcontinue(ec->evbase);
+
+ debug_return;
+}
+
/*
* Schedule a signal to be forwarded.
*/
static void
schedule_signal(struct exec_closure_pty *ec, int signo)
{
- struct sigforward *sigfwd;
char signame[SIG2STR_MAX];
debug_decl(schedule_signal, SUDO_DEBUG_EXEC)
snprintf(signame, sizeof(signame), "%d", signo);
sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for command", signame);
- if ((sigfwd = calloc(1, sizeof(*sigfwd))) == NULL)
- sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
- sigfwd->signo = signo;
- TAILQ_INSERT_TAIL(&ec->sigfwd_list, sigfwd, entries);
-
- if (sudo_ev_add(ec->evbase, ec->sigfwd_event, NULL, true) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
- /* Restart event loop to service signal immediately. */
- sudo_ev_loopcontinue(ec->evbase);
+ send_command_status(ec, CMD_SIGNO, signo);
debug_return;
}
/* Suspend parent and tell monitor how to resume on return. */
sudo_debug_printf(SUDO_DEBUG_INFO,
"command stopped, suspending parent");
- signo = suspend_sudo(WSTOPSIG(cstat.val), ec->ppgrp);
+ signo = suspend_sudo(ec, WSTOPSIG(cstat.val));
schedule_signal(ec, signo);
/* Re-enable I/O events */
add_io_events(ec->evbase);
if (WIFSTOPPED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"monitor stopped, suspending sudo");
- n = suspend_sudo(WSTOPSIG(status), ec->ppgrp);
+ n = suspend_sudo(ec, WSTOPSIG(status));
kill(pid, SIGCONT);
schedule_signal(ec, n);
/* Re-enable I/O events */
handle_sigchld_pty(ec);
break;
case SIGWINCH:
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ sync_ttysize(ec);
break;
default:
/*
}
/*
- * Forward signals in sigfwd_list to the monitor so it can
+ * Forward signals in monitor_messages to the monitor so it can
* deliver them to the command.
*/
static void
-sigfwd_cb(int sock, int what, void *v)
+fwdchannel_cb(int sock, int what, void *v)
{
struct exec_closure_pty *ec = v;
char signame[SIG2STR_MAX];
- struct sigforward *sigfwd;
- struct command_status cstat;
+ struct monitor_message *msg;
ssize_t nsent;
- debug_decl(sigfwd_cb, SUDO_DEBUG_EXEC)
-
- while ((sigfwd = TAILQ_FIRST(&ec->sigfwd_list)) != NULL) {
- if (sigfwd->signo == SIGCONT_FG)
- strlcpy(signame, "CONT_FG", sizeof(signame));
- else if (sigfwd->signo == SIGCONT_BG)
- strlcpy(signame, "CONT_BG", sizeof(signame));
- else if (sig2str(sigfwd->signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", sigfwd->signo);
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "sending SIG%s to monitor over backchannel", signame);
- cstat.type = CMD_SIGNO;
- cstat.val = sigfwd->signo;
- TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries);
- free(sigfwd);
- nsent = send(sock, &cstat, sizeof(cstat), 0);
- if (nsent != sizeof(cstat)) {
+ debug_decl(fwdchannel_cb, SUDO_DEBUG_EXEC)
+
+ while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) {
+ switch (msg->cstat.type) {
+ case CMD_SIGNO:
+ if (msg->cstat.val == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (msg->cstat.val == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(msg->cstat.val, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", msg->cstat.val);
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "sending SIG%s to monitor over backchannel", signame);
+ break;
+ case CMD_TTYWINCH:
+ sudo_debug_printf(SUDO_DEBUG_INFO, "sending window size change "
+ "to monitor over backchannelL %d x %d",
+ msg->cstat.val & 0xffff, (msg->cstat.val >> 16) & 0xffff);
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "sending cstat type %d, value %d to monitor over backchannel",
+ msg->cstat.type, msg->cstat.val);
+ break;
+ }
+ TAILQ_REMOVE(&ec->monitor_messages, msg, entries);
+ nsent = send(sock, &msg->cstat, sizeof(msg->cstat), 0);
+ if (nsent != sizeof(msg->cstat)) {
if (errno == EPIPE) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"broken pipe writing to monitor over backchannel");
- /* Other end of socket gone, empty out sigfwd_list. */
- while ((sigfwd = TAILQ_FIRST(&ec->sigfwd_list)) != NULL) {
- TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries);
- free(sigfwd);
+ /* Other end of socket gone, empty out monitor_messages. */
+ free(msg);
+ while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) {
+ TAILQ_REMOVE(&ec->monitor_messages, msg, entries);
+ free(msg);
}
/* XXX - need new CMD_ type for monitor errors. */
ec->cstat->type = CMD_ERRNO;
}
break;
}
+ free(msg);
}
}
ec->ppgrp = ppgrp;
ec->cstat = cstat;
ec->details = details;
- TAILQ_INIT(&ec->sigfwd_list);
+ TAILQ_INIT(&ec->monitor_messages);
/* Setup event base and events. */
ec->evbase = sudo_ev_base_alloc();
sudo_fatal(U_("unable to add event to queue"));
/* The signal forwarding event gets added on demand. */
- ec->sigfwd_event = sudo_ev_alloc(backchannel,
- SUDO_EV_WRITE, sigfwd_cb, ec);
- if (ec->sigfwd_event == NULL)
+ ec->fwdchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_WRITE, fwdchannel_cb, ec);
+ if (ec->fwdchannel_event == NULL)
sudo_fatal(NULL);
/* Set the default event base. */
sudo_ev_base_free(ec->evbase);
sudo_ev_free(ec->backchannel_event);
+ sudo_ev_free(ec->fwdchannel_event);
sudo_ev_free(ec->sigint_event);
sudo_ev_free(ec->sigquit_event);
sudo_ev_free(ec->sigtstp_event);
sudo_ev_free(ec->sigusr2_event);
sudo_ev_free(ec->sigchld_event);
sudo_ev_free(ec->sigwinch_event);
- sudo_ev_free(ec->sigfwd_event);
debug_return;
}
{
int io_pipe[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
bool interpose[3] = { false, false, false };
- struct sigforward *sigfwd, *sigfwd_next;
+ struct monitor_message *msg;
struct exec_closure_pty ec = { 0 };
struct plugin_container *plugin;
sigset_t set, oset;
}
}
- /* Set pty window size. */
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
-
if (foreground) {
/* Copy terminal attrs from user tty -> pty slave. */
- if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]))
- tty_initialized = true;
+ if (!sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to copy terminal settings to pty", __func__);
+ }
/* Start out in raw mode unless part of a pipeline or backgrounded. */
if (!pipeline && !ISSET(details->flags, CD_EXEC_BG)) {
/* Free things up. */
free_exec_closure_pty(&ec);
- TAILQ_FOREACH_SAFE(sigfwd, &ec.sigfwd_list, entries, sigfwd_next) {
- free(sigfwd);
+ while ((msg = TAILQ_FIRST(&ec.monitor_messages)) != NULL) {
+ TAILQ_REMOVE(&ec.monitor_messages, msg, entries);
+ free(msg);
}
debug_return_bool(true);
}
}
/*
- * Propagates tty size change signals to pty being used by the command
- * and passes new window size to the I/O plugin.
+ * Check for tty size changes.
+ * Passes the new window size to the I/O plugin and to the monitor.
*/
static void
-sync_ttysize(int src, int dst)
+sync_ttysize(struct exec_closure_pty *ec)
{
- struct winsize wsize, owsize;
- pid_t pgrp;
+ static struct winsize owsize;
+ struct winsize wsize;
debug_decl(sync_ttysize, SUDO_DEBUG_EXEC);
- if (ioctl(src, TIOCGWINSZ, &wsize) == 0 &&
- ioctl(dst, TIOCGWINSZ, &owsize) == 0 &&
- (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col)) {
+ if (ioctl(io_fds[SFD_USERTTY], TIOCGWINSZ, &wsize) == 0) {
+ if (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col) {
+ const unsigned int wsize_packed = (wsize.ws_row & 0xffff) |
+ ((wsize.ws_col & 0xffff) << 16);
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "window size change %dx%d -> %dx%d",
- owsize.ws_col, owsize.ws_row, wsize.ws_col, wsize.ws_row);
+ /* Log window change event. */
+ log_winchange(wsize.ws_row, wsize.ws_col);
- (void)ioctl(dst, TIOCSWINSZ, &wsize);
- if ((pgrp = tcgetpgrp(dst)) != -1)
- killpg(pgrp, SIGWINCH);
+ /* Send window change event to monitor process. */
+ send_command_status(ec, CMD_TTYWINCH, wsize_packed);
- /* Only log window size changes, not the initial setting. */
- if (tty_initialized)
- log_winchange(wsize.ws_row, wsize.ws_col);
+ /* Update old value. */
+ owsize = wsize;
+ }
}
debug_return;