=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.425 retrieving revision 1.426 diff -u -r1.425 -r1.426 --- src/usr.bin/ssh/channels.c 2023/01/06 02:42:34 1.425 +++ src/usr.bin/ssh/channels.c 2023/01/06 02:47:18 1.426 @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.425 2023/01/06 02:42:34 djm Exp $ */ +/* $OpenBSD: channels.c,v 1.426 2023/01/06 02:47:18 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -143,6 +143,12 @@ 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 { /* @@ -196,6 +202,10 @@ /* AF_UNSPEC or AF_INET or AF_INET6 */ int IPv4or6; + + /* Channel timeouts by type */ + struct ssh_channel_timeout *timeouts; + size_t ntimeouts; }; /* helper */ @@ -289,9 +299,58 @@ } /* + * 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) @@ -303,7 +362,10 @@ if (c->xctype != NULL) free(c->xctype); c->xctype = xstrdup(xctype); - debug2_f("labeled channel %d as %s", id, 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); } /* @@ -421,8 +483,10 @@ 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; } @@ -1095,6 +1159,7 @@ 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 || @@ -1251,6 +1316,9 @@ 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 @@ -1262,6 +1330,7 @@ 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 " @@ -1905,6 +1974,7 @@ 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 { @@ -1953,7 +2023,7 @@ char buf[CHAN_RBUF]; ssize_t len; int r; - size_t have, avail, maxlen = CHANNEL_MAX_READ; + size_t nr = 0, have, avail, maxlen = CHANNEL_MAX_READ; if ((c->io_ready & SSH_CHAN_IO_RFD) == 0) return 1; /* Shouldn't happen */ @@ -1974,13 +2044,15 @@ } 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 || errno == EAGAIN) return 1; debug2("channel %d: read failed rfd %d maxlen %zu: %s", c->self, c->rfd, maxlen, ssh_err(r)); goto rfail; } + if (nr != 0) + c->lastused = monotime(); return 1; } @@ -2001,6 +2073,7 @@ } 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); @@ -2071,6 +2144,7 @@ } return -1; } + c->lastused = monotime(); if (c->isatty && dlen >= 1 && buf[0] != '\r') { if (tcgetattr(c->wfd, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { @@ -2116,6 +2190,7 @@ 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; } @@ -2137,7 +2212,10 @@ 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); @@ -2426,14 +2504,29 @@ 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 (timeout != 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) { /* - * Arrange for poll wakeup when channel pause + * Arrange for poll() wakeup when channel pause * timer expires. */ ptimeout_deadline_monotime(timeout, @@ -3370,6 +3463,7 @@ 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;