- markus@cvs.openbsd.org 2001/04/11 13:56:13
     [channels.c ssh.c]
     https-connect and socks5 support. i feel so bad.
diff --git a/channels.c b/channels.c
index 4283abc..a1aa937 100644
--- a/channels.c
+++ b/channels.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.105 2001/04/10 12:15:23 markus Exp $");
+RCSID("$OpenBSD: channels.c,v 1.106 2001/04/11 13:56:13 markus Exp $");
 
 #include <openssl/rsa.h>
 #include <openssl/dsa.h>
@@ -576,6 +576,39 @@
 	return 0;	/* need more */
 }
 
+/* try to decode a http connect header */
+int
+channel_decode_https(Channel *c, fd_set * readset, fd_set * writeset)
+{
+	u_char *p, *host, *buf;
+	int port, ret;
+	char httpok[] = "HTTP/1.0 200\r\n\r\n";
+
+	debug2("channel %d: decode https connect", c->self);
+	ret = channel_decode_helper(c, strlen("connect "), '\r');
+	if (ret <= 0)
+		return ret;
+	p = buffer_ptr(&c->input);
+	buf = xmalloc(ret+1);
+	host = xmalloc(ret);
+	memcpy(buf, p, ret);
+	buf[ret] = '\0';
+	if (sscanf(buf, "CONNECT %[^:]:%u HTTP/", host, &port) != 2) {
+		debug("channel %d: cannot parse http header", c->self);
+		return -1;
+	}
+	debug("channel %d: dynamic request: https host %s port %u",
+	    c->self, host, port);
+	strlcpy(c->path, host, sizeof(c->path));
+	c->host_port = port;
+	xfree(host);
+	xfree(buf);
+	buffer_consume(&c->input, ret+4);
+	buffer_append(&c->output, httpok, strlen(httpok));
+
+	return 1;
+}
+
 /* try to decode a socks4 header */
 int
 channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
@@ -601,7 +634,7 @@
 	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);
+	debug2("channel %d: decode socks4: user %s/%d", c->self, p, len);
 	if (len > have)
 		fatal("channel %d: decode socks4: len %d > have %d",
 		    c->self, len, have);
@@ -613,10 +646,8 @@
 	strlcpy(c->path, host, sizeof(c->path));
 	c->host_port = ntohs(s4_req.dest_port);
 	
