diff --git a/clientloop.c b/clientloop.c index 3e9fa3220b7..e813163746c 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.398 2023/09/10 03:51:55 djm Exp $ */ +/* $OpenBSD: clientloop.c,v 1.399 2023/10/11 22:42:26 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1801,7 +1801,7 @@ client_request_x11(struct ssh *ssh, const char *request_type, int rchan) sock = x11_connect_display(ssh); if (sock < 0) return NULL; - c = channel_new(ssh, "x11", + c = channel_new(ssh, "x11-connection", SSH_CHANNEL_X11_OPEN, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); c->force_drain = 1; @@ -1836,7 +1836,7 @@ client_request_agent(struct ssh *ssh, const char *request_type, int rchan) else debug2_fr(r, "ssh_agent_bind_hostkey"); - c = channel_new(ssh, "authentication agent connection", + c = channel_new(ssh, "agent-connection", SSH_CHANNEL_OPEN, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "authentication agent connection", 1); @@ -1864,7 +1864,7 @@ client_request_tun_fwd(struct ssh *ssh, int tun_mode, } debug("Tunnel forwarding using interface %s", ifname); - c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, + c = channel_new(ssh, "tun-connection", SSH_CHANNEL_OPENING, fd, fd, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; diff --git a/misc.c b/misc.c index 42582c61829..662b7c67064 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.187 2023/08/28 03:31:16 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.188 2023/10/11 22:42:26 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -2493,6 +2493,43 @@ format_absolute_time(uint64_t t, char *buf, size_t len) strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &tm); } +/* + * Parse a "pattern=interval" clause (e.g. a ChannelTimeout). + * Returns 0 on success or non-zero on failure. + * Caller must free *typep. + */ +int +parse_pattern_interval(const char *s, char **typep, int *secsp) +{ + char *cp, *sdup; + int secs; + + if (typep != NULL) + *typep = NULL; + if (secsp != NULL) + *secsp = 0; + if (s == NULL) + return -1; + sdup = xstrdup(s); + + if ((cp = strchr(sdup, '=')) == NULL || cp == sdup) { + free(sdup); + return -1; + } + *cp++ = '\0'; + if ((secs = convtime(cp)) < 0) { + free(sdup); + return -1; + } + /* success */ + if (typep != NULL) + *typep = xstrdup(sdup); + if (secsp != NULL) + *secsp = secs; + free(sdup); + return 0; +} + /* check if path is absolute */ int path_absolute(const char *path) diff --git a/misc.h b/misc.h index 4f941597e9e..74c6f832cb1 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.105 2023/08/28 03:31:16 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.106 2023/10/11 22:42:26 djm Exp $ */ /* * Author: Tatu Ylonen @@ -95,6 +95,7 @@ int valid_env_name(const char *); const char *atoi_err(const char *, int *); int parse_absolute_time(const char *, uint64_t *); void format_absolute_time(uint64_t, char *, size_t); +int parse_pattern_interval(const char *, char **, int *); int path_absolute(const char *); int stdfd_devnull(int, int, int); int lib_contains_symbol(const char *, const char *); diff --git a/readconf.c b/readconf.c index 131c24f526d..23fb604d0fb 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.381 2023/08/28 03:31:16 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.382 2023/10/11 22:42:26 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -178,7 +178,7 @@ typedef enum { oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms, oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize, - oEnableEscapeCommandline, oObscureKeystrokeTiming, + oEnableEscapeCommandline, oObscureKeystrokeTiming, oChannelTimeout, oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -328,6 +328,7 @@ static struct { { "requiredrsasize", oRequiredRSASize }, { "enableescapecommandline", oEnableEscapeCommandline }, { "obscurekeystroketiming", oObscureKeystrokeTiming }, + { "channeltimeout", oChannelTimeout }, { NULL, oBadOption } }; @@ -2323,6 +2324,31 @@ process_config_line_depth(Options *options, struct passwd *pw, const char *host, *intptr = value; break; + case oChannelTimeout: + uvalue = options->num_channel_timeouts; + i = 0; + while ((arg = argv_next(&ac, &av)) != NULL) { + /* Allow "none" only in first position */ + if (strcasecmp(arg, "none") == 0) { + if (i > 0 || ac > 0) { + error("%s line %d: keyword %s \"none\" " + "argument must appear alone.", + filename, linenum, keyword); + goto out; + } + } else if (parse_pattern_interval(arg, + NULL, NULL) != 0) { + fatal("%s line %d: invalid channel timeout %s", + filename, linenum, arg); + } + if (!*activep || uvalue != 0) + continue; + opt_array_append(filename, linenum, keyword, + &options->channel_timeouts, + &options->num_channel_timeouts, arg); + } + break; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -2575,6 +2601,8 @@ initialize_options(Options * options) options->enable_escape_commandline = -1; options->obscure_keystroke_timing_interval = -1; options->tag = NULL; + options->channel_timeouts = NULL; + options->num_channel_timeouts = 0; } /* @@ -2815,6 +2843,16 @@ fill_default_options(Options * options) v = NULL; \ } \ } while(0) +#define CLEAR_ON_NONE_ARRAY(v, nv, none) \ + do { \ + if (options->nv == 1 && \ + strcasecmp(options->v[0], none) == 0) { \ + free(options->v[0]); \ + free(options->v); \ + options->v = NULL; \ + options->nv = 0; \ + } \ + } while (0) CLEAR_ON_NONE(options->local_command); CLEAR_ON_NONE(options->remote_command); CLEAR_ON_NONE(options->proxy_command); @@ -2823,6 +2861,9 @@ fill_default_options(Options * options) CLEAR_ON_NONE(options->pkcs11_provider); CLEAR_ON_NONE(options->sk_provider); CLEAR_ON_NONE(options->known_hosts_command); + CLEAR_ON_NONE_ARRAY(channel_timeouts, num_channel_timeouts, "none"); +#undef CLEAR_ON_NONE +#undef CLEAR_ON_NONE_ARRAY if (options->jump_host != NULL && strcmp(options->jump_host, "none") == 0 && options->jump_port == 0 && options->jump_user == NULL) { @@ -3527,6 +3568,8 @@ dump_client_config(Options *o, const char *host) dump_cfg_strarray(oSetEnv, o->num_setenv, o->setenv); dump_cfg_strarray_oneline(oLogVerbose, o->num_log_verbose, o->log_verbose); + dump_cfg_strarray_oneline(oChannelTimeout, + o->num_channel_timeouts, o->channel_timeouts); /* Special cases */ diff --git a/readconf.h b/readconf.h index ce261bd6364..702b027de89 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.152 2023/08/28 03:31:16 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.153 2023/10/11 22:42:26 djm Exp $ */ /* * Author: Tatu Ylonen @@ -182,6 +182,9 @@ typedef struct { int enable_escape_commandline; /* ~C commandline */ int obscure_keystroke_timing_interval; + char **channel_timeouts; /* inactivity timeout by channel type */ + u_int num_channel_timeouts; + char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } Options; diff --git a/servconf.c b/servconf.c index 49f7f7322e7..86c2979360c 100644 --- a/servconf.c +++ b/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.402 2023/09/08 06:34:24 djm Exp $ */ +/* $OpenBSD: servconf.c,v 1.403 2023/10/11 22:42:26 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -956,39 +956,6 @@ process_permitopen(struct ssh *ssh, ServerOptions *options) options->num_permitted_listens); } -/* Parse a ChannelTimeout clause "pattern=interval" */ -static int -parse_timeout(const char *s, char **typep, int *secsp) -{ - char *cp, *sdup; - int secs; - - if (typep != NULL) - *typep = NULL; - if (secsp != NULL) - *secsp = 0; - if (s == NULL) - return -1; - sdup = xstrdup(s); - - if ((cp = strchr(sdup, '=')) == NULL || cp == sdup) { - free(sdup); - return -1; - } - *cp++ = '\0'; - if ((secs = convtime(cp)) < 0) { - free(sdup); - return -1; - } - /* success */ - if (typep != NULL) - *typep = xstrdup(sdup); - if (secsp != NULL) - *secsp = secs; - free(sdup); - return 0; -} - void process_channel_timeouts(struct ssh *ssh, ServerOptions *options) { @@ -999,7 +966,7 @@ process_channel_timeouts(struct ssh *ssh, ServerOptions *options) debug3_f("setting %u timeouts", options->num_channel_timeouts); channel_clear_timeouts(ssh); for (i = 0; i < options->num_channel_timeouts; i++) { - if (parse_timeout(options->channel_timeouts[i], + if (parse_pattern_interval(options->channel_timeouts[i], &type, &secs) != 0) { fatal_f("internal error: bad timeout %s", options->channel_timeouts[i]); @@ -2549,7 +2516,8 @@ process_server_config_line_depth(ServerOptions *options, char *line, filename, linenum, keyword); goto out; } - } else if (parse_timeout(arg, NULL, NULL) != 0) { + } else if (parse_pattern_interval(arg, + NULL, NULL) != 0) { fatal("%s line %d: invalid channel timeout %s", filename, linenum, arg); } diff --git a/ssh.c b/ssh.c index 1dbbda7d6e3..8b8c595e533 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.594 2023/09/03 23:59:32 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.595 2023/10/11 22:42:26 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1573,6 +1573,20 @@ main(int ac, char **av) else timeout_ms = options.connection_timeout * 1000; + /* Apply channels timeouts, if set */ + channel_clear_timeouts(ssh); + for (j = 0; j < options.num_channel_timeouts; j++) { + debug3("applying channel timeout %s", + options.channel_timeouts[j]); + if (parse_pattern_interval(options.channel_timeouts[j], + &cp, &i) != 0) { + fatal_f("internal error: bad timeout %s", + options.channel_timeouts[j]); + } + channel_add_timeout(ssh, cp, i); + free(cp); + } + /* Open a connection to the remote host. */ if (ssh_connect(ssh, host, options.host_arg, addrs, &hostaddr, options.port, options.connection_attempts, diff --git a/ssh_config.5 b/ssh_config.5 index dd72a98f978..d1c7037d4b8 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,7 +33,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.389 2023/10/11 06:40:54 djm Exp $ +.\" $OpenBSD: ssh_config.5,v 1.390 2023/10/11 22:42:26 djm Exp $ .Dd $Mdocdate: October 11 2023 $ .Dt SSH_CONFIG 5 .Os @@ -455,6 +455,73 @@ Multiple .Cm CertificateFile directives will add to the list of certificates used for authentication. +.It Cm ChannelTimeout +Specifies whether and how quickly +.Xr ssh 1 +should close inactive channels. +Timeouts are specified as one or more +.Dq type=interval +pairs separated by whitespace, where the +.Dq type +must be a channel type name (as described in the table below), optionally +containing wildcard characters. +.Pp +The timeout value +.Dq interval +is specified in seconds or may use any of the units documented in the +.Sx TIME FORMATS +section. +For example, +.Dq session=5m +would cause the interactive session to terminate after five minutes of +inactivity. +Specifying a zero value disables the inactivity timeout. +.Pp +The available channel types include: +.Bl -tag -width Ds +.It Cm agent-connection +Open connections to +.Xr ssh-agent 1 . +.It Cm direct-tcpip , Cm direct-streamlocal@openssh.com +Open TCP or Unix socket (respectively) connections that have +been established from a +.Xr ssh 1 +local forwarding, i.e.\& +.Cm LocalForward +or +.Cm DynamicForward . +.It Cm forwarded-tcpip , Cm forwarded-streamlocal@openssh.com +Open TCP or Unix socket (respectively) connections that have been +established to a +.Xr sshd 8 +listening on behalf of a +.Xr ssh 1 +remote forwarding, i.e.\& +.Cm RemoteForward . +.It Cm session +The interactive main session, including shell session, command execution, +.Xr scp 1 , +.Xr sftp 1 , +etc. +.It Cm tun-connection +Open +.Cm TunnelForward +connections. +.It Cm x11-connection +Open X11 forwarding sessions. +.El +.Pp +Note that in all the above cases, terminating an inactive session does not +guarantee to remove all resources associated with the session, e.g. shell +processes or X11 clients relating to the session may continue to execute. +.Pp +Moreover, terminating an inactive channel or session does not necessarily +close the SSH connection, nor does it prevent a client from +requesting another channel of the same type. +In particular, expiring an inactive forwarding session does not prevent +another identical forwarding from being subsequently created. +.Pp +The default is not to expire channels of any type for inactivity. .It Cm CheckHostIP If set to .Cm yes ,