You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
14 KiB
455 lines
14 KiB
Index: clientloop.c |
|
--- clientloop.c.orig 2010-08-03 08:04:46.000000000 +0200 |
|
+++ clientloop.c 2010-08-24 18:28:31.000000000 +0200 |
|
@@ -161,6 +161,7 @@ |
|
static u_int buffer_high; /* Soft max buffer size. */ |
|
static int connection_in; /* Connection to server (input). */ |
|
static int connection_out; /* Connection to server (output). */ |
|
+static time_t idle_time_last; /* Last time of packet transmission. */ |
|
static int need_rekeying; /* Set to non-zero if rekeying is requested. */ |
|
static int session_closed; /* In SSH2: login session closed. */ |
|
static int x11_refuse_time; /* If >0, refuse x11 opens after this time. */ |
|
@@ -621,8 +622,11 @@ |
|
if (timeout_secs < 0) |
|
timeout_secs = 0; |
|
} |
|
- if (timeout_secs == INT_MAX) |
|
- tvp = NULL; |
|
+ if (timeout_secs == INT_MAX) { |
|
+ tv.tv_sec = 0; |
|
+ tv.tv_usec = 500 * 1000; /* time slot is 0.5sec */ |
|
+ tvp = &tv; |
|
+ } |
|
else { |
|
tv.tv_sec = timeout_secs; |
|
tv.tv_usec = 0; |
|
@@ -647,8 +651,43 @@ |
|
snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); |
|
buffer_append(&stderr_buffer, buf, strlen(buf)); |
|
quit_pending = 1; |
|
- } else if (ret == 0) |
|
- server_alive_check(); |
|
+ } else if (ret == 0){ |
|
+ if (options.server_alive_interval != 0 && compat20){ |
|
+ server_alive_check(); |
|
+ } |
|
+ } |
|
+ |
|
+ /* If the output channel has been silent for more than a specified |
|
+ * time, send a keepalive packet (heartbeat) to the server. |
|
+ * Keepalive packet is useful for keeping the connection over |
|
+ * IP masquerade / NAT boxes, firewalls, etc. |
|
+ * Some servers equipped with a watchdog timer require keepalive |
|
+ * packets (heartbeats) to detect link down. |
|
+ * |
|
+ * Note: Although the interval between keepalive packets is not |
|
+ * very precise, it's okay. |
|
+ * |
|
+ * Note: Some old servers may crash when they receive SSH_MSG_IGNORE. |
|
+ * Those who want to connect to such a server should turn this |
|
+ * function off by the option setting (e.g. Heartbeat 0). |
|
+ */ |
|
+ if (options.heartbeat_interval > 0) { |
|
+ if (FD_ISSET(connection_out,*writesetp)) { |
|
+ /* Update the time of last data transmission. */ |
|
+ idle_time_last = time(NULL); |
|
+ } |
|
+ if (time(NULL) - idle_time_last >= (int)options.heartbeat_interval){ |
|
+ if (compat20) { |
|
+ packet_start(SSH2_MSG_IGNORE); |
|
+ } |
|
+ else { |
|
+ packet_start(SSH_MSG_IGNORE); |
|
+ } |
|
+ packet_put_string("", 0); |
|
+ packet_send(); |
|
+ /* fputs("*",stderr); */ |
|
+ } |
|
+ } |
|
} |
|
|
|
static void |
|
@@ -1367,6 +1406,7 @@ |
|
debug("Entering interactive session."); |
|
|
|
start_time = get_current_time(); |
|
+ idle_time_last = time(NULL); |
|
|
|
/* Initialize variables. */ |
|
escape_pending1 = 0; |
|
Index: readconf.c |
|
--- readconf.c.orig 2010-08-03 08:04:46.000000000 +0200 |
|
+++ readconf.c 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -118,7 +118,7 @@ |
|
oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, |
|
oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, |
|
oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, |
|
- oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, |
|
+ oCompressionLevel, oTCPKeepAlive, oHeartbeat, oNumberOfPasswordPrompts, |
|
oUsePrivilegedPort, oLogLevel, oCiphers, oProtocol, oMacs, |
|
oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, |
|
oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, |
|
@@ -201,6 +201,7 @@ |
|
{ "compressionlevel", oCompressionLevel }, |
|
{ "tcpkeepalive", oTCPKeepAlive }, |
|
{ "keepalive", oTCPKeepAlive }, /* obsolete */ |
|
+ { "heartbeat", oHeartbeat }, |
|
{ "numberofpasswordprompts", oNumberOfPasswordPrompts }, |
|
{ "loglevel", oLogLevel }, |
|
{ "dynamicforward", oDynamicForward }, |
|
@@ -522,6 +523,10 @@ |
|
intptr = &options->no_host_authentication_for_localhost; |
|
goto parse_flag; |
|
|
|
+ case oHeartbeat: |
|
+ intptr = &options->heartbeat_interval; |
|
+ goto parse_int; |
|
+ |
|
case oNumberOfPasswordPrompts: |
|
intptr = &options->number_of_password_prompts; |
|
goto parse_int; |
|
@@ -1069,6 +1074,7 @@ |
|
options->strict_host_key_checking = -1; |
|
options->compression = -1; |
|
options->tcp_keep_alive = -1; |
|
+ options->heartbeat_interval = -1; |
|
options->compression_level = -1; |
|
options->port = -1; |
|
options->address_family = -1; |
|
@@ -1176,6 +1182,8 @@ |
|
options->compression = 0; |
|
if (options->tcp_keep_alive == -1) |
|
options->tcp_keep_alive = 1; |
|
+ if (options->heartbeat_interval == -1) |
|
+ options->heartbeat_interval = 0; /* 0 means "no heartbeat" */ |
|
if (options->compression_level == -1) |
|
options->compression_level = 6; |
|
if (options->port == -1) |
|
Index: readconf.h |
|
--- readconf.h.orig 2010-08-03 08:04:46.000000000 +0200 |
|
+++ readconf.h 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -59,6 +59,9 @@ |
|
int compression_level; /* Compression level 1 (fast) to 9 |
|
* (best). */ |
|
int tcp_keep_alive; /* Set SO_KEEPALIVE. */ |
|
+ int heartbeat_interval; /* Number of seconds between keepalive |
|
+ * packets (heartbeats) over encrypted |
|
+ * channel. (in secs.) */ |
|
LogLevel log_level; /* Level for logging. */ |
|
|
|
int port; /* Port to connect. */ |
|
Index: servconf.c |
|
--- servconf.c.orig 2010-06-26 01:38:45.000000000 +0200 |
|
+++ servconf.c 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -81,6 +81,8 @@ |
|
options->xauth_location = NULL; |
|
options->strict_modes = -1; |
|
options->tcp_keep_alive = -1; |
|
+ options->watchdog_timeout = -1; |
|
+ options->watchdog_timeout1 = -1; |
|
options->log_facility = SYSLOG_FACILITY_NOT_SET; |
|
options->log_level = SYSLOG_LEVEL_NOT_SET; |
|
options->rhosts_rsa_authentication = -1; |
|
@@ -191,6 +193,10 @@ |
|
options->strict_modes = 1; |
|
if (options->tcp_keep_alive == -1) |
|
options->tcp_keep_alive = 1; |
|
+ if (options->watchdog_timeout == -1) |
|
+ options->watchdog_timeout = 0; /* 0 means "no timeout" */ |
|
+ if (options->watchdog_timeout1 == -1) |
|
+ options->watchdog_timeout1 = 0; /* 0 means "no timeout" */ |
|
if (options->log_facility == SYSLOG_FACILITY_NOT_SET) |
|
options->log_facility = SYSLOG_FACILITY_AUTH; |
|
if (options->log_level == SYSLOG_LEVEL_NOT_SET) |
|
@@ -298,7 +304,7 @@ |
|
sListenAddress, sAddressFamily, |
|
sPrintMotd, sPrintLastLog, sIgnoreRhosts, |
|
sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, |
|
- sStrictModes, sEmptyPasswd, sTCPKeepAlive, |
|
+ sStrictModes, sEmptyPasswd, sTCPKeepAlive, sWatchdogTimeout, sWatchdogTimeout1, |
|
sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, |
|
sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, |
|
sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, |
|
@@ -401,6 +407,8 @@ |
|
{ "compression", sCompression, SSHCFG_GLOBAL }, |
|
{ "tcpkeepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, |
|
{ "keepalive", sTCPKeepAlive, SSHCFG_GLOBAL }, /* obsolete alias */ |
|
+ { "watchdogtimeout", sWatchdogTimeout, SSHCFG_GLOBAL }, |
|
+ { "watchdogtimeout1", sWatchdogTimeout1, SSHCFG_GLOBAL }, |
|
{ "allowtcpforwarding", sAllowTcpForwarding, SSHCFG_ALL }, |
|
{ "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL }, |
|
{ "allowusers", sAllowUsers, SSHCFG_GLOBAL }, |
|
@@ -978,6 +986,14 @@ |
|
intptr = &options->tcp_keep_alive; |
|
goto parse_flag; |
|
|
|
+ case sWatchdogTimeout: |
|
+ intptr = &options->watchdog_timeout; |
|
+ goto parse_int; |
|
+ |
|
+ case sWatchdogTimeout1: |
|
+ intptr = &options->watchdog_timeout1; |
|
+ goto parse_int; |
|
+ |
|
case sEmptyPasswd: |
|
intptr = &options->permit_empty_passwd; |
|
goto parse_flag; |
|
Index: servconf.h |
|
--- servconf.h.orig 2010-05-10 03:58:03.000000000 +0200 |
|
+++ servconf.h 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -70,6 +70,10 @@ |
|
char *xauth_location; /* Location of xauth program */ |
|
int strict_modes; /* If true, require string home dir modes. */ |
|
int tcp_keep_alive; /* If true, set SO_KEEPALIVE. */ |
|
+ int watchdog_timeout, watchdog_timeout1; |
|
+ /* Timeout of the watchdog timer which |
|
+ checks the input activities over |
|
+ encrypted channel. (in secs.) */ |
|
char *ciphers; /* Supported SSH2 ciphers. */ |
|
char *macs; /* Supported SSH2 macs. */ |
|
int protocol; /* Supported protocol versions. */ |
|
Index: serverloop.c |
|
--- serverloop.c.orig 2009-09-09 03:07:28.000000000 +0200 |
|
+++ serverloop.c 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -107,6 +107,8 @@ |
|
static int connection_closed = 0; /* Connection to client closed. */ |
|
static u_int buffer_high; /* "Soft" max buffer size. */ |
|
static int no_more_sessions = 0; /* Disallow further sessions. */ |
|
+static time_t idle_time_last; /* Last time of packet receipt. */ |
|
+static int child_forced_to_terminate; /* The child will be killed by sshd. */ |
|
|
|
/* |
|
* This SIGCHLD kludge is used to detect when the child exits. The server |
|
@@ -281,6 +283,7 @@ |
|
{ |
|
struct timeval tv, *tvp; |
|
int ret; |
|
+ int watchdog_timeout = 0; |
|
int client_alive_scheduled = 0; |
|
int program_alive_scheduled = 0; |
|
|
|
@@ -350,6 +353,19 @@ |
|
if (max_time_milliseconds == 0 || client_alive_scheduled) |
|
max_time_milliseconds = 100; |
|
|
|
+ /* When the watchdog is needed, set the maximum length |
|
+ * of timeout to 0.25sec. |
|
+ */ |
|
+ watchdog_timeout = options.watchdog_timeout; |
|
+ if (!compat20 && options.watchdog_timeout1 > 0){ |
|
+ watchdog_timeout = options.watchdog_timeout1; |
|
+ } |
|
+ if (watchdog_timeout > 0) { |
|
+ if (max_time_milliseconds == 0 || max_time_milliseconds > 250) { |
|
+ max_time_milliseconds = 250; |
|
+ } |
|
+ } |
|
+ |
|
if (max_time_milliseconds == 0) |
|
tvp = NULL; |
|
else { |
|
@@ -377,6 +393,23 @@ |
|
} |
|
} |
|
|
|
+ /* |
|
+ * Watchdog timer: |
|
+ * If the input channel has been silent for more than the specified |
|
+ * time, try to kill the child process(es) to protect server resources. |
|
+ */ |
|
+ if (watchdog_timeout > 0) { |
|
+ if (FD_ISSET(connection_in,*readsetp)) { |
|
+ /* Update the time of last data receipt. */ |
|
+ idle_time_last = time(NULL); |
|
+ /* fputs("*",stderr); */ |
|
+ } |
|
+ if (!child_terminated && \ |
|
+ (time(NULL) - idle_time_last) > watchdog_timeout) { |
|
+ child_forced_to_terminate = 1; |
|
+ } |
|
+ } |
|
+ |
|
notify_done(*readsetp); |
|
} |
|
|
|
@@ -560,7 +593,9 @@ |
|
u_int max_time_milliseconds; |
|
u_int previous_stdout_buffer_bytes; |
|
u_int stdout_buffer_bytes; |
|
- int type; |
|
+ int type, i; |
|
+ |
|
+ child_forced_to_terminate = 0; |
|
|
|
debug("Entering interactive session."); |
|
|
|
@@ -627,6 +662,8 @@ |
|
|
|
server_init_dispatch(); |
|
|
|
+ idle_time_last = time(NULL); |
|
+ |
|
/* Main loop of the server for the interactive session mode. */ |
|
for (;;) { |
|
|
|
@@ -707,6 +744,9 @@ |
|
cleanup_exit(255); |
|
} |
|
|
|
+ /* Break, if watchdog timeout occured. */ |
|
+ if (child_forced_to_terminate) break; |
|
+ |
|
/* Process any channel events. */ |
|
channel_after_select(readset, writeset); |
|
|
|
@@ -716,6 +756,24 @@ |
|
/* Process output to the client and to program stdin. */ |
|
process_output(writeset); |
|
} |
|
+ |
|
+ /* |
|
+ * If the child should be terminated due to |
|
+ * watchdog timeout, send kill signal to the child. |
|
+ */ |
|
+ if (child_forced_to_terminate) { |
|
+ /* We won't have pid=0. However, for safety... */ |
|
+ if ( pid != 0 ){ |
|
+ kill(pid, SIGHUP); |
|
+ for (i=0 ; i<5 ; i++){ |
|
+ sleep(1); |
|
+ if (child_terminated) break; |
|
+ } |
|
+ if (i>=5) kill(pid, SIGKILL); |
|
+ logit("Warning: Command has been killed due to watchdog timeout."); |
|
+ } |
|
+ } |
|
+ |
|
if (readset) |
|
xfree(readset); |
|
if (writeset) |
|
@@ -724,7 +782,9 @@ |
|
/* Cleanup and termination code. */ |
|
|
|
/* Wait until all output has been sent to the client. */ |
|
- drain_output(); |
|
+ if (!child_forced_to_terminate) { |
|
+ drain_output(); |
|
+ } |
|
|
|
debug("End of interactive session; stdin %ld, stdout (read %ld, sent %ld), stderr %ld bytes.", |
|
stdin_bytes, fdout_bytes, stdout_bytes, stderr_bytes); |
|
@@ -752,6 +812,12 @@ |
|
/* We no longer want our SIGCHLD handler to be called. */ |
|
mysignal(SIGCHLD, SIG_DFL); |
|
|
|
+ /* If the child has been terminated, free the session and exit here. */ |
|
+ if (child_forced_to_terminate) { |
|
+ session_destroy_all(NULL); |
|
+ return; |
|
+ } |
|
+ |
|
while ((wait_pid = waitpid(-1, &wait_status, 0)) < 0) |
|
if (errno != EINTR) |
|
packet_disconnect("wait: %.100s", strerror(errno)); |
|
@@ -825,6 +891,7 @@ |
|
|
|
mysignal(SIGCHLD, sigchld_handler); |
|
child_terminated = 0; |
|
+ child_forced_to_terminate = 0; |
|
connection_in = packet_get_connection_in(); |
|
connection_out = packet_get_connection_out(); |
|
|
|
@@ -841,6 +908,8 @@ |
|
|
|
server_init_dispatch(); |
|
|
|
+ idle_time_last = time(NULL); |
|
+ |
|
for (;;) { |
|
process_buffered_input_packets(); |
|
|
|
@@ -857,6 +926,12 @@ |
|
cleanup_exit(255); |
|
} |
|
|
|
+ /* Terminate child processes, if watchdog timeout occured. */ |
|
+ if (child_forced_to_terminate){ |
|
+ packet_disconnect("Command has been killed due to watchdog timeout."); |
|
+ logit("Warning: Command has been killed due to watchdog timeout."); |
|
+ } |
|
+ |
|
collect_children(); |
|
if (!rekeying) { |
|
channel_after_select(readset, writeset); |
|
Index: ssh.1 |
|
--- ssh.1.orig 2010-08-05 05:03:13.000000000 +0200 |
|
+++ ssh.1 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -428,6 +428,7 @@ |
|
.It GSSAPIAuthentication |
|
.It GSSAPIDelegateCredentials |
|
.It HashKnownHosts |
|
+.It Heartbeat |
|
.It Host |
|
.It HostbasedAuthentication |
|
.It HostKeyAlgorithms |
|
Index: ssh_config.5 |
|
--- ssh_config.5.orig 2010-08-05 05:03:13.000000000 +0200 |
|
+++ ssh_config.5 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -531,6 +531,23 @@ |
|
will not be converted automatically, |
|
but may be manually hashed using |
|
.Xr ssh-keygen 1 . |
|
+.It Cm Heartbeat |
|
+Specifies the interval between heartbeats, in seconds. If the output |
|
+channel has been silent for more than the specified time, a null message |
|
+(SSH_MSG_IGNORE) is sent to the server. |
|
+.Cm Heartbeat |
|
+is useful for keeping alive connections over IP masquerade / NAT boxes, |
|
+firewalls, etc., that implement connection timeouts, and in combination |
|
+with the |
|
+.Cm WatchdogTimeout |
|
+option to |
|
+.Xr sshd 8 . |
|
+Heartbeat does not work if |
|
+.Cm ServerAliveInterval |
|
+is enabled at the same time. |
|
+The default is |
|
+.Dq 0 , |
|
+which disables the hearbeat. |
|
.It Cm HostbasedAuthentication |
|
Specifies whether to try rhosts based authentication with public key |
|
authentication. |
|
Index: sshd_config.5 |
|
--- sshd_config.5.orig 2010-07-02 05:37:17.000000000 +0200 |
|
+++ sshd_config.5 2010-08-24 18:25:44.000000000 +0200 |
|
@@ -1021,6 +1021,30 @@ |
|
escalation by containing any corruption within the unprivileged processes. |
|
The default is |
|
.Dq yes . |
|
+.It Cm WatchdogTimeout |
|
+Specifies the watchdog timeout interval, in seconds. |
|
+If a session input channel has been silent for more than the specified interval, |
|
+.Cm sshd |
|
+terminates the session by killing the child process(es). Only input |
|
+packets from the client reset the watchdog timer; this makes it possible |
|
+to terminate a session even if the server continues sending some data |
|
+to the client. |
|
+When used in combination with |
|
+.Cm ClientAliveInterval |
|
+and/or the |
|
+.Cm Heartbeat |
|
+option of |
|
+.Xr ssh 1 |
|
+this feature will detect and terminate hung sessions over unreliable |
|
+networks, without interfering with normal usage. |
|
+The default is |
|
+.Dq 0 , |
|
+which disables the watchdog. |
|
+.It Cm WatchdogTimeout1 |
|
+Specifies the watchdog timeout interval, in seconds, for SSH1 protocol |
|
+only. See the |
|
+.Cm WatchdogTimeout |
|
+option. |
|
.It Cm X11DisplayOffset |
|
Specifies the first display number available for |
|
.Xr sshd 8 Ns 's
|
|
|