- djm@cvs.openbsd.org 2005/03/01 10:09:52
     [auth-options.c channels.c channels.h clientloop.c compat.c compat.h]
     [misc.c misc.h readconf.c readconf.h servconf.c ssh.1 ssh.c ssh_config.5]
     [sshd_config.5]
     bz#413: allow optional specification of bind address for port forwardings.
     Patch originally by Dan Astorian, but worked on by several people
     Adds GatewayPorts=clientspecified option on server to allow remote
     forwards to bind to client-specified ports.
diff --git a/ChangeLog b/ChangeLog
index e4ec748..f31a526 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -19,6 +19,14 @@
      [ssh_config.5]
      bz#849: document timeout on untrusted x11 forwarding sessions. Reported by
      orion AT cora.nwra.com; ok markus@
+   - djm@cvs.openbsd.org 2005/03/01 10:09:52
+     [auth-options.c channels.c channels.h clientloop.c compat.c compat.h]
+     [misc.c misc.h readconf.c readconf.h servconf.c ssh.1 ssh.c ssh_config.5]
+     [sshd_config.5]
+     bz#413: allow optional specification of bind address for port forwardings.
+     Patch originally by Dan Astorian, but worked on by several people
+     Adds GatewayPorts=clientspecified option on server to allow remote 
+     forwards to bind to client-specified ports.
 
 20050226
  - (dtucker) [openbsd-compat/bsd-openpty.c openbsd-compat/inet_ntop.c]
@@ -2195,4 +2203,4 @@
    - (djm) Trim deprecated options from INSTALL. Mention UsePAM
    - (djm) Fix quote handling in sftp; Patch from admorten AT umich.edu
 
-$Id: ChangeLog,v 1.3671 2005/03/01 10:17:31 djm Exp $
+$Id: ChangeLog,v 1.3672 2005/03/01 10:24:33 djm Exp $
diff --git a/auth-options.c b/auth-options.c
index 0e146ab..04d12d6 100644
--- a/auth-options.c
+++ b/auth-options.c
@@ -10,7 +10,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: auth-options.c,v 1.28 2003/06/02 09:17:34 markus Exp $");
+RCSID("$OpenBSD: auth-options.c,v 1.29 2005/03/01 10:09:52 djm Exp $");
 
 #include "xmalloc.h"
 #include "match.h"
@@ -217,7 +217,7 @@
 		}
 		cp = "permitopen=\"";
 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
-			char host[256], sport[6];
+			char *host, *p;
 			u_short port;
 			char *patterns = xmalloc(strlen(opts) + 1);
 
@@ -236,25 +236,29 @@
 			if (!*opts) {
 				debug("%.100s, line %lu: missing end quote",
 				    file, linenum);
-				auth_debug_add("%.100s, line %lu: missing end quote",
-				    file, linenum);
+				auth_debug_add("%.100s, line %lu: missing "
+				    "end quote", file, linenum);
 				xfree(patterns);
 				goto bad_option;
 			}
 			patterns[i] = 0;
 			opts++;
-			if (sscanf(patterns, "%255[^:]:%5[0-9]", host, sport) != 2 &&
-			    sscanf(patterns, "%255[^/]/%5[0-9]", host, sport) != 2) {
-				debug("%.100s, line %lu: Bad permitopen specification "
-				    "<%.100s>", file, linenum, patterns);
+			p = patterns;
+			host = hpdelim(&p);
+			if (host == NULL || strlen(host) >= NI_MAXHOST) {
+				debug("%.100s, line %lu: Bad permitopen "
+				    "specification <%.100s>", file, linenum, 
+				    patterns);
 				auth_debug_add("%.100s, line %lu: "
-				    "Bad permitopen specification", file, linenum);
+				    "Bad permitopen specification", file,
+				    linenum);
 				xfree(patterns);
 				goto bad_option;
 			}
-			if ((port = a2port(sport)) == 0) {
-				debug("%.100s, line %lu: Bad permitopen port <%.100s>",
-				    file, linenum, sport);
+ 			host = cleanhostname(host);
+ 			if (p == NULL || (port = a2port(p)) == 0) {
+				debug("%.100s, line %lu: Bad permitopen port "
+				    "<%.100s>", file, linenum, p ? p : "");
 				auth_debug_add("%.100s, line %lu: "
 				    "Bad permitopen port", file, linenum);
 				xfree(patterns);
diff --git a/channels.c b/channels.c
index 8550e51..1be213b 100644
--- a/channels.c
+++ b/channels.c
@@ -39,7 +39,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.211 2004/10/29 21:47:15 djm Exp $");
+RCSID("$OpenBSD: channels.c,v 1.212 2005/03/01 10:09:52 djm Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -2179,14 +2179,14 @@
     const char *host_to_connect, u_short port_to_connect, int gateway_ports)
 {
 	Channel *c;
-	int success, sock, on = 1;
+	int sock, r, success = 0, on = 1, wildcard = 0, is_client;
 	struct addrinfo hints, *ai, *aitop;
-	const char *host;
+	const char *host, *addr;
 	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
 
-	success = 0;
 	host = (type == SSH_CHANNEL_RPORT_LISTENER) ?
 	    listen_addr : host_to_connect;
+	is_client = (type == SSH_CHANNEL_PORT_LISTENER);
 
 	if (host == NULL) {
 		error("No forward host name.");
@@ -2198,16 +2198,60 @@
 	}
 
 	/*
+	 * Determine whether or not a port forward listens to loopback,
+	 * specified address or wildcard. On the client, a specified bind 
+	 * address will always override gateway_ports. On the server, a 
+	 * gateway_ports of 1 (``yes'') will override the client's 
+	 * specification and force a wildcard bind, whereas a value of 2 
+	 * (``clientspecified'') will bind to whatever address the client 
+	 * asked for.
+	 *
+	 * Special-case listen_addrs are:
+	 *
+	 * "0.0.0.0"               -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR
+	 * "" (empty string), "*"  -> wildcard v4/v6
+	 * "localhost"             -> loopback v4/v6
+	 */
+	addr = NULL;
+	if (listen_addr == NULL) {
+		/* No address specified: default to gateway_ports setting */
+		if (gateway_ports)
+			wildcard = 1;
+	} else if (gateway_ports || is_client) {
+		if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
+		    strcmp(listen_addr, "0.0.0.0") == 0) ||
+		    *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 ||
+		    (!is_client && gateway_ports == 1))
+			wildcard = 1;
+		else if (strcmp(listen_addr, "localhost") != 0)
+			addr = listen_addr;
+	}
+
+	debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s",
+	    type, wildcard, (addr == NULL) ? "NULL" : addr);
+
+	/*
 	 * getaddrinfo returns a loopback address if the hostname is
 	 * set to NULL and hints.ai_flags is not AI_PASSIVE
 	 */
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_family = IPv4or6;
-	hints.ai_flags = gateway_ports ? AI_PASSIVE : 0;
+	hints.ai_flags = wildcard ? AI_PASSIVE : 0;
 	hints.ai_socktype = SOCK_STREAM;
 	snprintf(strport, sizeof strport, "%d", listen_port);
