- djm@cvs.openbsd.org 2003/05/15 14:55:25
     [readconf.c readconf.h ssh_config ssh_config.5 sshconnect.c]
     add a ConnectTimeout option to ssh, based on patch from
     Jean-Charles Longuet (jclonguet at free.fr); portable #207 ok markus@
diff --git a/ChangeLog b/ChangeLog
index 1e031aa..7a36475 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -10,6 +10,10 @@
    - markus@cvs.openbsd.org 2003/05/15 14:09:21
      [auth2-krb5.c]
      fix 64bit issue; report itojun@
+   - djm@cvs.openbsd.org 2003/05/15 14:55:25
+     [readconf.c readconf.h ssh_config ssh_config.5 sshconnect.c]
+     add a ConnectTimeout option to ssh, based on patch from
+     Jean-Charles Longuet (jclonguet at free.fr); portable #207 ok markus@
 
 20030515
  - (djm) OpenBSD CVS Sync
@@ -1541,4 +1545,4 @@
      save auth method before monitor_reset_key_state(); bugzilla bug #284;
      ok provos@
 
-$Id: ChangeLog,v 1.2726 2003/05/16 01:38:46 djm Exp $
+$Id: ChangeLog,v 1.2727 2003/05/16 01:39:04 djm Exp $
diff --git a/readconf.c b/readconf.c
index fee7a89..a0cf3d6 100644
--- a/readconf.c
+++ b/readconf.c
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: readconf.c,v 1.110 2003/05/15 14:02:47 jakob Exp $");
+RCSID("$OpenBSD: readconf.c,v 1.111 2003/05/15 14:55:25 djm Exp $");
 
 #include "ssh.h"
 #include "xmalloc.h"
@@ -106,7 +106,7 @@
 	oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
 	oHostKeyAlgorithms, oBindAddress, oSmartcardDevice,
 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
-	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS,
+	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
 	oDeprecated, oUnsupported
 } OpCodes;
 
@@ -193,6 +193,7 @@
 #endif
 	{ "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost },
 	{ "rekeylimit", oRekeyLimit },
+	{ "connecttimeout", oConnectTimeout },
 	{ NULL, oBadOption }
 };
 
@@ -309,6 +310,20 @@
 		/* don't panic, but count bad options */
 		return -1;
 		/* NOTREACHED */
+	case oConnectTimeout:
+		intptr = &options->connection_timeout;
+/* parse_time: */
+		arg = strdelim(&s);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: missing time value.",
+			    filename, linenum);
+		if ((value = convtime(arg)) == -1)
+			fatal("%s line %d: invalid time value.",
+			    filename, linenum);
+		if (*intptr == -1)
+			*intptr = value;
+		break;
+
 	case oForwardAgent:
 		intptr = &options->forward_agent;
 parse_flag:
@@ -808,6 +823,7 @@
 	options->compression_level = -1;
 	options->port = -1;
 	options->connection_attempts = -1;
+	options->connection_timeout = -1;
 	options->number_of_password_prompts = -1;
 	options->cipher = -1;
 	options->ciphers = NULL;
diff --git a/readconf.h b/readconf.h
index 991e200..c884de6 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: readconf.h,v 1.49 2003/05/15 01:48:10 jakob Exp $	*/
+/*	$OpenBSD: readconf.h,v 1.50 2003/05/15 14:55:25 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -60,6 +60,8 @@
 	int     port;		/* Port to connect. */
 	int     connection_attempts;	/* Max attempts (seconds) before
 					 * giving up */
+	int     connection_timeout;	/* Max time (seconds) before
+				 	 * aborting connection attempt */
 	int     number_of_password_prompts;	/* Max number of password
 						 * prompts. */
 	int     cipher;		/* Cipher to use. */
diff --git a/ssh_config b/ssh_config
index 94cffbf..8a0acc1 100644
--- a/ssh_config
+++ b/ssh_config
@@ -1,4 +1,4 @@
-#	$OpenBSD: ssh_config,v 1.16 2002/07/03 14:21:05 markus Exp $
+#	$OpenBSD: ssh_config,v 1.17 2003/05/15 14:55:25 djm Exp $
 
 # This is the ssh client system-wide configuration file.  See
 # ssh_config(5) for more information.  This file provides defaults for
