aboutsummaryrefslogtreecommitdiff
path: root/channels.c
diff options
context:
space:
mode:
Diffstat (limited to 'channels.c')
-rw-r--r--channels.c246
1 files changed, 187 insertions, 59 deletions
diff --git a/channels.c b/channels.c
index e75a0cf9b509..0d26358cc65e 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.420 2022/09/19 08:49:50 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.427 2023/01/18 02:00:10 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -151,6 +151,12 @@ struct permission_set {
int all_permitted;
};
+/* Used to record timeouts per channel type */
+struct ssh_channel_timeout {
+ char *type_pattern;
+ u_int timeout_secs;
+};
+
/* Master structure for channels state */
struct ssh_channels {
/*
@@ -204,6 +210,10 @@ struct ssh_channels {
/* AF_UNSPEC or AF_INET or AF_INET6 */
int IPv4or6;
+
+ /* Channel timeouts by type */
+ struct ssh_channel_timeout *timeouts;
+ size_t ntimeouts;
};
/* helper */
@@ -297,6 +307,76 @@ channel_lookup(struct ssh *ssh, int id)
}
/*
+ * Add a timeout for open channels whose c->ctype (or c->xctype if it is set)
+ * match type_pattern.
+ */
+void
+channel_add_timeout(struct ssh *ssh, const char *type_pattern,
+ u_int timeout_secs)
+{
+ struct ssh_channels *sc = ssh->chanctxt;
+
+ debug2_f("channel type \"%s\" timeout %u seconds",
+ type_pattern, timeout_secs);
+ sc->timeouts = xrecallocarray(sc->timeouts, sc->ntimeouts,
+ sc->ntimeouts + 1, sizeof(*sc->timeouts));
+ sc->timeouts[sc->ntimeouts].type_pattern = xstrdup(type_pattern);
+ sc->timeouts[sc->ntimeouts].timeout_secs = timeout_secs;
+ sc->ntimeouts++;
+}
+
+/* Clears all previously-added channel timeouts */
+void
+channel_clear_timeouts(struct ssh *ssh)
+{
+ struct ssh_channels *sc = ssh->chanctxt;
+ size_t i;
+
+ debug3_f("clearing");
+ for (i = 0; i < sc->ntimeouts; i++)
+ free(sc->timeouts[i].type_pattern);
+ free(sc->timeouts);
+ sc->timeouts = NULL;
+ sc->ntimeouts = 0;
+}
+
+static u_int
+lookup_timeout(struct ssh *ssh, const char *type)
+{
+ struct ssh_channels *sc = ssh->chanctxt;
+ size_t i;
+
+ for (i = 0; i < sc->ntimeouts; i++) {
+ if (match_pattern(type, sc->timeouts[i].type_pattern))
+ return sc->timeouts[i].timeout_secs;
+ }
+
+ return 0;
+}
+
+/*
+ * Sets "extended type" of a channel; used by session layer to add additional
+ * information about channel types (e.g. shell, login, subsystem) that can then
+ * be used to select timeouts.
+ * Will reset c->inactive_deadline as a side-effect.
+ */
+void
+channel_set_xtype(struct ssh *ssh, int id, const char *xctype)
+{
+ Channel *c;
+
+ if ((c = channel_by_id(ssh, id)) == NULL)
+ fatal_f("missing channel %d", id);
+ if (c->xctype != NULL)
+ free(c->xctype);
+ c->xctype = xstrdup(xctype);
+ /* Type has changed, so look up inactivity deadline again */
+ c->inactive_deadline = lookup_timeout(ssh, c->xctype);
+ debug2_f("labeled channel %d as %s (inactive timeout %u)", id, xctype,
+ c->inactive_deadline);
+}
+
+/*
* Register filedescriptors for a channel, used when allocating a channel or
* when the channel consumer/producer is ready, e.g. shell exec'd
*/
@@ -337,16 +417,19 @@ channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd,
*/
if (rfd != -1 && !isatty(rfd) &&
(val = fcntl(rfd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) {
+ c->restore_flags[0] = val;
c->restore_block |= CHANNEL_RESTORE_RFD;
set_nonblock(rfd);
}
if (wfd != -1 && !isatty(wfd) &&
(val = fcntl(wfd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) {
+ c->restore_flags[1] = val;
c->restore_block |= CHANNEL_RESTORE_WFD;
set_nonblock(wfd);
}
if (efd != -1 && !isatty(efd) &&
(val = fcntl(efd, F_GETFL)) != -1 && !(val & O_NONBLOCK)) {
+ c->restore_flags[2] = val;
c->restore_block |= CHANNEL_RESTORE_EFD;
set_nonblock(efd);
}
@@ -415,8 +498,10 @@ channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd,
c->remote_name = xstrdup(remote_name);
c->ctl_chan = -1;
c->delayed = 1; /* prevent call to channel_post handler */
+ c->inactive_deadline = lookup_timeout(ssh, c->ctype);
TAILQ_INIT(&c->status_confirms);
- debug("channel %d: new [%s]", found, remote_name);
+ debug("channel %d: new %s [%s] (inactive timeout: %u)",
+ found, c->ctype, remote_name, c->inactive_deadline);
return c;
}
@@ -428,10 +513,16 @@ channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
if (fd == -1)
return 0;
- if ((*fdp == c->rfd && (c->restore_block & CHANNEL_RESTORE_RFD) != 0) ||
- (*fdp == c->wfd && (c->restore_block & CHANNEL_RESTORE_WFD) != 0) ||
- (*fdp == c->efd && (c->restore_block & CHANNEL_RESTORE_EFD) != 0))
- (void)fcntl(*fdp, F_SETFL, 0); /* restore blocking */
+ /* restore blocking */
+ if (*fdp == c->rfd &&
+ (c->restore_block & CHANNEL_RESTORE_RFD) != 0)
+ (void)fcntl(*fdp, F_SETFL, c->restore_flags[0]);
+ else if (*fdp == c->wfd &&
+ (c->restore_block & CHANNEL_RESTORE_WFD) != 0)
+ (void)fcntl(*fdp, F_SETFL, c->restore_flags[1]);
+ else if (*fdp == c->efd &&
+ (c->restore_block & CHANNEL_RESTORE_EFD) != 0)
+ (void)fcntl(*fdp, F_SETFL, c->restore_flags[2]);
if (*fdp == c->rfd) {
c->io_want &= ~SSH_CHAN_IO_RFD;
@@ -656,6 +747,8 @@ channel_free(struct ssh *ssh, Channel *c)
c->path = NULL;
free(c->listening_addr);
c->listening_addr = NULL;
+ free(c->xctype);
+ c->xctype = NULL;
while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) {
if (cc->abandon_cb != NULL)
cc->abandon_cb(ssh, c, cc->ctx);
@@ -871,9 +964,9 @@ channel_format_status(const Channel *c)
{
char *ret = NULL;
- xasprintf(&ret, "t%d %s%u i%u/%zu o%u/%zu e[%s]/%zu "
+ xasprintf(&ret, "t%d [%s] %s%u i%u/%zu o%u/%zu e[%s]/%zu "
"fd %d/%d/%d sock %d cc %d io 0x%02x/0x%02x",
- c->type,
+ c->type, c->xctype != NULL ? c->xctype : c->ctype,
c->have_remote_id ? "r" : "nr", c->remote_id,
c->istate, sshbuf_len(c->input),
c->ostate, sshbuf_len(c->output),
@@ -1087,6 +1180,7 @@ channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd,
channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, is_tty);
c->type = SSH_CHANNEL_OPEN;
+ c->lastused = monotime();
c->local_window = c->local_window_max = window_max;
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 ||
@@ -1222,6 +1316,32 @@ x11_open_helper(struct ssh *ssh, struct sshbuf *b)
return 1;
}
+void
+channel_force_close(struct ssh *ssh, Channel *c, int abandon)
+{
+ debug3_f("channel %d: forcibly closing", c->self);
+ if (c->istate == CHAN_INPUT_OPEN)
+ chan_read_failed(ssh, c);
+ if (c->istate == CHAN_INPUT_WAIT_DRAIN) {
+ sshbuf_reset(c->input);
+ chan_ibuf_empty(ssh, c);
+ }
+ if (c->ostate == CHAN_OUTPUT_OPEN ||
+ c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
+ sshbuf_reset(c->output);
+ chan_write_failed(ssh, c);
+ }
+ if (c->detach_user)
+ c->detach_user(ssh, c->self, 1, NULL);
+ if (c->efd != -1)
+ channel_close_fd(ssh, c, &c->efd);
+ if (abandon)
+ c->type = SSH_CHANNEL_ABANDONED;
+ /* exempt from inactivity timeouts */
+ c->inactive_deadline = 0;
+ c->lastused = 0;
+}
+
static void
channel_pre_x11_open(struct ssh *ssh, Channel *c)
{
@@ -1231,17 +1351,14 @@ channel_pre_x11_open(struct ssh *ssh, Channel *c)
if (ret == 1) {
c->type = SSH_CHANNEL_OPEN;
+ c->lastused = monotime();
channel_pre_open(ssh, c);
} else if (ret == -1) {
- logit("X11 connection rejected because of wrong authentication.");
+ logit("X11 connection rejected because of wrong "
+ "authentication.");
debug2("X11 rejected %d i%d/o%d",
c->self, c->istate, c->ostate);
- chan_read_failed(ssh, c);
- sshbuf_reset(c->input);
- chan_ibuf_empty(ssh, c);
- sshbuf_reset(c->output);
- chan_write_failed(ssh, c);
- debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate);
+ channel_force_close(ssh, c, 0);
}
}
@@ -1591,11 +1708,7 @@ static void
rdynamic_close(struct ssh *ssh, Channel *c)
{
c->type = SSH_CHANNEL_OPEN;
- chan_read_failed(ssh, c);
- sshbuf_reset(c->input);
- chan_ibuf_empty(ssh, c);
- sshbuf_reset(c->output);
- chan_write_failed(ssh, c);
+ channel_force_close(ssh, c, 0);
}
/* reverse dynamic port forwarding */
@@ -1693,7 +1806,7 @@ channel_post_x11_listener(struct ssh *ssh, Channel *c)
snprintf(buf, sizeof buf, "X11 connection from %.200s port %d",
remote_ipaddr, remote_port);
- nc = channel_new(ssh, "accepted x11 socket",
+ nc = channel_new(ssh, "x11-connection",
SSH_CHANNEL_OPENING, newsock, newsock, -1,
c->local_window_max, c->local_maxpacket, 0, buf, 1);
open_preamble(ssh, __func__, nc, "x11");
@@ -1852,7 +1965,7 @@ channel_post_auth_listener(struct ssh *ssh, Channel *c)
c->notbefore = monotime() + 1;
return;
}
- nc = channel_new(ssh, "accepted auth socket",
+ nc = channel_new(ssh, "agent-connection",
SSH_CHANNEL_OPENING, newsock, newsock, -1,
c->local_window_max, c->local_maxpacket,
0, "accepted auth socket", 1);
@@ -1882,6 +1995,7 @@ channel_post_connecting(struct ssh *ssh, Channel *c)
c->self, c->connect_ctx.host, c->connect_ctx.port);
channel_connect_ctx_free(&c->connect_ctx);
c->type = SSH_CHANNEL_OPEN;
+ c->lastused = monotime();
if (isopen) {
/* no message necessary */
} else {
@@ -1930,7 +2044,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
char buf[CHAN_RBUF];
ssize_t len;
int r, force;
- size_t have, avail, maxlen = CHANNEL_MAX_READ;
+ size_t nr = 0, have, avail, maxlen = CHANNEL_MAX_READ;
int pty_zeroread = 0;
#ifdef PTY_ZEROREAD
@@ -1959,7 +2073,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
}
if (maxlen > avail)
maxlen = avail;
- if ((r = sshbuf_read(c->rfd, c->input, maxlen, NULL)) != 0) {
+ if ((r = sshbuf_read(c->rfd, c->input, maxlen, &nr)) != 0) {
if (errno == EINTR || (!force &&
(errno == EAGAIN || errno == EWOULDBLOCK)))
return 1;
@@ -1967,6 +2081,8 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
c->self, c->rfd, maxlen, ssh_err(r));
goto rfail;
}
+ if (nr != 0)
+ c->lastused = monotime();
return 1;
}
@@ -1992,6 +2108,7 @@ channel_handle_rfd(struct ssh *ssh, Channel *c)
}
return -1;
}
+ c->lastused = monotime();
if (c->input_filter != NULL) {
if (c->input_filter(ssh, c, buf, len) == -1) {
debug2("channel %d: filter stops", c->self);
@@ -2072,6 +2189,7 @@ channel_handle_wfd(struct ssh *ssh, Channel *c)
}
return -1;
}
+ c->lastused = monotime();
#ifndef BROKEN_TCGETATTR_ICANON
if (c->isatty && dlen >= 1 && buf[0] != '\r') {
if (tcgetattr(c->wfd, &tio) == 0 &&
@@ -2120,6 +2238,7 @@ channel_handle_efd_write(struct ssh *ssh, Channel *c)
if ((r = sshbuf_consume(c->extended, len)) != 0)
fatal_fr(r, "channel %i: consume", c->self);
c->local_consumed += len;
+ c->lastused = monotime();
}
return 1;
}
@@ -2144,7 +2263,10 @@ channel_handle_efd_read(struct ssh *ssh, Channel *c)
if (len <= 0) {
debug2("channel %d: closing read-efd %d", c->self, c->efd);
channel_close_fd(ssh, c, &c->efd);
- } else if (c->extended_usage == CHAN_EXTENDED_IGNORE)
+ return 1;
+ }
+ c->lastused = monotime();
+ if (c->extended_usage == CHAN_EXTENDED_IGNORE)
debug3("channel %d: discard efd", c->self);
else if ((r = sshbuf_put(c->extended, buf, len)) != 0)
fatal_fr(r, "channel %i: append", c->self);
@@ -2333,7 +2455,7 @@ channel_post_mux_listener(struct ssh *ssh, Channel *c)
close(newsock);
return;
}
- nc = channel_new(ssh, "multiplex client", SSH_CHANNEL_MUX_CLIENT,
+ nc = channel_new(ssh, "mux-control", SSH_CHANNEL_MUX_CLIENT,
newsock, newsock, -1, c->local_window_max,
c->local_maxpacket, 0, "mux-control", 1);
nc->mux_rcb = c->mux_rcb;
@@ -2395,7 +2517,7 @@ channel_garbage_collect(struct ssh *ssh, Channel *c)
return;
debug2("channel %d: gc: notify user", c->self);
- c->detach_user(ssh, c->self, NULL);
+ c->detach_user(ssh, c->self, 0, NULL);
/* if we still have a callback */
if (c->detach_user != NULL)
return;
@@ -2410,7 +2532,7 @@ channel_garbage_collect(struct ssh *ssh, Channel *c)
enum channel_table { CHAN_PRE, CHAN_POST };
static void
-channel_handler(struct ssh *ssh, int table, time_t *unpause_secs)
+channel_handler(struct ssh *ssh, int table, struct timespec *timeout)
{
struct ssh_channels *sc = ssh->chanctxt;
chan_fn **ftab = table == CHAN_PRE ? sc->channel_pre : sc->channel_post;
@@ -2419,8 +2541,6 @@ channel_handler(struct ssh *ssh, int table, time_t *unpause_secs)
time_t now;
now = monotime();
- if (unpause_secs != NULL)
- *unpause_secs = 0;
for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
c = sc->channels[i];
if (c == NULL)
@@ -2435,29 +2555,37 @@ channel_handler(struct ssh *ssh, int table, time_t *unpause_secs)
continue;
}
if (ftab[c->type] != NULL) {
- /*
- * Run handlers that are not paused.
- */
- if (c->notbefore <= now)
+ if (table == CHAN_PRE &&
+ c->type == SSH_CHANNEL_OPEN &&
+ c->inactive_deadline != 0 && c->lastused != 0 &&
+ now >= c->lastused + c->inactive_deadline) {
+ /* channel closed for inactivity */
+ verbose("channel %d: closing after %u seconds "
+ "of inactivity", c->self,
+ c->inactive_deadline);
+ channel_force_close(ssh, c, 1);
+ } else if (c->notbefore <= now) {
+ /* Run handlers that are not paused. */
(*ftab[c->type])(ssh, c);
- else if (unpause_secs != NULL) {
+ /* inactivity timeouts must interrupt poll() */
+ if (timeout != NULL &&
+ c->type == SSH_CHANNEL_OPEN &&
+ c->lastused != 0 &&
+ c->inactive_deadline != 0) {
+ ptimeout_deadline_monotime(timeout,
+ c->lastused + c->inactive_deadline);
+ }
+ } else if (timeout != NULL) {
/*
- * Collect the time that the earliest
- * channel comes off pause.
+ * Arrange for poll() wakeup when channel pause
+ * timer expires.
*/
- debug3_f("chan %d: skip for %d more "
- "seconds", c->self,
- (int)(c->notbefore - now));
- if (*unpause_secs == 0 ||
- (c->notbefore - now) < *unpause_secs)
- *unpause_secs = c->notbefore - now;
+ ptimeout_deadline_monotime(timeout,
+ c->notbefore);
}
}
channel_garbage_collect(ssh, c);
}
- if (unpause_secs != NULL && *unpause_secs != 0)
- debug3_f("first channel unpauses in %d seconds",
- (int)*unpause_secs);
}
/*
@@ -2603,7 +2731,7 @@ channel_prepare_pollfd(Channel *c, u_int *next_pollfd,
/* * Allocate/prepare poll structure */
void
channel_prepare_poll(struct ssh *ssh, struct pollfd **pfdp, u_int *npfd_allocp,
- u_int *npfd_activep, u_int npfd_reserved, time_t *minwait_secs)
+ u_int *npfd_activep, u_int npfd_reserved, struct timespec *timeout)
{
struct ssh_channels *sc = ssh->chanctxt;
u_int i, oalloc, p, npfd = npfd_reserved;
@@ -2627,7 +2755,7 @@ channel_prepare_poll(struct ssh *ssh, struct pollfd **pfdp, u_int *npfd_allocp,
*npfd_activep = npfd_reserved;
oalloc = sc->channels_alloc;
- channel_handler(ssh, CHAN_PRE, minwait_secs);
+ channel_handler(ssh, CHAN_PRE, timeout);
if (oalloc != sc->channels_alloc) {
/* shouldn't happen */
@@ -2976,7 +3104,7 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
error_fr(r, "parse");
goto out;
}
- c = channel_new(ssh, "mux proxy", SSH_CHANNEL_MUX_PROXY,
+ c = channel_new(ssh, "mux-proxy", SSH_CHANNEL_MUX_PROXY,
-1, -1, -1, 0, 0, 0, ctype, 1);
c->mux_ctx = downstream; /* point to mux client */
c->mux_downstream_id = id; /* original downstream id */
@@ -3003,7 +3131,7 @@ channel_proxy_downstream(struct ssh *ssh, Channel *downstream)
error_fr(r, "parse");
goto out;
}
- c = channel_new(ssh, "mux proxy", SSH_CHANNEL_MUX_PROXY,
+ c = channel_new(ssh, "mux-proxy", SSH_CHANNEL_MUX_PROXY,
-1, -1, -1, 0, 0, 0, "mux-down-connect", 1);
c->mux_ctx = downstream; /* point to mux client */
c->mux_downstream_id = id;
@@ -3386,6 +3514,7 @@ channel_input_open_confirmation(int type, u_int32_t seq, struct ssh *ssh)
c->open_confirm(ssh, c->self, 1, c->open_confirm_ctx);
debug2_f("channel %d: callback done", c->self);
}
+ c->lastused = monotime();
debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
c->remote_window, c->remote_maxpacket);
return 0;
@@ -3719,7 +3848,7 @@ channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type,
}
/* Allocate a channel number for the socket. */
- c = channel_new(ssh, "port listener", type, sock, sock, -1,
+ c = channel_new(ssh, "port-listener", type, sock, sock, -1,
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
0, "port listener", 1);
c->path = xstrdup(host);
@@ -3802,7 +3931,7 @@ channel_setup_fwd_listener_streamlocal(struct ssh *ssh, int type,
debug("Local forwarding listening on path %s.", fwd->listen_path);
/* Allocate a channel number for the socket. */
- c = channel_new(ssh, "unix listener", type, sock, sock, -1,
+ c = channel_new(ssh, "unix-listener", type, sock, sock, -1,
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
0, "unix listener", 1);
c->path = xstrdup(path);
@@ -4081,7 +4210,7 @@ int
channel_request_remote_forwarding(struct ssh *ssh, struct Forward *fwd)
{
int r, success = 0, idx = -1;
- char *host_to_connect, *listen_host, *listen_path;
+ const char *host_to_connect, *listen_host, *listen_path;
int port_to_connect, listen_port;
/* Send the forward request to the remote side. */
@@ -4112,18 +4241,17 @@ channel_request_remote_forwarding(struct ssh *ssh, struct Forward *fwd)
host_to_connect = listen_host = listen_path = NULL;
port_to_connect = listen_port = 0;
if (fwd->connect_path != NULL) {
- host_to_connect = xstrdup(fwd->connect_path);
+ host_to_connect = fwd->connect_path;
port_to_connect = PORT_STREAMLOCAL;
} else {
- host_to_connect = xstrdup(fwd->connect_host);
+ host_to_connect = fwd->connect_host;
port_to_connect = fwd->connect_port;
}
if (fwd->listen_path != NULL) {
- listen_path = xstrdup(fwd->listen_path);
+ listen_path = fwd->listen_path;
listen_port = PORT_STREAMLOCAL;
} else {
- if (fwd->listen_host != NULL)
- listen_host = xstrdup(fwd->listen_host);
+ listen_host = fwd->listen_host;
listen_port = fwd->listen_port;
}
idx = permission_set_add(ssh, FORWARD_USER, FORWARD_LOCAL,
@@ -4888,7 +5016,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
*chanids = xcalloc(num_socks + 1, sizeof(**chanids));
for (n = 0; n < num_socks; n++) {
sock = socks[n];
- nc = channel_new(ssh, "x11 listener",
+ nc = channel_new(ssh, "x11-listener",
SSH_CHANNEL_X11_LISTENER, sock, sock, -1,
CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT,
0, "X11 inet listener", 1);