upstream: Add a sshd_config UnusedConnectionTimeout option to terminate

client connections that have no open channels for some length of time. This
complements the recently-added ChannelTimeout option that terminates inactive
channels after a timeout.

ok markus@

OpenBSD-Commit-ID: ca983be74c0350364c11f8ba3bd692f6f24f5da9
This commit is contained in:
djm@openbsd.org 2023-01-17 09:44:48 +00:00 committed by Damien Miller
parent 8ec2e31238
commit 0293c19807
No known key found for this signature in database
4 changed files with 88 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/* $OpenBSD: servconf.c,v 1.389 2023/01/06 02:47:18 djm Exp $ */ /* $OpenBSD: servconf.c,v 1.390 2023/01/17 09:44:48 djm Exp $ */
/* /*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved * All rights reserved
@ -198,6 +198,7 @@ initialize_server_options(ServerOptions *options)
options->required_rsa_size = -1; options->required_rsa_size = -1;
options->channel_timeouts = NULL; options->channel_timeouts = NULL;
options->num_channel_timeouts = 0; options->num_channel_timeouts = 0;
options->unused_connection_timeout = -1;
} }
/* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
@ -446,6 +447,8 @@ fill_default_server_options(ServerOptions *options)
options->sk_provider = xstrdup("internal"); options->sk_provider = xstrdup("internal");
if (options->required_rsa_size == -1) if (options->required_rsa_size == -1)
options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
if (options->unused_connection_timeout == -1)
options->unused_connection_timeout = 0;
assemble_algorithms(options); assemble_algorithms(options);
@ -529,7 +532,7 @@ typedef enum {
sStreamLocalBindMask, sStreamLocalBindUnlink, sStreamLocalBindMask, sStreamLocalBindUnlink,
sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
sRequiredRSASize, sChannelTimeout, sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout,
sDeprecated, sIgnore, sUnsupported sDeprecated, sIgnore, sUnsupported
} ServerOpCodes; } ServerOpCodes;
@ -691,6 +694,7 @@ static struct {
{ "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL },
{ "requiredrsasize", sRequiredRSASize, SSHCFG_ALL }, { "requiredrsasize", sRequiredRSASize, SSHCFG_ALL },
{ "channeltimeout", sChannelTimeout, SSHCFG_ALL }, { "channeltimeout", sChannelTimeout, SSHCFG_ALL },
{ "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL },
{ NULL, sBadOption, 0 } { NULL, sBadOption, 0 }
}; };
@ -2537,6 +2541,17 @@ process_server_config_line_depth(ServerOptions *options, char *line,
} }
break; break;
case sUnusedConnectionTimeout:
intptr = &options->unused_connection_timeout;
/* peek at first arg for "none" so we can reuse parse_time */
if (av[0] != NULL && strcasecmp(av[0], "none") == 0) {
(void)argv_next(&ac, &av); /* consume arg */
if (*activep)
*intptr = 0;
break;
}
goto parse_time;
case sDeprecated: case sDeprecated:
case sIgnore: case sIgnore:
case sUnsupported: case sUnsupported:
@ -2709,6 +2724,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
M_CP_INTOPT(rekey_interval); M_CP_INTOPT(rekey_interval);
M_CP_INTOPT(log_level); M_CP_INTOPT(log_level);
M_CP_INTOPT(required_rsa_size); M_CP_INTOPT(required_rsa_size);
M_CP_INTOPT(unused_connection_timeout);
/* /*
* The bind_mask is a mode_t that may be unsigned, so we can't use * The bind_mask is a mode_t that may be unsigned, so we can't use
@ -2861,6 +2877,10 @@ fmt_intarg(ServerOpCodes code, int val)
static void static void
dump_cfg_int(ServerOpCodes code, int val) dump_cfg_int(ServerOpCodes code, int val)
{ {
if (code == sUnusedConnectionTimeout && val == 0) {
printf("%s none\n", lookup_opcode_name(code));
return;
}
printf("%s %d\n", lookup_opcode_name(code), val); printf("%s %d\n", lookup_opcode_name(code), val);
} }
@ -2977,6 +2997,7 @@ dump_config(ServerOptions *o)
dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max); dump_cfg_int(sClientAliveCountMax, o->client_alive_count_max);
dump_cfg_int(sRequiredRSASize, o->required_rsa_size); dump_cfg_int(sRequiredRSASize, o->required_rsa_size);
dump_cfg_oct(sStreamLocalBindMask, o->fwd_opts.streamlocal_bind_mask); dump_cfg_oct(sStreamLocalBindMask, o->fwd_opts.streamlocal_bind_mask);
dump_cfg_int(sUnusedConnectionTimeout, o->unused_connection_timeout);
/* formatted integer arguments */ /* formatted integer arguments */
dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login); dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.h,v 1.158 2023/01/06 02:47:19 djm Exp $ */ /* $OpenBSD: servconf.h,v 1.159 2023/01/17 09:44:48 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -233,6 +233,8 @@ typedef struct {
char **channel_timeouts; /* inactivity timeout by channel type */ char **channel_timeouts; /* inactivity timeout by channel type */
u_int num_channel_timeouts; u_int num_channel_timeouts;
int unused_connection_timeout;
} ServerOptions; } ServerOptions;
/* Information about the incoming connection as used by Match */ /* Information about the incoming connection as used by Match */

