- markus@cvs.openbsd.org 2001/04/07 08:55:18
     [buffer.c channels.c channels.h readconf.c ssh.c]
     allow the ssh client act as a SOCKS4 proxy (dynamic local
     portforwarding).  work by Dan Kaminsky <dankamin@cisco.com> and me.
     thanks to Dan for this great patch: use 'ssh -D 1080 host' and make
     netscape use localhost:1080 as a socks proxy.
diff --git a/channels.c b/channels.c
index d5526fb..0e334db 100644
--- a/channels.c
+++ b/channels.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.102 2001/04/06 21:00:10 markus Exp $");
+RCSID("$OpenBSD: channels.c,v 1.103 2001/04/07 08:55:17 markus Exp $");
 
 #include <openssl/rsa.h>
 #include <openssl/dsa.h>
@@ -51,6 +51,7 @@
 #include "packet.h"
 #include "xmalloc.h"
 #include "buffer.h"
+#include "bufaux.h"
 #include "uidswap.h"
 #include "log.h"
 #include "misc.h"
@@ -133,6 +134,8 @@
 /* AF_UNSPEC or AF_INET or AF_INET6 */
 extern int IPv4or6;
 
+void	 port_open_helper(Channel *c, char *rtype);
+
 /* Sets specific protocol options. */
 
 void
@@ -539,6 +542,89 @@
 	}
 }
 
+#define SSH_SOCKS_HEAD 1+1+2+4
+
+void
+channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
+{
+	u_char *p, *host;
+	int len, i, done, have;
+	char username[256];
+	struct {
+		u_int8_t version;
+		u_int8_t command;
+		u_int16_t dest_port;
+		struct in_addr dest_ip;
+	} s4_req, s4_rsp;
+
+	have = buffer_len(&c->input);
+
+	debug("channel %d: pre_dynamic have: %d", c->self, have);
+	/*buffer_dump(&c->input);*/
+
+	/* Check if the fixed size part of the packet is in buffer. */
+	if (have < SSH_SOCKS_HEAD + 1) {
+		/* need more */
+		FD_SET(c->sock, readset);
+		return;
+	}
+	/* Check for end of username */
+	p = buffer_ptr(&c->input);
+	done = 0;
+	for (i = SSH_SOCKS_HEAD; i < have; i++) {
+		if (p[i] == '\0') {
+			done = 1;
+			break;
+		}
+	}
+	if (!done) {
+		/* need more */
+		FD_SET(c->sock, readset);
+		return;
+	}
+	buffer_get(&c->input, (char *)&s4_req.version, 1);
+	buffer_get(&c->input, (char *)&s4_req.command, 1);
+	buffer_get(&c->input, (char *)&s4_req.dest_port, 2);
+	buffer_get(&c->input, (char *)&s4_req.dest_ip, 4);
+	p = buffer_ptr(&c->input);
+	len = strlen(p);
+	have = buffer_len(&c->input);
+	debug2("channel %d: pre_dynamic user: %s/%d", c->self, p, len);
+	if (len > have)
+		fatal("channel %d: pre_dynamic: len %d > have %d",
+		    c->self, len, have);
+	strlcpy(username, p, sizeof(username));
+	buffer_consume(&c->input, len);
+	buffer_consume(&c->input, 1);		/* trailing '\0' */
+
+	host = inet_ntoa(s4_req.dest_ip);
+	strlcpy(c->path, host, sizeof(c->path));
+	c->host_port = ntohs(s4_req.dest_port);
+	
+	debug("channel %d: dynamic request received: "
+	    "socks%x://%s@%s:%u/command?%u",
+	    c->self, s4_req.version, username, host, c->host_port,
+	    s4_req.command);
+
+	if ((s4_req.version != 4) || (s4_req.command != 1)) {
+		debug("channel %d: cannot handle: socks VN %d CN %d",
+		    c->self, s4_req.version, s4_req.command);
+		channel_free(c->self);
+		return;
+	}
+
+	s4_rsp.version = 0;			/* VN: version of reply code */
+	s4_rsp.command = 90;			/* CD: request granted */
+	s4_rsp.dest_port = 0;			/* ignored */
+	s4_rsp.dest_ip.s_addr = INADDR_ANY;	/* ignored */
+	buffer_append(&c->output, (char *)&s4_rsp, sizeof(s4_rsp));
+
+	/* switch to next state */
+	c->type = SSH_CHANNEL_OPENING;
+	port_open_helper(c, "direct-tcpip");
+}
+
+
 /* This is our fake X11 server socket. */
 void
 channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
