- djm@cvs.openbsd.org 2011/09/09 22:46:44
     [channels.c channels.h clientloop.h mux.c ssh.c]
     support for cancelling local and remote port forwards via the multiplex
     socket. Use ssh -O cancel -L xx:xx:xx -R yy:yy:yy user@host" to request
     the cancellation of the specified forwardings; ok markus@
diff --git a/channels.c b/channels.c
index 24d4a9f..0f7e1a8 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.311 2011/06/22 22:08:42 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.312 2011/09/09 22:46:44 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -302,6 +302,8 @@
 	buffer_init(&c->output);
 	buffer_init(&c->extended);
 	c->path = NULL;
+	c->listening_addr = NULL;
+	c->listening_port = 0;
 	c->ostate = CHAN_OUTPUT_OPEN;
 	c->istate = CHAN_INPUT_OPEN;
 	c->flags = 0;
@@ -411,6 +413,10 @@
 		xfree(c->path);
 		c->path = NULL;
 	}
+	if (c->listening_addr) {
+		xfree(c->listening_addr);
+		c->listening_addr = NULL;
+	}
 	while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) {
 		if (cc->abandon_cb != NULL)
 			cc->abandon_cb(c, cc->ctx);
@@ -2634,6 +2640,46 @@
 	IPv4or6 = af;
 }
 
+
+/*
+ * Determine whether or not a port forward listens to loopback, the
+ * 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
+ */
+static const char *
+channel_fwd_bind_addr(const char *listen_addr, int *wildcardp,
+    int is_client, int gateway_ports)
+{
+	const char *addr = NULL;
+	int wildcard = 0;
+
+	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 && is_client == 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;
+	}
+	if (wildcardp != NULL)
+		*wildcardp = wildcard;
+	return addr;
+}
+
 static int
 channel_setup_fwd_listener(int type, const char *listen_addr,
     u_short listen_port, int *allocated_listen_port,
@@ -2659,36 +2705,9 @@
 		return 0;
 	}
 
-	/*
-	 * 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 && is_client == 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;
-	}
-
+	/* Determine the bind address, cf. channel_fwd_bind_addr() comment */
+	addr = channel_fwd_bind_addr(listen_addr, &wildcard,
+	    is_client, gateway_ports);
 	debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s",
 	    type, wildcard, (addr == NULL) ? "NULL" : addr);
 
@@ -2793,6 +2812,7 @@
 		c->path = xstrdup(host);
 		c->host_port = port_to_connect;
 		c->listening_port = listen_port;
+		c->listening_addr = addr == NULL ? NULL : xstrdup(addr);
 		success = 1;
 	}
 	if (success == 0)
@@ -2810,9 +2830,36 @@
 
 	for (i = 0; i < channels_alloc; i++) {
 		Channel *c = channels[i];
+		if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER)
+			continue;
+		if (strcmp(c->path, host) == 0 && c->listening_port == port) {
+			debug2("%s: close channel %d", __func__, i);
+			channel_free(c);
+			found = 1;
+		}
+	}
 
-		if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER &&
-		    strcmp(c->path, host) == 0 && c->listening_port == port) {
+	return (found);
+}
+
+int
+channel_cancel_lport_listener(const char *lhost, u_short lport,
+    u_short cport, int gateway_ports)
+{
+	u_int i;
+	int found = 0;
+	const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, gateway_ports);
+
+	for (i = 0; i < channels_alloc; i++) {
+		Channel *c = channels[i];
+		if (c == NULL || c->type != SSH_CHANNEL_PORT_LISTENER)
+			continue;
+		if (c->listening_port != lport || c->host_port != cport)
+			continue;
+		if ((c->listening_addr == NULL && addr != NULL) ||
+		    (c->listening_addr != NULL && addr == NULL))
+			continue;
+		if (addr == NULL || strcmp(c->listening_addr, addr) == 0) {
 			debug2("%s: close channel %d", __func__, i);
 			channel_free(c);
 			found = 1;
@@ -2843,10 +2890,30 @@
 }
 
 /*
+ * Translate the requested rfwd listen host to something usable for
+ * this server.
+ */
+static const char *
+channel_rfwd_bind_host(const char *listen_host)
+{
+	if (listen_host == NULL) {
+		if (datafellows & SSH_BUG_RFWD_ADDR)
+			return "127.0.0.1";
+		else
+			return "localhost";
+	} else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0) {
+		if (datafellows & SSH_BUG_RFWD_ADDR)
+			return "0.0.0.0";
+		else
+			return "";
+	} else
+		return listen_host;
+}
+
+/*
  * Initiate forwarding of connections to port "port" on remote host through
  * the secure channel to host:port from local side.
  */
-
 int
 channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
     const char *host_to_connect, u_short port_to_connect)
@@ -2855,25 +2922,10 @@
 
 	/* Send the forward request to the remote side. */
 	if (compat20) {
-		const char *address_to_bind;
-		if (listen_host == NULL) {
-			if (datafellows & SSH_BUG_RFWD_ADDR)
-				address_to_bind = "127.0.0.1";
-			else
-				address_to_bind = "localhost";
-		} else if (*listen_host == '\0' ||
-			   strcmp(listen_host, "*") == 0) {
-			if (datafellows & SSH_BUG_RFWD_ADDR)
-				address_to_bind = "0.0.0.0";
-			else
-				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 */
-		packet_put_cstring(address_to_bind);
+		packet_put_char(1);		/* boolean: want reply */
+		packet_put_cstring(channel_rfwd_bind_host(listen_host));
 		packet_put_int(listen_port);
 		packet_send();
 		packet_write_wait();
@@ -2917,13 +2969,13 @@
  * Request cancellation of remote forwarding of connection host:port from
  * local side.
  */
-void
+int
 channel_request_rforward_cancel(const char *host, u_short port)
 {
 	int i;
 
 	if (!compat20)
-		return;
+		return -1;
 
 	for (i = 0; i < num_permitted_opens; i++) {
 		if (permitted_opens[i].host_to_connect != NULL &&
@@ -2932,12 +2984,12 @@
 	}
 	if (i >= num_permitted_opens) {
 		debug("%s: requested forward not found", __func__);
-		return;
+		return -1;
 	}
 	packet_start(SSH2_MSG_GLOBAL_REQUEST);
 	packet_put_cstring("cancel-tcpip-forward");
 	packet_put_char(0);
-	packet_put_cstring(host == NULL ? "" : host);
+	packet_put_cstring(channel_rfwd_bind_host(host));
 	packet_put_int(port);
 	packet_send();
 
@@ -2945,6 +2997,8 @@
 	permitted_opens[i].port_to_connect = 0;
 	xfree(permitted_opens[i].host_to_connect);
 	permitted_opens[i].host_to_connect = NULL;
+
+	return 0;
 }
 
 /*