-	if (getaddrinfo(NULL, strport, &hints, &aitop) != 0)
-		packet_disconnect("getaddrinfo: fatal error");
+	if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) {
+		if (addr == NULL) {
+			/* This really shouldn't happen */
+			packet_disconnect("getaddrinfo: fatal error: %s",
+			    gai_strerror(r));
+		} else {
+			verbose("channel_setup_fwd_listener: "
+			    "getaddrinfo(%.64s): %s", addr, gai_strerror(r));
+			packet_send_debug("channel_setup_fwd_listener: "
+			    "getaddrinfo(%.64s): %s", addr, gai_strerror(r));
+		}
+		aitop = NULL;
+	}
 
 	for (ai = aitop; ai; ai = ai->ai_next) {
 		if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
@@ -2290,11 +2334,12 @@
 
 /* protocol local port fwd, used by ssh (and sshd in v1) */
 int
-channel_setup_local_fwd_listener(u_short listen_port,
+channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port,
     const char *host_to_connect, u_short port_to_connect, int gateway_ports)
 {
 	return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER,
-	    NULL, listen_port, host_to_connect, port_to_connect, gateway_ports);
+	    listen_host, listen_port, host_to_connect, port_to_connect,
+	    gateway_ports);
 }
 
 /* protocol v2 remote port fwd, used by sshd */
@@ -2312,7 +2357,7 @@
  */
 
 void
-channel_request_remote_forwarding(u_short listen_port,
+channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
     const char *host_to_connect, u_short port_to_connect)
 {
 	int type, success = 0;
@@ -2323,7 +2368,14 @@
 
 	/* Send the forward request to the remote side. */
 	if (compat20) {
-		const char *address_to_bind = "0.0.0.0";
+		const char *address_to_bind;
+		if (listen_host == NULL)
+			address_to_bind = "localhost";
+		else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0)
+			address_to_bind = "";
+		else
+			address_to_bind = listen_host;
+
 		packet_start(SSH2_MSG_GLOBAL_REQUEST);
 		packet_put_cstring("tcpip-forward");
 		packet_put_char(1);			/* boolean: want reply */
@@ -2369,10 +2421,9 @@
  * local side.
  */
 void
-channel_request_rforward_cancel(u_short port)
+channel_request_rforward_cancel(const char *host, u_short port)
 {
 	int i;
-	const char *address_to_bind = "0.0.0.0";
 
 	if (!compat20)
 		return;
@@ -2389,7 +2440,7 @@
 	packet_start(SSH2_MSG_GLOBAL_REQUEST);
 	packet_put_cstring("cancel-tcpip-forward");
 	packet_put_char(0);
-	packet_put_cstring(address_to_bind);
+	packet_put_cstring(host == NULL ? "" : host);
 	packet_put_int(port);
 	packet_send();
 
@@ -2430,7 +2481,8 @@
 #endif
 
 	/* Initiate forwarding */
-	channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports);
+	channel_setup_local_fwd_listener(NULL, port, hostname,
+	    host_port, gateway_ports);
 
 	/* Free the argument string. */
 	xfree(hostname);
diff --git a/channels.h b/channels.h
index c47de55..fc20fb2 100644
--- a/channels.h
+++ b/channels.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: channels.h,v 1.75 2004/10/29 21:47:15 djm Exp $	*/
+/*	$OpenBSD: channels.h,v 1.76 2005/03/01 10:09:52 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -203,9 +203,11 @@
 void     channel_input_port_forward_request(int, int);
 int	 channel_connect_to(const char *, u_short);
 int	 channel_connect_by_listen_address(u_short);
-void	 channel_request_remote_forwarding(u_short, const char *, u_short);
-void	 channel_request_rforward_cancel(u_short port);
-int	 channel_setup_local_fwd_listener(u_short, const char *, u_short, int);
+void	 channel_request_remote_forwarding(const char *, u_short,
+	     const char *, u_short);
+int	 channel_setup_local_fwd_listener(const char *, u_short,
+	     const char *, u_short, int);
+void	 channel_request_rforward_cancel(const char *host, u_short port);
 int	 channel_setup_remote_fwd_listener(const char *, u_short, int);
 int	 channel_cancel_rport_listener(const char *, u_short);
 
diff --git a/clientloop.c b/clientloop.c
index 033a98a..1e25088 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -59,7 +59,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: clientloop.c,v 1.134 2004/11/07 00:01:46 djm Exp $");
+RCSID("$OpenBSD: clientloop.c,v 1.135 2005/03/01 10:09:52 djm Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -763,11 +763,11 @@
 process_cmdline(void)
 {
 	void (*handler)(int);
-	char *s, *cmd;
-	u_short fwd_port, fwd_host_port;
-	char buf[1024], sfwd_port[6], sfwd_host_port[6];
+	char *s, *cmd, *cancel_host;
 	int delete = 0;
 	int local = 0;
+	u_short cancel_port;
+	Forward fwd;
 
 	leave_raw_mode();
 	handler = signal(SIGINT, SIG_IGN);
@@ -813,37 +813,38 @@
 		s++;
 
 	if (delete) {
-		if (sscanf(s, "%5[0-9]", sfwd_host_port) != 1) {
-			logit("Bad forwarding specification.");
+		cancel_port = 0;
+		cancel_host = hpdelim(&s);	/* may be NULL */
+		if (s != NULL) {
+			cancel_port = a2port(s);
+			cancel_host = cleanhostname(cancel_host);
+		} else {
+			cancel_port = a2port(cancel_host);
+			cancel_host = NULL;
+		}
+		if (cancel_port == 0) {
+			logit("Bad forwarding close port");
 			goto out;
 		}
-		if ((fwd_host_port = a2port(sfwd_host_port)) == 0) {
-			logit("Bad forwarding port(s).");
-			goto out;
-		}
-		channel_request_rforward_cancel(fwd_host_port);
+		channel_request_rforward_cancel(cancel_host, cancel_port);
 	} else {
-		if (sscanf(s, "%5[0-9]:%255[^:]:%5[0-9]",
-		    sfwd_port, buf, sfwd_host_port) != 3 &&
-		    sscanf(s, "%5[0-9]/%255[^/]/%5[0-9]",
-		    sfwd_port, buf, sfwd_host_port) != 3) {
+		if (!parse_forward(&fwd, s)) {
 			logit("Bad forwarding specification.");
 			goto out;
 		}
-		if ((fwd_port = a2port(sfwd_port)) == 0 ||
-		    (fwd_host_port = a2port(sfwd_host_port)) == 0) {
-			logit("Bad forwarding port(s).");
-			goto out;
-		}
 		if (local) {
-			if (channel_setup_local_fwd_listener(fwd_port, buf,
-			    fwd_host_port, options.gateway_ports) < 0) {
+			if (channel_setup_local_fwd_listener(fwd.listen_host,
+			    fwd.listen_port, fwd.connect_host,
+			    fwd.connect_port, options.gateway_ports) < 0) {
 				logit("Port forwarding failed.");
 				goto out;
 			}
-		} else
-			channel_request_remote_forwarding(fwd_port, buf,
-			    fwd_host_port);
+		} else {
+			channel_request_remote_forwarding(fwd.listen_host,
+			    fwd.listen_port, fwd.connect_host,
+			    fwd.connect_port);
+		}
+
 		logit("Forwarding port.");
 	}
 
diff --git a/compat.c b/compat.c
index 2fdebe7..4086e85 100644
--- a/compat.c
+++ b/compat.c
@@ -23,7 +23,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: compat.c,v 1.70 2003/11/02 11:01:03 markus Exp $");
+RCSID("$OpenBSD: compat.c,v 1.71 2005/03/01 10:09:52 djm Exp $");
 
 #include "buffer.h"
 #include "packet.h"
@@ -62,24 +62,28 @@
 		  "OpenSSH_2.1*,"
 		  "OpenSSH_2.2*",	SSH_OLD_SESSIONID|SSH_BUG_BANNER|
 					SSH_OLD_DHGEX|SSH_BUG_NOREKEY|
-					SSH_BUG_EXTEOF},
+					SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.3.0*",	SSH_BUG_BANNER|SSH_BUG_BIGENDIANAES|
 					SSH_OLD_DHGEX|SSH_BUG_NOREKEY|