@@ -25,6 +25,7 @@
 #   HostbasedAuthentication no
 #   BatchMode no
 #   CheckHostIP yes
+#   ConnectTimeout 0
 #   StrictHostKeyChecking ask
 #   IdentityFile ~/.ssh/identity
 #   IdentityFile ~/.ssh/id_rsa
diff --git a/ssh_config.5 b/ssh_config.5
index 2f33aa3..c5de4a9 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,7 +34,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.8 2003/05/14 18:16:20 jakob Exp $
+.\" $OpenBSD: ssh_config.5,v 1.9 2003/05/15 14:55:25 djm Exp $
 .Dd September 25, 1999
 .Dt SSH_CONFIG 5
 .Os
@@ -227,6 +227,11 @@
 The argument must be an integer.
 This may be useful in scripts if the connection sometimes fails.
 The default is 1.
+.It Cm ConnectTimeout
+Specifies the timeout (in seconds) used when connecting to the ssh
+server, instead of using the default system TCP timeout. This value is 
+used only when the target is down or really unreachable, not when it
+refuses the connection.
 .It Cm DynamicForward
 Specifies that a TCP/IP port on the local machine be forwarded
 over the secure channel, and the application
diff --git a/sshconnect.c b/sshconnect.c
index 32bef7d..8aac221 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -13,7 +13,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: sshconnect.c,v 1.140 2003/05/14 18:16:21 jakob Exp $");
+RCSID("$OpenBSD: sshconnect.c,v 1.141 2003/05/15 14:55:25 djm Exp $");
 
 #include <openssl/bn.h>
 
@@ -218,6 +218,71 @@
 	return sock;
 }
 
+static int
+timeout_connect(int sockfd, const struct sockaddr *serv_addr,
+    socklen_t addrlen, int timeout)
+{
+	fd_set *fdset;
+	struct timeval tv;
+	socklen_t optlen;
+	int fdsetsz, optval, rc;
+
+	if (timeout <= 0)
+		return (connect(sockfd, serv_addr, addrlen));
+
+	if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0)
+		return (-1);
+
+	rc = connect(sockfd, serv_addr, addrlen);
+	if (rc == 0)
+		return (0);
+	if (errno != EINPROGRESS)
+		return (-1);
+
+	fdsetsz = howmany(sockfd + 1, NFDBITS) * sizeof(fd_mask);
+	fdset = (fd_set *)xmalloc(fdsetsz);
+
+	memset(fdset, '\0', fdsetsz);
+	FD_SET(sockfd, fdset);
+	tv.tv_sec = timeout;
+	tv.tv_usec = 0;
+
+	for(;;) {
+		rc = select(sockfd + 1, NULL, fdset, NULL, &tv);
+		if (rc != -1 || errno != EINTR)
+			break;
+	}
+
+	switch(rc) {
+	case 0:
+		/* Timed out */
+		errno = ETIMEDOUT;
+		return (-1);
+	case -1:
+		/* Select error */
+	    	debug("select: %s", strerror(errno));
+		return (-1);
+	case 1:
+		/* Completed or failed */
+		optval = 0;
+		optlen = sizeof(optval);
+		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, 
+		    &optlen) == -1)
+		    	debug("getsockopt: %s", strerror(errno));
+			return (-1);
+		if (optval != 0) {
+			errno = optval;
+			return (-1);
+		}
+		break;
+	default:
+		/* Should not occur */
+		fatal("Bogus return (%d) from select()", rc);
+	}
+
+	return (0);
+}
+
 /*
  * Opens a TCP/IP connection to the remote server on the given host.
  * The address of the remote host will be returned in hostaddr.
@@ -306,7 +371,8 @@
 				/* Any error is already output */
 				continue;
 
-			if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) {
+			if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen,
+			    options.connection_timeout) >= 0) {
 				/* Successful connection. */
 				memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen);
 				break;