add ChannelTimeout support to the client, mirroring the same option
authordjm <djm@openbsd.org>
Wed, 11 Oct 2023 22:42:26 +0000 (22:42 +0000)
committerdjm <djm@openbsd.org>
Wed, 11 Oct 2023 22:42:26 +0000 (22:42 +0000)
in the server. ok markus@

usr.bin/ssh/clientloop.c
usr.bin/ssh/misc.c
usr.bin/ssh/misc.h
usr.bin/ssh/readconf.c
usr.bin/ssh/readconf.h
usr.bin/ssh/servconf.c
usr.bin/ssh/ssh.c
usr.bin/ssh/ssh_config.5

index 656f1a5..940a7e8 100644 (file)
@@ -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 <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1792,7 +1792,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;
@@ -1827,7 +1827,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);
@@ -1855,7 +1855,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;
 
index 59ee9c9..433c234 100644 (file)
@@ -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.
@@ -2386,6 +2386,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)
index 5e0c452..b10b503 100644 (file)
@@ -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 <ylo@cs.hut.fi>
@@ -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 *);
index 2d85d48..12d1446 100644 (file)
@@ -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 <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -162,7 +162,7 @@ typedef enum {
        oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms,
        oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
        oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
-       oEnableEscapeCommandline, oObscureKeystrokeTiming,
+       oEnableEscapeCommandline, oObscureKeystrokeTiming, oChannelTimeout,
        oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -312,6 +312,7 @@ static struct {
        { "requiredrsasize", oRequiredRSASize },
        { "enableescapecommandline", oEnableEscapeCommandline },
        { "obscurekeystroketiming", oObscureKeystrokeTiming },
+       { "channeltimeout", oChannelTimeout },
 
        { NULL, oBadOption }
 };
@@ -2300,6 +2301,31 @@ parse_pubkey_algos:
                        *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);
@@ -2552,6 +2578,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;
 }
 
 /*
@@ -2785,6 +2813,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);
@@ -2793,6 +2831,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) {
@@ -3497,6 +3538,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 */
 
index ce261bd..702b027 100644 (file)
@@ -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 <ylo@cs.hut.fi>
@@ -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;
 
index a3c3270..84b3fb1 100644 (file)
@@ -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 <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -905,39 +905,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)
 {
@@ -948,7 +915,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]);
@@ -2488,7 +2455,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);
                        }
index f215872..bebeada 100644 (file)
@@ -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 <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1552,6 +1552,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,
index c479d74..65f5345 100644 (file)
@@ -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 ,