-	debug("channel %d: dynamic request: "
-	    "socks%x://%s@%s:%u/command?%u",
-	    c->self, s4_req.version, username, host, c->host_port,
-	    s4_req.command);
+	debug("channel %d: dynamic request: socks4 host %s port %u command %u",
+	    c->self, host, c->host_port, s4_req.command);
 
 	if (s4_req.command != 1) {
 		debug("channel %d: cannot handle: socks4 cn %d",
@@ -631,6 +662,115 @@
 	return 1;
 }
 
+/* try to decode a socks5 header */
+#define SSH_SOCKS5_AUTHDONE	0x1000
+#define SSH_SOCKS5_NOAUTH	0x00
+#define SSH_SOCKS5_IPV4		0x01
+#define SSH_SOCKS5_DOMAIN	0x03
+#define SSH_SOCKS5_IPV6		0x04
+#define SSH_SOCKS5_CONNECT	0x01
+#define SSH_SOCKS5_SUCCESS	0x00
+
+int
+channel_decode_socks5(Channel *c, fd_set * readset, fd_set * writeset)
+{
+	struct {
+		u_int8_t version;
+		u_int8_t command;
+		u_int8_t reserved;
+		u_int8_t atyp;
+	} s5_req, s5_rsp;
+	u_int16_t dest_port;
+	u_char *p, dest_addr[255+1];
+	int i, have, found, nmethods, addrlen, af;
+
+	debug2("channel %d: decode socks5", c->self);
+	p = buffer_ptr(&c->input);
+	if (p[0] != 0x05)
+		return -1;
+	have = buffer_len(&c->input);
+	if (!(c->flags & SSH_SOCKS5_AUTHDONE)) {
+		/* format: ver | nmethods | methods */
+		if (have < 2) 
+			return 0;
+		nmethods = p[1];
+		if (have < nmethods + 2)
+			return 0;
+		/* look for method: "NO AUTHENTICATION REQUIRED" */
+		for (found = 0, i = 2 ; i < nmethods + 2; i++) {
+			if (p[i] == SSH_SOCKS5_NOAUTH ) {
+				found = 1;
+				break;
+			}
+		}
+		if (!found) {
+			debug("channel %d: method SSH_SOCKS5_NOAUTH not found",
+			    c->self);
+			return -1;
+		}
+		buffer_consume(&c->input, nmethods + 2);
+		buffer_put_char(&c->output, 0x05);		/* version */
+		buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH);	/* method */
+		FD_SET(c->sock, writeset);
+		c->flags |= SSH_SOCKS5_AUTHDONE;
+		debug2("channel %d: socks5 auth done", c->self);
+		return 0;				/* need more */
+	}
+	debug2("channel %d: socks5 post auth", c->self);
+	if (have < sizeof(s5_req)+1)
+		return 0;			/* need more */
+	memcpy((char *)&s5_req, p, sizeof(s5_req));
+	if (s5_req.version != 0x05 ||
+	    s5_req.command != SSH_SOCKS5_CONNECT ||
+	    s5_req.reserved != 0x00) {
+		debug("channel %d: only socks5 connect supported", c->self);
+		return -1;
+	}
+	switch(s5_req.atyp){
+	case SSH_SOCKS5_IPV4:
+		addrlen = 4;
+		af = AF_INET;
+		break;
+	case SSH_SOCKS5_DOMAIN:
+		addrlen = p[sizeof(s5_req)];
+		af = -1;
+		break;
+	case SSH_SOCKS5_IPV6:
+		addrlen = 16;
+		af = AF_INET6;
+		break;
+	default:
+		debug("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp);
+		return -1;
+	}
+	if (have < 4 + addrlen + 2)
+		return 0;
+	buffer_consume(&c->input, sizeof(s5_req));
+	buffer_get(&c->input, (char *)&dest_addr, addrlen);
+	buffer_get(&c->input, (char *)&dest_port, 2);
+	dest_addr[addrlen] = '\0';
+	if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
+		strlcpy(c->path, dest_addr, sizeof(c->path));
+	else if (inet_ntop(af, dest_addr, c->path, sizeof(c->path)) == NULL)
+		return -1;
+	c->host_port = ntohs(dest_port);
+	
+	debug("channel %d: dynamic request: socks5 host %s port %u command %u",
+	    c->self, c->path, c->host_port, s5_req.command);
+
+	s5_rsp.version = 0x05;
+	s5_rsp.command = SSH_SOCKS5_SUCCESS;
+	s5_rsp.reserved = 0;			/* ignored */
+	s5_rsp.atyp = SSH_SOCKS5_IPV4;
+	((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY;
+	dest_port = 0;				/* ignored */
+
+	buffer_append(&c->output, (char *)&s5_rsp, sizeof(s5_rsp));
+	buffer_append(&c->output, (char *)&dest_addr, sizeof(struct in_addr));
+	buffer_append(&c->output, (char *)&dest_port, sizeof(dest_port));
+	return 1;
+}
+
 /* dynamic port forwarding */
 void
 channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
@@ -651,17 +791,15 @@
 	/* try to guess the protocol */
 	p = buffer_ptr(&c->input);
 	switch (p[0]) {
-	case 0x04:
-		ret = channel_decode_socks4(c, readset, writeset);
-		break;
-#if 0
 	case 'C':
 		ret = channel_decode_https(c, readset, writeset);
 		break;
+	case 0x04:
+		ret = channel_decode_socks4(c, readset, writeset);
+		break;
 	case 0x05:
 		ret = channel_decode_socks5(c, readset, writeset);
 		break;
-#endif
 	default:
 		ret = -1;
 		break;