View File

@ -1,4 +1,4 @@
/* $OpenBSD: serverloop.c,v 1.233 2023/01/06 02:38:23 djm Exp $ */ /* $OpenBSD: serverloop.c,v 1.234 2023/01/17 09:44:48 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -172,17 +172,19 @@ wait_until_can_do_something(struct ssh *ssh,
int *conn_in_readyp, int *conn_out_readyp) int *conn_in_readyp, int *conn_out_readyp)
{ {
struct timespec timeout; struct timespec timeout;
char remote_id[512];
int ret; int ret;
int client_alive_scheduled = 0; int client_alive_scheduled = 0;
u_int p; u_int p;
/* time we last heard from the client OR sent a keepalive */ time_t now;
static time_t last_client_time; static time_t last_client_time, unused_connection_expiry;
*conn_in_readyp = *conn_out_readyp = 0; *conn_in_readyp = *conn_out_readyp = 0;
/* Prepare channel poll. First two pollfd entries are reserved */ /* Prepare channel poll. First two pollfd entries are reserved */
ptimeout_init(&timeout); ptimeout_init(&timeout);
channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep, 2, &timeout); channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep, 2, &timeout);
now = monotime();
if (*npfd_activep < 2) if (*npfd_activep < 2)
fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */ fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */
if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) { if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) {
@ -190,6 +192,18 @@ wait_until_can_do_something(struct ssh *ssh,
ssh_packet_get_rekey_timeout(ssh)); ssh_packet_get_rekey_timeout(ssh));
} }
/*
* If no channels are open and UnusedConnectionTimeout is set, then
* start the clock to terminate the connection.
*/
if (options.unused_connection_timeout != 0) {
if (channel_still_open(ssh) || unused_connection_expiry == 0) {
unused_connection_expiry = now +
options.unused_connection_timeout;
}
ptimeout_deadline_monotime(&timeout, unused_connection_expiry);
}
/* /*
* if using client_alive, set the max timeout accordingly, * if using client_alive, set the max timeout accordingly,
* and indicate that this particular timeout was for client * and indicate that this particular timeout was for client
@ -199,8 +213,9 @@ wait_until_can_do_something(struct ssh *ssh,
* analysis more difficult, but we're not doing it yet. * analysis more difficult, but we're not doing it yet.
*/ */
if (options.client_alive_interval) { if (options.client_alive_interval) {
/* Time we last heard from the client OR sent a keepalive */
if (last_client_time == 0) if (last_client_time == 0)
last_client_time = monotime(); last_client_time = now;
ptimeout_deadline_sec(&timeout, options.client_alive_interval); ptimeout_deadline_sec(&timeout, options.client_alive_interval);
/* XXX ? deadline_monotime(last_client_time + alive_interval) */ /* XXX ? deadline_monotime(last_client_time + alive_interval) */
client_alive_scheduled = 1; client_alive_scheduled = 1;
@ -237,9 +252,9 @@ wait_until_can_do_something(struct ssh *ssh,
*conn_in_readyp = (*pfdp)[0].revents != 0; *conn_in_readyp = (*pfdp)[0].revents != 0;
*conn_out_readyp = (*pfdp)[1].revents != 0; *conn_out_readyp = (*pfdp)[1].revents != 0;
now = monotime(); /* need to reset after ppoll() */
/* ClientAliveInterval probing */ /* ClientAliveInterval probing */
if (client_alive_scheduled) { if (client_alive_scheduled) {
time_t now = monotime();
if (ret == 0 && if (ret == 0 &&
now > last_client_time + options.client_alive_interval) { now > last_client_time + options.client_alive_interval) {
/* ppoll timed out and we're due to probe */ /* ppoll timed out and we're due to probe */
@ -250,6 +265,14 @@ wait_until_can_do_something(struct ssh *ssh,
last_client_time = now; last_client_time = now;
} }
} }
/* UnusedConnectionTimeout handling */
if (unused_connection_expiry != 0 &&
now > unused_connection_expiry && !channel_still_open(ssh)) {
sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id));
logit("terminating inactive connection from %s", remote_id);
cleanup_exit(255);
}
} }
/* /*

View File

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.\" $OpenBSD: sshd_config.5,v 1.345 2023/01/06 08:44:11 jmc Exp $ .\" $OpenBSD: sshd_config.5,v 1.346 2023/01/17 09:44:48 djm Exp $
.Dd $Mdocdate: January 6 2023 $ .Dd $Mdocdate: January 17 2023 $
.Dt SSHD_CONFIG 5 .Dt SSHD_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -459,6 +459,9 @@ close the SSH connection, nor does it prevent a client from
requesting another channel of the same type. requesting another channel of the same type.
In particular, expiring an inactive forwarding session does not prevent In particular, expiring an inactive forwarding session does not prevent
another identical forwarding from being subsequently created. another identical forwarding from being subsequently created.
See also
.Cm UnusedConnectionTimeout ,
which may be used in conjunction with this option.
.Pp .Pp
The default is not to expire channels of any type for inactivity. The default is not to expire channels of any type for inactivity.
.It Cm ChrootDirectory .It Cm ChrootDirectory
@ -1256,6 +1259,7 @@ Available keywords are
.Cm AuthorizedPrincipalsFile , .Cm AuthorizedPrincipalsFile ,
.Cm Banner , .Cm Banner ,
.Cm CASignatureAlgorithms , .Cm CASignatureAlgorithms ,
.Cm ChannelTimeout ,
.Cm ChrootDirectory , .Cm ChrootDirectory ,
.Cm ClientAliveCountMax , .Cm ClientAliveCountMax ,
.Cm ClientAliveInterval , .Cm ClientAliveInterval ,
@ -1295,6 +1299,7 @@ Available keywords are
.Cm StreamLocalBindMask , .Cm StreamLocalBindMask ,
.Cm StreamLocalBindUnlink , .Cm StreamLocalBindUnlink ,
.Cm TrustedUserCAKeys , .Cm TrustedUserCAKeys ,
.Cm UnusedConnectionTimeout ,
.Cm X11DisplayOffset , .Cm X11DisplayOffset ,
.Cm X11Forwarding .Cm X11Forwarding
and and
@ -1811,6 +1816,33 @@ for authentication using
.Cm TrustedUserCAKeys . .Cm TrustedUserCAKeys .
For more details on certificates, see the CERTIFICATES section in For more details on certificates, see the CERTIFICATES section in
.Xr ssh-keygen 1 . .Xr ssh-keygen 1 .
.It Cm UnusedConnectionTimeout
Specifies whether and how quickly
.Xr sshd 8
should close client connections with no open channels.
Open channels include active shell, command execution or subsystem
sessions, connected network, socket, agent of X11 forwardings.
Forwarding listeners, such as those from the
.Xr ssh 1
.Fl R
flag are not considered as open channels and do not prevent the timeout.
The timeout value
is specified in seconds or may use any of the units documented in the
.Sx TIME FORMATS
section.
.Pp
Note that this timeout starts when the client connection completes
user authentication but before the client has an opportunity to open any
channels.
Caution should be used when using short timeout values, as they may not
provide sufficient time for the client to request and open its channels
before terminating the connection.
.Pp
The default
.Cm none
is to never expire connections for having no open channels.
This option may be useful in conjunction with
.Cm ChannelTimeout .
.It Cm UseDNS .It Cm UseDNS
Specifies whether Specifies whether
.Xr sshd 8 .Xr sshd 8