- beck@cvs.openbsd.org 2001/04/13 22:46:54
     [channels.c channels.h servconf.c servconf.h serverloop.c sshd.8]
     Add options ClientAliveInterval and ClientAliveCountMax to sshd.
     This gives the ability to do a "keepalive" via the encrypted channel
     which can't be spoofed (unlike TCP keepalives). Useful for when you want
     to use ssh connections to authenticate people for something, and know
     relatively quickly when they are no longer authenticated. Disabled
     by default (of course). ok markus@
diff --git a/serverloop.c b/serverloop.c
index d6b360d..5a5b1e3 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -35,7 +35,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: serverloop.c,v 1.60 2001/04/05 23:39:20 markus Exp $");
+RCSID("$OpenBSD: serverloop.c,v 1.61 2001/04/13 22:46:54 beck Exp $");
 
 #include "xmalloc.h"
 #include "packet.h"
@@ -91,6 +91,8 @@
 
 void	server_init_dispatch(void);
 
+int client_alive_timeouts = 0;
+
 void
 sigchld_handler(int sig)
 {
@@ -190,6 +192,21 @@
 {
 	struct timeval tv, *tvp;
 	int ret;
+	int client_alive_scheduled = 0;
+
+	/*
+	 * if using client_alive, set the max timeout accordingly, 
+	 * and indicate that this particular timeout was for client
+	 * alive by setting the client_alive_scheduled flag.
+	 *
+	 * this could be randomized somewhat to make traffic
+	 * analysis more difficult, but we're not doing it yet.  
+	 */
+	if (max_time_milliseconds == 0 && options.client_alive_interval) {
+	        client_alive_scheduled = 1;
+		max_time_milliseconds = options.client_alive_interval * 1000;
+	} else 
+	        client_alive_scheduled = 0;
 
 	/* When select fails we restart from here. */
 retry_select:
@@ -239,7 +256,7 @@
 	 * from it, then read as much as is available and exit.
 	 */
 	if (child_terminated && packet_not_very_much_data_to_write())
-		if (max_time_milliseconds == 0)
+		if (max_time_milliseconds == 0 || client_alive_scheduled)
 			max_time_milliseconds = 100;
 
 	if (max_time_milliseconds == 0)
@@ -255,12 +272,36 @@
 	/* Wait for something to happen, or the timeout to expire. */
 	ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
 
-	if (ret < 0) {
+	if (ret == -1) {
 		if (errno != EINTR)
 			error("select: %.100s", strerror(errno));
 		else
 			goto retry_select;
 	}
+	if (ret == 0 && client_alive_scheduled) {
+		/* timeout, check to see how many we have had */
+		client_alive_timeouts++;
+
+		if (client_alive_timeouts > options.client_alive_count_max ) {
+			packet_disconnect(
+				"Timeout, your session not responding.");
+		} else {
+			/*
+			 * send a bogus channel request with "wantreply" 
+			 * we should get back a failure
+			 */
+			int id;
+			
+			id = channel_find_open();
+			if (id != -1) {
+				channel_request_start(id,
+				  "keepalive@openssh.com", 1);
+				packet_send();
+			} else 
+				packet_disconnect(
+					"No open channels after timeout!");
+		}
+	} 
 }
 
 /*
@@ -701,6 +742,19 @@
 }
 
 void
+server_input_channel_failure(int type, int plen, void *ctxt)
+{
+	debug("Got CHANNEL_FAILURE for keepalive");
+	/* 
+         * reset timeout, since we got a sane answer from the client.
+	 * even if this was generated by something other than
+	 * the bogus CHANNEL_REQUEST we send for keepalives.
+	 */
+	client_alive_timeouts = 0; 
+}
+
+
+void
 server_input_stdin_data(int type, int plen, void *ctxt)
 {
 	char *data;
@@ -912,7 +966,8 @@
 	dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &channel_input_channel_request);
 	dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust);
 	dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request);
-
+	/* client_alive */
+	dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_channel_failure);
 	/* rekeying */
 	dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit);
 }
@@ -949,3 +1004,4 @@
 	else
 		server_init_dispatch_15();
 }
+