@@ -591,73 +677,100 @@
 	}
 }
 
+void
+port_open_helper(Channel *c, char *rtype)
+{
+	int direct;
+	char buf[1024];
+	char *remote_ipaddr = get_peer_ipaddr(c->sock);
+	u_short remote_port = get_peer_port(c->sock);
+
+	direct = (strcmp(rtype, "direct-tcpip") == 0);
+
+	snprintf(buf, sizeof buf,
+	    "%s: listening port %d for %.100s port %d, "
+	    "connect from %.200s port %d",
+	    rtype, c->listening_port, c->path, c->host_port,
+	    remote_ipaddr, remote_port);
+
+	xfree(c->remote_name);
+	c->remote_name = xstrdup(buf);
+
+	if (compat20) {
+		packet_start(SSH2_MSG_CHANNEL_OPEN);
+		packet_put_cstring(rtype);
+		packet_put_int(c->self);
+		packet_put_int(c->local_window_max);
+		packet_put_int(c->local_maxpacket);
+		if (direct) {
+			/* target host, port */
+			packet_put_cstring(c->path);
+			packet_put_int(c->host_port);
+		} else {
+			/* listen address, port */
+			packet_put_cstring(c->path);
+			packet_put_int(c->listening_port);
+		}
+		/* originator host and port */
+		packet_put_cstring(remote_ipaddr);
+		packet_put_int(remote_port);
+		packet_send();
+	} else {
+		packet_start(SSH_MSG_PORT_OPEN);
+		packet_put_int(c->self);
+		packet_put_cstring(c->path);
+		packet_put_int(c->host_port);
+		if (have_hostname_in_open)
+			packet_put_cstring(c->remote_name);
+		packet_send();
+	}
+	xfree(remote_ipaddr);
+}
+
 /*
  * This socket is listening for connections to a forwarded TCP/IP port.
  */
 void
 channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset)
 {
+	Channel *nc;
 	struct sockaddr addr;
-	int newsock, newch;
+	int newsock, newch, nextstate;
 	socklen_t addrlen;
-	char buf[1024], *remote_ipaddr, *rtype;
-	int remote_port;
-
-	rtype = (c->type == SSH_CHANNEL_RPORT_LISTENER) ?
-	    "forwarded-tcpip" : "direct-tcpip";
+	char *rtype;
 
 	if (FD_ISSET(c->sock, readset)) {
 		debug("Connection to port %d forwarding "
 		    "to %.100s port %d requested.",
 		    c->listening_port, c->path, c->host_port);
+
+		rtype = (c->type == SSH_CHANNEL_RPORT_LISTENER) ?
+		    "forwarded-tcpip" : "direct-tcpip";
+		nextstate = (c->host_port == 0) ? SSH_CHANNEL_DYNAMIC :
+		    SSH_CHANNEL_OPENING;
+
 		addrlen = sizeof(addr);
 		newsock = accept(c->sock, &addr, &addrlen);
 		if (newsock < 0) {
 			error("accept: %.100s", strerror(errno));
 			return;
 		}
-		remote_ipaddr = get_peer_ipaddr(newsock);
-		remote_port = get_peer_port(newsock);
-		snprintf(buf, sizeof buf,
-		    "listen port %d for %.100s port %d, "
-		    "connect from %.200s port %d",
-		    c->listening_port, c->path, c->host_port,
-		    remote_ipaddr, remote_port);
-
 		newch = channel_new(rtype,
-		    SSH_CHANNEL_OPENING, newsock, newsock, -1,
+		    nextstate, newsock, newsock, -1,
 		    c->local_window_max, c->local_maxpacket,
-		    0, xstrdup(buf), 1);
-		if (compat20) {
-			packet_start(SSH2_MSG_CHANNEL_OPEN);
-			packet_put_cstring(rtype);
-			packet_put_int(newch);
-			packet_put_int(c->local_window_max);
-			packet_put_int(c->local_maxpacket);
-			if (c->type == SSH_CHANNEL_RPORT_LISTENER) {
-				/* listen address, port */
-				packet_put_string(c->path, strlen(c->path));
-				packet_put_int(c->listening_port);
-			} else {
-				/* target host, port */
-				packet_put_string(c->path, strlen(c->path));
-				packet_put_int(c->host_port);
-			}
-			/* originator host and port */
-			packet_put_cstring(remote_ipaddr);
-			packet_put_int(remote_port);
-			packet_send();
-		} else {
-			packet_start(SSH_MSG_PORT_OPEN);
-			packet_put_int(newch);
-			packet_put_string(c->path, strlen(c->path));
-			packet_put_int(c->host_port);
-			if (have_hostname_in_open) {
-				packet_put_string(buf, strlen(buf));
-			}
-			packet_send();
+		    0, xstrdup(rtype), 1);
+
+		nc = channel_lookup(newch);
+		if (nc == NULL) {
+			error("xxx: no new channel:");
+			return;
 		}
-		xfree(remote_ipaddr);
+		nc->listening_port = c->listening_port;
+		nc->host_port = c->host_port;
+		strlcpy(nc->path, c->path, sizeof(nc->path));
+
+		if (nextstate != SSH_CHANNEL_DYNAMIC)
+			port_open_helper(nc, rtype);
 	}
 }
 