-					SSH_BUG_EXTEOF},
+					SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.3.*",	SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX|
-					SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
+					SSH_BUG_NOREKEY|SSH_BUG_EXTEOF|
+					SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.5.0p1*,"
 		  "OpenSSH_2.5.1p1*",
 					SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX|
-					SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
+					SSH_BUG_NOREKEY|SSH_BUG_EXTEOF|
+					SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.5.0*,"
 		  "OpenSSH_2.5.1*,"
 		  "OpenSSH_2.5.2*",	SSH_OLD_DHGEX|SSH_BUG_NOREKEY|
-					SSH_BUG_EXTEOF},
-		{ "OpenSSH_2.5.3*",	SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
+					SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR},
+		{ "OpenSSH_2.5.3*",	SSH_BUG_NOREKEY|SSH_BUG_EXTEOF|
+					SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.*,"
 		  "OpenSSH_3.0*,"
-		  "OpenSSH_3.1*",	SSH_BUG_EXTEOF},
+		  "OpenSSH_3.1*",	SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR},
+		{ "OpenSSH_3.*",	SSH_OLD_FORWARD_ADDR },
 		{ "Sun_SSH_1.0*",	SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
 		{ "OpenSSH*",		0 },
 		{ "*MindTerm*",		0 },
diff --git a/compat.h b/compat.h
index 5efb5c2..cf92dbd 100644
--- a/compat.h
+++ b/compat.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: compat.h,v 1.38 2004/07/11 17:48:47 deraadt Exp $	*/
+/*	$OpenBSD: compat.h,v 1.39 2005/03/01 10:09:52 djm Exp $	*/
 
 /*
  * Copyright (c) 1999, 2000, 2001 Markus Friedl.  All rights reserved.
@@ -55,6 +55,7 @@
 #define SSH_BUG_EXTEOF		0x00200000
 #define SSH_BUG_PROBE		0x00400000
 #define SSH_BUG_FIRSTKEX	0x00800000
+#define SSH_OLD_FORWARD_ADDR	0x01000000
 
 void     enable_compat13(void);
 void     enable_compat20(void);
diff --git a/misc.c b/misc.c
index a901255..2e366f8 100644
--- a/misc.c
+++ b/misc.c
@@ -23,7 +23,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: misc.c,v 1.27 2004/12/11 01:48:56 dtucker Exp $");
+RCSID("$OpenBSD: misc.c,v 1.28 2005/03/01 10:09:52 djm Exp $");
 
 #include "misc.h"
 #include "log.h"
@@ -275,6 +275,48 @@
 	return total;
 }
 
+/*
+ * Search for next delimiter between hostnames/addresses and ports.
+ * Argument may be modified (for termination).
+ * Returns *cp if parsing succeeds.
+ * *cp is set to the start of the next delimiter, if one was found.
+ * If this is the last field, *cp is set to NULL.
+ */
+char *
+hpdelim(char **cp)
+{
+	char *s, *old;
+
+	if (cp == NULL || *cp == NULL)
+		return NULL;
+
+	old = s = *cp;
+	if (*s == '[') {
+		if ((s = strchr(s, ']')) == NULL)
+			return NULL;
+		else
+			s++;
+	} else if ((s = strpbrk(s, ":/")) == NULL)
+		s = *cp + strlen(*cp); /* skip to end (see first case below) */
+
+	switch (*s) {
+	case '\0':
+		*cp = NULL;	/* no more fields*/
+		break;
+	
+	case ':':
+	case '/':
+		*s = '\0';	/* terminate */
+		*cp = s + 1;
+		break;
+	
+	default:
+		return NULL;
+	}
+
+	return old;
+}
+
 char *
 cleanhostname(char *host)
 {
diff --git a/misc.h b/misc.h
index 193216f..8bbc87f 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: misc.h,v 1.20 2004/12/11 01:48:56 dtucker Exp $	*/
+/*	$OpenBSD: misc.h,v 1.21 2005/03/01 10:09:52 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -20,6 +20,7 @@
 int	 unset_nonblock(int);
 void	 set_nodelay(int);
 int	 a2port(const char *);
+char	*hpdelim(char **);
 char	*cleanhostname(char *);
 char	*colon(char *);
 long	 convtime(const char *);
diff --git a/readconf.c b/readconf.c
index a4fe1fe..c3dc71e 100644
--- a/readconf.c
+++ b/readconf.c
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: readconf.c,v 1.134 2004/07/11 17:48:47 deraadt Exp $");
+RCSID("$OpenBSD: readconf.c,v 1.135 2005/03/01 10:09:52 djm Exp $");
 
 #include "ssh.h"
 #include "xmalloc.h"
@@ -206,21 +206,23 @@
  */
 
 void
-add_local_forward(Options *options, u_short port, const char *host,
-		  u_short host_port)
+add_local_forward(Options *options, const Forward *newfwd)
 {
 	Forward *fwd;
 #ifndef NO_IPPORT_RESERVED_CONCEPT
 	extern uid_t original_real_uid;
-	if (port < IPPORT_RESERVED && original_real_uid != 0)
+	if (newfwd->listen_port < IPPORT_RESERVED && original_real_uid != 0)
 		fatal("Privileged ports can only be forwarded by root.");
 #endif
 	if (options->num_local_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION)
 		fatal("Too many local forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION);
 	fwd = &options->local_forwards[options->num_local_forwards++];
-	fwd->port = port;
-	fwd->host = xstrdup(host);
-	fwd->host_port = host_port;
+
+	fwd->listen_host = (newfwd->listen_host == NULL) ?
+	    NULL : xstrdup(newfwd->listen_host);
+	fwd->listen_port = newfwd->listen_port;
+	fwd->connect_host = xstrdup(newfwd->connect_host);
+	fwd->connect_port = newfwd->connect_port;
 }
 
 /*
@@ -229,17 +231,19 @@
  */
 
 void
-add_remote_forward(Options *options, u_short port, const char *host,
-		   u_short host_port)
+add_remote_forward(Options *options, const Forward *newfwd)
 {
 	Forward *fwd;
 	if (options->num_remote_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION)
 		fatal("Too many remote forwards (max %d).",
 		    SSH_MAX_FORWARDS_PER_DIRECTION);
 	fwd = &options->remote_forwards[options->num_remote_forwards++];
-	fwd->port = port;
-	fwd->host = xstrdup(host);
-	fwd->host_port = host_port;
+
+	fwd->listen_host = (newfwd->listen_host == NULL) ?
+	    NULL : xstrdup(newfwd->listen_host);
+	fwd->listen_port = newfwd->listen_port;
+	fwd->connect_host = xstrdup(newfwd->connect_host);
+	fwd->connect_port = newfwd->connect_port;
 }
 
 static void
@@ -247,11 +251,15 @@
 {
 	int i;
 
-	for (i = 0; i < options->num_local_forwards; i++)
-		xfree(options->local_forwards[i].host);
+	for (i = 0; i < options->num_local_forwards; i++) {
+		xfree(options->local_forwards[i].listen_host);
+		xfree(options->local_forwards[i].connect_host);
+	}
 	options->num_local_forwards = 0;
-	for (i = 0; i < options->num_remote_forwards; i++)
-		xfree(options->remote_forwards[i].host);
+	for (i = 0; i < options->num_remote_forwards; i++) {
+		xfree(options->remote_forwards[i].listen_host);
+		xfree(options->remote_forwards[i].connect_host);
+	}
 	options->num_remote_forwards = 0;
 }
 
@@ -284,11 +292,10 @@
 		    char *line, const char *filename, int linenum,
 		    int *activep)
 {
-	char buf[256], *s, **charptr, *endofnumber, *keyword, *arg;
+	char *s, **charptr, *endofnumber, *keyword, *arg, *arg2, fwdarg[256];
 	int opcode, *intptr, value;
 	size_t len;
-	u_short fwd_port, fwd_host_port;
-	char sfwd_host_port[6];
+	Forward fwd;
 
 	/* Strip trailing whitespace */
 	for(len = strlen(line) - 1; len > 0; len--) {
@@ -645,30 +652,26 @@
 	case oLocalForward:
 	case oRemoteForward:
 		arg = strdelim(&s);
-		if (!arg || *arg == '\0')
+		if (arg == NULL || *arg == '\0')
 			fatal("%.200s line %d: Missing port argument.",
 			    filename, linenum);
-		if ((fwd_port = a2port(arg)) == 0)
-			fatal("%.200s line %d: Bad listen port.",
+		arg2 = strdelim(&s);
+		if (arg2 == NULL || *arg2 == '\0')
+			fatal("%.200s line %d: Missing target argument.",
 			    filename, linenum);
-		arg = strdelim(&s);
-		if (!arg || *arg == '\0')
-			fatal("%.200s line %d: Missing second argument.",
-			    filename, linenum);
-		if (sscanf(arg, "%255[^:]:%5[0-9]", buf, sfwd_host_port) != 2 &&
-		    sscanf(arg, "%255[^/]/%5[0-9]", buf, sfwd_host_port) != 2)
+
+		/* construct a string for parse_forward */
+		snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2);
+
+		if (parse_forward(&fwd, fwdarg) == 0)
 			fatal("%.200s line %d: Bad forwarding specification.",
 			    filename, linenum);
-		if ((fwd_host_port = a2port(sfwd_host_port)) == 0)
-			fatal("%.200s line %d: Bad forwarding port.",
-			    filename, linenum);
+
 		if (*activep) {
 			if (opcode == oLocalForward)
-				add_local_forward(options, fwd_port, buf,
-				    fwd_host_port);
+				add_local_forward(options, &fwd);
 			else if (opcode == oRemoteForward)
-				add_remote_forward(options, fwd_port, buf,
-				    fwd_host_port);
+				add_remote_forward(options, &fwd);
 		}
 		break;
 
@@ -677,12 +680,25 @@
 		if (!arg || *arg == '\0')
 			fatal("%.200s line %d: Missing port argument.",
 			    filename, linenum);
-		fwd_port = a2port(arg);
-		if (fwd_port == 0)
+		memset(&fwd, '\0', sizeof(fwd));
+		fwd.connect_host = "socks";
+		fwd.listen_host = hpdelim(&arg);
+		if (fwd.listen_host == NULL ||
+		    strlen(fwd.listen_host) >= NI_MAXHOST)
+			fatal("%.200s line %d: Bad forwarding specification.",
+			    filename, linenum);
+		if (arg) {
+			fwd.listen_port = a2port(arg);
+			fwd.listen_host = cleanhostname(fwd.listen_host);
+		} else {
+			fwd.listen_port = a2port(fwd.listen_host);
+			fwd.listen_host = "";
+		}
+		if (fwd.listen_port == 0)
 			fatal("%.200s line %d: Badly formatted port number.",
 			    filename, linenum);
 		if (*activep)
-			add_local_forward(options, fwd_port, "socks", 0);
+			add_local_forward(options, &fwd);
 		break;
 
 	case oClearAllForwardings:
@@ -1045,3 +1061,68 @@
 	/* options->host_key_alias should not be set by default */
 	/* options->preferred_authentications will be set in ssh */
 }
+
+/*
+ * parse_forward
+ * parses a string containing a port forwarding specification of the form:
+ *	[listenhost:]listenport:connecthost:connectport
+ * returns number of arguments parsed or zero on error
+ */
+int
+parse_forward(Forward *fwd, const char *fwdspec)
+{
+	int i;
+	char *p, *cp, *fwdarg[4];
+
+	memset(fwd, '\0', sizeof(*fwd));
+
+	cp = p = xstrdup(fwdspec);
+
+	/* skip leading spaces */
+	while (*cp && isspace(*cp))
+		cp++;
+
+	for (i = 0; i < 4; ++i)
+		if ((fwdarg[i] = hpdelim(&cp)) == NULL)
+			break;
+
+	/* Check for trailing garbage in 4-arg case*/
+	if (cp != NULL)
+		i = 0;	/* failure */
+
+	switch (i) {
+	case 3:
+		fwd->listen_host = NULL;
+		fwd->listen_port = a2port(fwdarg[0]);
+		fwd->connect_host = xstrdup(cleanhostname(fwdarg[1]));
+		fwd->connect_port = a2port(fwdarg[2]);
+		break;
+
+	case 4:
+		fwd->listen_host = xstrdup(cleanhostname(fwdarg[0]));
+		fwd->listen_port = a2port(fwdarg[1]);
+		fwd->connect_host = xstrdup(cleanhostname(fwdarg[2]));
+		fwd->connect_port = a2port(fwdarg[3]);
+		break;
+	default:
+		i = 0; /* failure */
+	}
+
+	xfree(p);
+
+	if (fwd->listen_port == 0 && fwd->connect_port == 0)
+		goto fail_free;
+
+	if (fwd->connect_host != NULL &&
+	    strlen(fwd->connect_host) >= NI_MAXHOST)
+		goto fail_free;
+
+	return (i);
+
+ fail_free:
+	if (fwd->connect_host != NULL)
+		xfree(fwd->connect_host);
+	if (fwd->listen_host != NULL)
+		xfree(fwd->listen_host);
+	return (0);
+}
diff --git a/readconf.h b/readconf.h
index ded4225..03b772a 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: readconf.h,v 1.64 2004/07/11 17:48:47 deraadt Exp $	*/
+/*	$OpenBSD: readconf.h,v 1.65 2005/03/01 10:09:52 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -21,9 +21,10 @@
 /* Data structure for representing a forwarding request. */
 
 typedef struct {
-	u_short	  port;		/* Port to forward. */
-	char	 *host;		/* Host to connect. */
-	u_short	  host_port;	/* Port to connect on host. */
+	char	 *listen_host;		/* Host (address) to listen on. */
+	u_short	  listen_port;		/* Port to forward. */
+	char	 *connect_host;		/* Host to connect. */
+	u_short	  connect_port;		/* Port to connect on connect_host. */
 }       Forward;
 /* Data structure for representing option data. */
 
@@ -117,11 +118,12 @@
 void     initialize_options(Options *);
 void     fill_default_options(Options *);
 int	 read_config_file(const char *, const char *, Options *, int);
+int	 parse_forward(Forward *, const char *);
 
 int
 process_config_line(Options *, const char *, char *, const char *, int, int *);
 
-void	 add_local_forward(Options *, u_short, const char *, u_short);
-void	 add_remote_forward(Options *, u_short, const char *, u_short);
+void	 add_local_forward(Options *, const Forward *);
+void	 add_remote_forward(Options *, const Forward *);
 
 #endif				/* READCONF_H */
diff --git a/servconf.c b/servconf.c
index 541a9c8..2d1a0c3 100644
--- a/servconf.c
+++ b/servconf.c
@@ -10,7 +10,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: servconf.c,v 1.138 2004/12/23 23:11:00 djm Exp $");
+RCSID("$OpenBSD: servconf.c,v 1.139 2005/03/01 10:09:52 djm Exp $");
 
 #include "ssh.h"
 #include "log.h"
@@ -440,6 +440,7 @@
 	char *cp, **charptr, *arg, *p;
 	int *intptr, value, i, n;
 	ServerOpCodes opcode;
+	u_short port;
 
 	cp = line;
 	arg = strdelim(&cp);
@@ -512,39 +513,21 @@
 
 	case sListenAddress:
 		arg = strdelim(&cp);
-		if (!arg || *arg == '\0' || strncmp(arg, "[]", 2) == 0)
-			fatal("%s line %d: missing inet addr.",
+		if (arg == NULL || *arg == '\0')
+			fatal("%s line %d: missing address",
 			    filename, linenum);
-		if (*arg == '[') {
-			if ((p = strchr(arg, ']')) == NULL)
-				fatal("%s line %d: bad ipv6 inet addr usage.",
-				    filename, linenum);
-			arg++;
-			memmove(p, p+1, strlen(p+1)+1);
-		} else if (((p = strchr(arg, ':')) == NULL) ||
-			    (strchr(p+1, ':') != NULL)) {
-			add_listen_addr(options, arg, 0);
-			break;
-		}
-		if (*p == ':') {
-			u_short port;
+		p = hpdelim(&arg);
+		if (p == NULL)
+			fatal("%s line %d: bad address:port usage",
+			    filename, linenum);
+		p = cleanhostname(p);
+		if (arg == NULL)
+			port = 0;
+		else if ((port = a2port(arg)) == 0)
+			fatal("%s line %d: bad port number", filename, linenum);
 
-			p++;
-			if (*p == '\0')
-				fatal("%s line %d: bad inet addr:port usage.",
-				    filename, linenum);
-			else {
-				*(p-1) = '\0';
-				if ((port = a2port(p)) == 0)
-					fatal("%s line %d: bad port number.",
-					    filename, linenum);
-				add_listen_addr(options, arg, port);
-			}
-		} else if (*p == '\0')
-			add_listen_addr(options, arg, 0);
-		else
-			fatal("%s line %d: bad inet addr usage.",
-			    filename, linenum);
+		add_listen_addr(options, p, port);
+
 		break;
 
 	case sAddressFamily:
@@ -742,7 +725,23 @@
 
 	case sGatewayPorts:
 		intptr = &options->gateway_ports;
-		goto parse_flag;
+		arg = strdelim(&cp);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: missing yes/no/clientspecified "
+			    "argument.", filename, linenum);
+		value = 0;	/* silence compiler */
+		if (strcmp(arg, "clientspecified") == 0)
+			value = 2;
+		else if (strcmp(arg, "yes") == 0)
+			value = 1;
+		else if (strcmp(arg, "no") == 0)
+			value = 0;
+		else
+			fatal("%s line %d: Bad yes/no/clientspecified "
+			    "argument: %s", filename, linenum, arg);
+		if (*intptr == -1)
+			*intptr = value;
+		break;
 
 	case sUseDNS:
 		intptr = &options->use_dns;
diff --git a/ssh.1 b/ssh.1
index b8a91a1..27da08c 100644
--- a/ssh.1
+++ b/ssh.1
@@ -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.1,v 1.199 2004/11/07 17:42:36 jmc Exp $
+.\" $OpenBSD: ssh.1,v 1.200 2005/03/01 10:09:52 djm Exp $
 .Dd September 25, 1999
 .Dt SSH 1
 .Os
@@ -53,13 +53,13 @@
 .Op Fl i Ar identity_file
 .Oo Fl L Xo
 .Sm off
+.Oo Ar bind_address : Oc
 .Ar port :
 .Ar host :
 .Ar hostport
 .Sm on
 .Xc
 .Oc
-.Ek
 .Op Fl l Ar login_name
 .Op Fl m Ar mac_spec
 .Op Fl O Ar ctl_cmd
@@ -69,6 +69,7 @@
 .Ek
 .Oo Fl R Xo
 .Sm off
+.Oo Ar bind_address : Oc
 .Ar port :
 .Ar host :
 .Ar hostport
@@ -570,6 +571,7 @@
 Disables forwarding (delegation) of GSSAPI credentials to the server.
 .It Fl L Xo
 .Sm off
+.Oo Ar bind_address : Oc
 .Ar port : host : hostport
 .Sm on
 .Xc
@@ -577,7 +579,9 @@
 forwarded to the given host and port on the remote side.
 This works by allocating a socket to listen to
 .Ar port
-on the local side, and whenever a connection is made to this port, the
+on the local side, optionally bound to the specified
+.Ar bind_address .
+Whenever a connection is made to this port, the
 connection is forwarded over the secure channel, and a connection is
 made to
 .Ar host
@@ -585,14 +589,30 @@
 .Ar hostport
 from the remote machine.
 Port forwardings can also be specified in the configuration file.
-Only root can forward privileged ports.
 IPv6 addresses can be specified with an alternative syntax:
 .Sm off
 .Xo
+.Oo Ar bind_address / Oc
 .Ar port No / Ar host No /
-.Ar hostport .
+.Ar hostport
 .Xc
 .Sm on
+or by enclosing the address in square brackets.
+Only the superuser can forward privileged ports.
+By default, the local port is bound in accordance with the
+.Cm GatewayPorts
+setting.
+However, an explicit
+.Ar bind_address
+may be used to bind the connection to a specific address.
+The
+.Ar bind_address
+of
+.Dq localhost
+indicates that the listening port be bound for local use only, while an 
+empty address or 
+.Dq *
+indicates that the port should be available from all interfaces.
 .It Fl l Ar login_name
 Specifies the user to log in as on the remote machine.
 This also may be specified on a per-host basis in the configuration file.
@@ -724,6 +744,7 @@
 Causes all warning and diagnostic messages to be suppressed.
 .It Fl R Xo
 .Sm off
+.Oo Ar bind_address : Oc
 .Ar port : host : hostport
 .Sm on
 .Xc
@@ -738,16 +759,34 @@
 port
 .Ar hostport
 from the local machine.
+.Pp
 Port forwardings can also be specified in the configuration file.
 Privileged ports can be forwarded only when
 logging in as root on the remote machine.
-IPv6 addresses can be specified with an alternative syntax:
-.Sm off
+IPv6 addresses can be specified by enclosing the address in square braces or
+using an alternative syntax:
 .Xo
-.Ar port No / Ar host No /
-.Ar hostport .
-.Xc
+.Sm off
+.Oo Ar bind_address / Oc
+.Ar host/port/hostport
 .Sm on
+.Xc .
+.Pp
+By default, the listening socket on the server will be bound to the loopback
+interface only.
+This may be overriden by specifying a
+.Ar bind_address .
+An empty 
+.Ar bind_address , 
+or the address
+.Ql *
+indicates that the remote socket should listen on all interfaces.
+Specifying a remote
+.Ar bind_address
+will only succeed if the server's 
+.Cm GatewayPorts 
+option is enabled (see
+.Xr sshd_config 5 ).
 .It Fl S Ar ctl_path
 Specifies the location of a control socket for connection sharing.
 Refer to the description of
diff --git a/ssh.c b/ssh.c
index 1f50727..99b25af 100644
--- a/ssh.c
+++ b/ssh.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: ssh.c,v 1.231 2005/02/16 09:56:44 otto Exp $");
+RCSID("$OpenBSD: ssh.c,v 1.232 2005/03/01 10:09:52 djm Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/err.h>
@@ -158,9 +158,10 @@
 {
 	fprintf(stderr,
 "usage: ssh [-1246AaCfgkMNnqsTtVvXxY] [-b bind_address] [-c cipher_spec]\n"
-"           [-D port] [-e escape_char] [-F configfile] [-i identity_file]\n"
-"           [-L port:host:hostport] [-l login_name] [-m mac_spec] [-O ctl_cmd]\n"
-"           [-o option] [-p port] [-R port:host:hostport] [-S ctl_path]\n"
+"           [-D [listen-host:]port] [-e escape_char] [-F configfile]\n"
+"           [-i identity_file] [-L [listen-host:]port:host:hostport]\n"
+"           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n"
+"           [-R [listen-host:]port:host:hostport] [-S ctl_path]\n"
 "           [user@]hostname [command]\n"
 	);
 	exit(1);
@@ -178,14 +179,13 @@
 main(int ac, char **av)
 {
 	int i, opt, exit_status;
-	u_short fwd_port, fwd_host_port;
-	char sfwd_port[6], sfwd_host_port[6];
 	char *p, *cp, *line, buf[256];
 	struct stat st;
 	struct passwd *pw;
 	int dummy;
 	extern int optind, optreset;
 	extern char *optarg;
+	Forward fwd;
 
 	__progname = ssh_get_progname(av[0]);
 	init_rng();
@@ -401,39 +401,51 @@
 			break;
 
 		case 'L':
-		case 'R':
-			if (sscanf(optarg, "%5[0123456789]:%255[^:]:%5[0123456789]",
-			    sfwd_port, buf, sfwd_host_port) != 3 &&
-			    sscanf(optarg, "%5[0123456789]/%255[^/]/%5[0123456789]",
-			    sfwd_port, buf, sfwd_host_port) != 3) {
+			if (parse_forward(&fwd, optarg))
+				add_local_forward(&options, &fwd);
+			else {
 				fprintf(stderr,
-				    "Bad forwarding specification '%s'\n",
+				    "Bad local forwarding specification '%s'\n",
 				    optarg);
-				usage();
-				/* NOTREACHED */
-			}
-			if ((fwd_port = a2port(sfwd_port)) == 0 ||
-			    (fwd_host_port = a2port(sfwd_host_port)) == 0) {
-				fprintf(stderr,
-				    "Bad forwarding port(s) '%s'\n", optarg);
 				exit(1);
 			}
-			if (opt == 'L')
-				add_local_forward(&options, fwd_port, buf,
-				    fwd_host_port);
-			else if (opt == 'R')
-				add_remote_forward(&options, fwd_port, buf,
-				    fwd_host_port);
+			break;
+
+		case 'R':
+			if (parse_forward(&fwd, optarg)) {
+				add_remote_forward(&options, &fwd);
+			} else {
+				fprintf(stderr,
+				    "Bad remote forwarding specification "
+				    "'%s'\n", optarg);
+				exit(1);
+			}
 			break;
 
 		case 'D':
-			fwd_port = a2port(optarg);
-			if (fwd_port == 0) {
+			cp = p = xstrdup(optarg);
+			memset(&fwd, '\0', sizeof(fwd));
+			fwd.connect_host = "socks";
+			if ((fwd.listen_host = hpdelim(&cp)) == NULL) {
+				fprintf(stderr, "Bad dynamic forwarding "
+				    "specification '%.100s'\n", optarg);
+				exit(1);
+			}
+			if (cp != NULL) {
+				fwd.listen_port = a2port(cp);
+				fwd.listen_host = cleanhostname(fwd.listen_host);
+			} else {
+				fwd.listen_port = a2port(fwd.listen_host);
+				fwd.listen_host = "";
+			}
+
+			if (fwd.listen_port == 0) {
 				fprintf(stderr, "Bad dynamic port '%s'\n",
 				    optarg);
 				exit(1);
 			}
-			add_local_forward(&options, fwd_port, "socks", 0);
+			add_local_forward(&options, &fwd);
+			xfree(p);
 			break;
 
 		case 'C':
@@ -842,14 +854,19 @@
 
 	/* Initiate local TCP/IP port forwardings. */
 	for (i = 0; i < options.num_local_forwards; i++) {
-		debug("Connections to local port %d forwarded to remote address %.200s:%d",
-		    options.local_forwards[i].port,
-		    options.local_forwards[i].host,
-		    options.local_forwards[i].host_port);
+		debug("Local connections to %.200s:%d forwarded to remote "
+		    "address %.200s:%d",
+		    (options.local_forwards[i].listen_host == NULL) ? 
+		    (options.gateway_ports ? "*" : "LOCALHOST") : 
+		    options.local_forwards[i].listen_host,
+		    options.local_forwards[i].listen_port,
+		    options.local_forwards[i].connect_host,
+		    options.local_forwards[i].connect_port);
 		success += channel_setup_local_fwd_listener(
-		    options.local_forwards[i].port,
-		    options.local_forwards[i].host,
-		    options.local_forwards[i].host_port,
+		    options.local_forwards[i].listen_host,
+		    options.local_forwards[i].listen_port,
+		    options.local_forwards[i].connect_host,
+		    options.local_forwards[i].connect_port,
 		    options.gateway_ports);
 	}
 	if (i > 0 && success == 0)
@@ -857,14 +874,17 @@
 
 	/* Initiate remote TCP/IP port forwardings. */
 	for (i = 0; i < options.num_remote_forwards; i++) {
-		debug("Connections to remote port %d forwarded to local address %.200s:%d",
-		    options.remote_forwards[i].port,
-		    options.remote_forwards[i].host,
-		    options.remote_forwards[i].host_port);
+		debug("Remote connections from %.200s:%d forwarded to "
+		    "local address %.200s:%d",
+		    options.remote_forwards[i].listen_host,
+		    options.remote_forwards[i].listen_port,
+		    options.remote_forwards[i].connect_host,
+		    options.remote_forwards[i].connect_port);
 		channel_request_remote_forwarding(
-		    options.remote_forwards[i].port,
-		    options.remote_forwards[i].host,
-		    options.remote_forwards[i].host_port);
+		    options.remote_forwards[i].listen_host,
+		    options.remote_forwards[i].listen_port,
+		    options.remote_forwards[i].connect_host,
+		    options.remote_forwards[i].connect_port);
 	}
 }
 
@@ -1040,12 +1060,12 @@
 		return;
 	debug("remote forward %s for: listen %d, connect %s:%d",
 	    type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure",
-	    options.remote_forwards[i].port,
-	    options.remote_forwards[i].host,
-	    options.remote_forwards[i].host_port);
+	    options.remote_forwards[i].listen_port,
+	    options.remote_forwards[i].connect_host,
+	    options.remote_forwards[i].connect_port);
 	if (type == SSH2_MSG_REQUEST_FAILURE)
-		logit("Warning: remote port forwarding failed for listen port %d",
-		    options.remote_forwards[i].port);
+		logit("Warning: remote port forwarding failed for listen "
+		    "port %d", options.remote_forwards[i].listen_port);
 }
 
 static void
diff --git a/ssh_config.5 b/ssh_config.5
index 8f6d851..6b6cfc5 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.42 2005/02/28 00:54:10 djm Exp $
+.\" $OpenBSD: ssh_config.5,v 1.43 2005/03/01 10:09:52 djm Exp $
 .Dd September 25, 1999
 .Dt SSH_CONFIG 5
 .Os
@@ -480,12 +480,37 @@
 Specifies that a TCP/IP port on the local machine be forwarded over
 the secure channel to the specified host and port from the remote machine.
 The first argument must be a port number, and the second must be
-.Ar host:port .
-IPv6 addresses can be specified with an alternative syntax:
-.Ar host/port .
-Multiple forwardings may be specified, and additional
-forwardings can be given on the command line.
+.Xo
+.Sm off
+.Oo Ar bind_address : Oc
+.Ar host:port
+.Sm on
+.Xc .
+IPv6 addresses can be specified by enclosing addresses in square brackets or 
+by using an alternative syntax:
+.Xo
+.Sm off
+.Oo Ar bind_address / Oc
+.Ar host/port
+.Sm on
+.Xc .
+Multiple forwardings may be specified, and additional forwardings can be 
+given on the command line.
 Only the superuser can forward privileged ports.
+By default, the local port is bound in accordance with the
+.Cm GatewayPorts
+setting.
+However, an explicit
+.Ar bind_address
+may be used to bind the connection to a specific address.
+The
+.Ar bind_address
+of
+.Dq localhost
+indicates that the listening port be bound for local use only, while an 
+empty address or 
+.Dq *
+indicates that the port should be available from all interfaces.
 .It Cm LogLevel
 Gives the verbosity level that is used when logging messages from
 .Nm ssh .
@@ -592,12 +617,39 @@
 Specifies that a TCP/IP port on the remote machine be forwarded over
 the secure channel to the specified host and port from the local machine.
 The first argument must be a port number, and the second must be
-.Ar host:port .
-IPv6 addresses can be specified with an alternative syntax:
-.Ar host/port .
+.Xo
+.Sm off
+.Oo Ar bind_address : Oc
+.Ar host:port
+.Sm on
+.Xc .
+IPv6 addresses can be specified by enclosing any addresses in square brackets
+or by using the alternative syntax:
+.Xo
+.Sm off
+.Oo Ar bind_address / Oc
+.Ar host/port
+.Sm on
+.Xc .
 Multiple forwardings may be specified, and additional
 forwardings can be given on the command line.
 Only the superuser can forward privileged ports.
+.Pp
+If the
+.Ar bind_address
+is not specified, the default is to only bind to loopback addresses.
+If the
+.Ar bind_address
+is
+.Ql *
+or an empty string, then the forwarding is requested to listen on all
+interfaces.
+Specifying a remote
+.Ar bind_address
+will only succeed if the server's 
+.Cm GatewayPorts 
+option is enabled (see
+.Xr sshd_config 5 ).
 .It Cm RhostsRSAAuthentication
 Specifies whether to try rhosts based authentication with RSA host
 authentication.
diff --git a/sshd_config.5 b/sshd_config.5
index da6d97c..8d291e6 100644
--- a/sshd_config.5
+++ b/sshd_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: sshd_config.5,v 1.38 2005/01/08 00:41:19 jmc Exp $
+.\" $OpenBSD: sshd_config.5,v 1.39 2005/03/01 10:09:52 djm Exp $
 .Dd September 25, 1999
 .Dt SSHD_CONFIG 5
 .Os
@@ -256,12 +256,15 @@
 .Cm GatewayPorts
 can be used to specify that
 .Nm sshd
-should bind remote port forwardings to the wildcard address,
-thus allowing remote hosts to connect to forwarded ports.
-The argument must be
+should allow remote port forwardings to bind to non-loopback addresses, thus
+allowing other hosts to connect.
+The argument may be
+.Dq no
+to force remote port forwardings to be available to the local host only,
 .Dq yes
-or
-.Dq no .
+to force remote port forwardings to bind to the wildcard address, or
+.Dq clientspecified
+to allow the client to select the address to which the forwarding is bound.
 The default is
 .Dq no .
 .It Cm GSSAPIAuthentication