@@ -733,6 +846,15 @@
 		if (len <= 0) {
 			debug("channel %d: read<=0 rfd %d len %d",
 			    c->self, c->rfd, len);
+			if (c->type == SSH_CHANNEL_DYNAMIC) {
+				/*
+				 * we are not yet connected to a remote peer,
+				 * so the connection-close protocol won't work
+				 */
+				debug("channel %d: dynamic: closed", c->self);
+				channel_free(c->self);
+				return -1;
+			}
 			if (compat13) {
 				buffer_consume(&c->output, buffer_len(&c->output));
 				c->type = SSH_CHANNEL_INPUT_DRAINING;
@@ -894,6 +1016,12 @@
 }
 
 void
+channel_post_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
+{
+	channel_handle_rfd(c, readset, writeset);
+}
+
+void
 channel_handler_init_20(void)
 {
 	channel_pre[SSH_CHANNEL_OPEN] =			&channel_pre_open_20;
@@ -903,6 +1031,7 @@
 	channel_pre[SSH_CHANNEL_X11_LISTENER] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
+	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
 
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open_2;
 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
@@ -910,6 +1039,7 @@
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
 	channel_post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
+	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_dynamic;
 }
 
 void
@@ -923,6 +1053,7 @@
 	channel_pre[SSH_CHANNEL_INPUT_DRAINING] =	&channel_pre_input_draining;
 	channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] =	&channel_pre_output_draining;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
+	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
 
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open_1;
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
@@ -930,6 +1061,7 @@
 	channel_post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
 	channel_post[SSH_CHANNEL_OUTPUT_DRAINING] =	&channel_post_output_drain_13;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
+	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_dynamic;
 }
 
 void
@@ -941,12 +1073,14 @@
 	channel_pre[SSH_CHANNEL_PORT_LISTENER] =	&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
+	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
 
 	channel_post[SSH_CHANNEL_X11_LISTENER] =	&channel_post_x11_listener;
 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
 	channel_post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open_1;
 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
+	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_dynamic;
 }
 
 void
@@ -1500,6 +1634,7 @@
 		case SSH_CHANNEL_RPORT_LISTENER:
 		case SSH_CHANNEL_CLOSED:
 		case SSH_CHANNEL_AUTH_SOCKET:
+		case SSH_CHANNEL_DYNAMIC:
 		case SSH_CHANNEL_CONNECTING: 	/* XXX ??? */
 			continue;
 		case SSH_CHANNEL_LARVAL:
@@ -1551,6 +1686,7 @@
 		case SSH_CHANNEL_LARVAL:
 		case SSH_CHANNEL_OPENING:
 		case SSH_CHANNEL_CONNECTING:
+		case SSH_CHANNEL_DYNAMIC:
 		case SSH_CHANNEL_OPEN:
 		case SSH_CHANNEL_X11_OPEN:
 		case SSH_CHANNEL_INPUT_DRAINING: