[PATCH 4/5] PROXY/SOCKS5: added methods for SOCKS5 proxies

Enrico Scholz <[email protected]> Wed, 27 Feb 2008 11:38:22 +0100
Newsgroups gmane.network.irc.irssi.devel
Message-ID <6c8932835787ef76b3cff26af70432b6f0b66125.1204046798.git.enrico.scholz@informatik.tu-chemnitz.de>
This patch adds code for connecting through SOCKS5 proxies. It was
primarily written for use with TOR, so there are some open issues:

* it only allows to make proxy requests with full hostnames; ipv4/ipv6 is
  not supported

* GSSAPI authentication (which is mentioned as mandatory in RFC 1928) is
  not implemented

* plaintext authentication is untested

To use it
* set 'proxy_type' to 'socks5'

Signed-off-by: Enrico Scholz <[email protected]>
---
 src/core/network-proxy-socks5.c |  338 +++++++++++++++++++++++++++++++++++++++
 src/core/network-proxy-socks5.h |   31 ++++
 2 files changed, 369 insertions(+), 0 deletions(-)
 create mode 100644 src/core/network-proxy-socks5.c
 create mode 100644 src/core/network-proxy-socks5.h

diff --git a/src/core/network-proxy-socks5.c b/src/core/network-proxy-socks5.c
new file mode 100644
index 0000000..64a8f51
--- /dev/null
+++ b/src/core/network-proxy-socks5.c
@@ -0,0 +1,338 @@
+/*	--*- c -*--
+ * Copyright (C) 2008 Enrico Scholz <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 and/or 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "module.h"
+#include "network-proxy-socks5.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "network.h"
+#include "network-proxy-priv.h"
+
+/* RFC 1928 */
+struct client_greeting
+{
+	uint8_t		ver;
+	uint8_t		nmethods;
+	uint8_t		methods[];
+} __attribute__((__packed__));
+
+struct server_greeting
+{
+	uint8_t		ver;
+	uint8_t		method;
+} __attribute__((__packed__));
+
+struct server_response_plain
+{
+	uint8_t		ver;
+	uint8_t		status;
+} __attribute__((__packed__));
+
+struct client_request
+{
+	uint8_t		ver;
+	uint8_t		cmd;
+	uint8_t		rsv;
+	uint8_t		atyp;
+	uint8_t		dst[];
+} __attribute__((__packed__));
+
+struct server_response
+{
+	uint8_t		ver;
+	uint8_t		rep;
+	uint8_t		res;
+	uint8_t		atyp;
+	uint8_t		bnd[];
+} __attribute__((__packed__));
+
+static void
+network_proxy_socks5_destroy(struct network_proxy *proxy)
+{
+	struct _network_proxy_socks5	*self = container_of(proxy, struct _network_proxy_socks5, proxy);
+
+	g_free((void *)self->password);
+	g_free((void *)self->username);
+	_network_proxy_destroy(proxy);
+	g_free(self);
+}
+
+static struct network_proxy *
+network_proxy_socks5_clone(struct network_proxy const *proxy)
+{
+	struct _network_proxy_socks5	*self = container_of(proxy, struct _network_proxy_socks5, proxy);
+	struct _network_proxy_socks5	*res;
+
+	res = g_malloc0(sizeof *res);
+
+	_network_proxy_clone(&res->proxy, &self->proxy);
+	res->username = g_strdup(self->username);
+	res->password = g_strdup(self->password);
+	return &res->proxy;
+}
+
+static bool
+socks5_connect_unauthorized(GIOChannel *ch)
+{
+	/* nothing to do here */
+	(void)ch;
+	return true;
+}
+
+/* TODO: test this method! */
+static bool
+socks5_connect_plain(struct _network_proxy_socks5 const *proxy, GIOChannel *ch)
+{
+	uint8_t				ver  = 0x01;
+	uint8_t				ulen = strlen(proxy->username);
+	uint8_t				plen = proxy->password ? strlen(proxy->password) : 0;
+	struct server_response_plain	resp;
+
+	if (ulen==0 ||
+	    !_network_proxy_send_all(ch, &ver, sizeof ver) ||
+	    !_network_proxy_send_all(ch, &ulen, sizeof ulen) ||
+	    !_network_proxy_send_all(ch, proxy->username, ulen) ||
+	    !_network_proxy_send_all(ch, &plen, sizeof plen) ||
+	    (plen>0 && !_network_proxy_send_all(ch, proxy->password, plen)) ||
+	    !_network_proxy_flush(ch) ||
+	    !_network_proxy_recv_all(ch, &resp, sizeof resp))
+		return false;
+
+	if (resp.ver!=0x01) {
+		g_warning("unexpected plaintext response version %#04x", resp.ver);
+		return false;
+	}
+
+	if (resp.status!=0x00) {
+		g_warning("socks5 authentication error (%#04x)", resp.status);
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+socks5_connect(struct _network_proxy_socks5 const *proxy, GIOChannel *ch,
+	       char const *address, uint16_t port)
+{
+	bool				rc;
+
+	struct server_greeting		s_greeting;
+	struct server_response		s_response;
+
+
+	/* Phase 1: exchange greeting */
+	{
+		struct client_greeting		c_greeting = {
+			.ver      = 0x05,
+			.nmethods = proxy->username && proxy->username[0] ? 2 : 1
+		};
+		/* HACK: order is important because it depends upon
+		 * c_greeting.nmethods  */
+		char const			methods[] = {
+			0x00,			/* no authentication */
+			0x02			/* username/password */
+		};
+		if (!_network_proxy_send_all(ch, &c_greeting, sizeof c_greeting) ||
+		    !_network_proxy_send_all(ch, methods,     c_greeting.nmethods) ||
+		    !_network_proxy_flush(ch) ||
+		    !_network_proxy_recv_all(ch, &s_greeting, sizeof s_greeting))
+			goto err;
+
+		if (s_greeting.ver!=5) {
+			g_warning("version mismatch during initial socks5 greeting; got version %#04x",
+				  s_greeting.ver);
+			goto err;
+		}
+	}
+
+	/* Phase 2: authentication */
+	{
+		switch (s_greeting.method) {
+		case 0x00: rc = socks5_connect_unauthorized(ch); break;
+		case 0x02: rc = socks5_connect_plain(proxy, ch); break;
+		default:
+			g_warning("unsupported authentication method %#04x", s_greeting.method);
+			rc = false;
+		}
+
+		if (!rc)
+			goto err;
+	}
+
+	/* Phase 3: connection request */
+	{
+		struct client_request		c_request = {
+			.ver	  = 0x05,
+			.cmd	  = 0x01,	/* CONNECT */
+			.atyp     = 0x03,	/* domain name */
+		};
+		uint8_t				address_len = strlen(address);
+		uint16_t			dst_port = htons(port);
+		uint16_t			bnd_port;
+		char				bnd_address[257];
+
+		if (!_network_proxy_send_all(ch, &c_request,   sizeof c_request) ||
+		    !_network_proxy_send_all(ch, &address_len, sizeof address_len) ||
+		    !_network_proxy_send_all(ch, address,      address_len) ||
+		    !_network_proxy_send_all(ch, &dst_port,    sizeof dst_port) ||
+		    !_network_proxy_flush(ch) ||
+		    !_network_proxy_recv_all(ch, &s_response,  sizeof s_response))
+			goto err;
+
+		if (s_response.ver != 0x05) {
+			g_warning("version mismatch in socks5 response; got version %#04x",
+				  s_response.ver);
+			goto err;
+		}
+
+		rc = false;
+		switch (s_response.rep) {
+		case 0x00: rc = true; break;	/* succeeded */
+		case 0x01: g_warning("SOCKS5: general SOCKS server failure"); break;
+		case 0x02: g_warning("SOCKS5: connection not allowed by ruleset"); break;
+		case 0x03: g_warning("SOCKS5: Network unreachable"); break;
+		case 0x04: g_warning("SOCKS5: Host unreachable"); break;
+		case 0x05: g_warning("SOCKS5: Connection refused"); break;
+		case 0x06: g_warning("SOCKS5: TTL expired"); break;
+		case 0x07: g_warning("SOCKS5: Command not supported"); break;
+		case 0x08: g_warning("SOCKS5: Address type not supported"); break;
+		default:   g_warning("SOCKS5: unknown error %#04x", s_response.rep); break;
+		}
+
+		if (!rc)
+			goto err;
+
+		switch(s_response.atyp) {
+		case 0x01: {
+			struct in_addr	ip;
+			if (!_network_proxy_recv_all(ch, &ip,     sizeof ip) ||
+			    !inet_ntop(AF_INET, &ip, bnd_address, sizeof bnd_address))
+				rc = false;
+			break;
+		}
+
+		case 0x04: {
+			struct in6_addr	ip;
+			if (!_network_proxy_recv_all(ch, &ip,      sizeof ip) ||
+			    !inet_ntop(AF_INET6, &ip, bnd_address, sizeof bnd_address))
+				rc = false;
+			break;
+		}
+
+		case 0x03: {
+			uint8_t		tmp;
+			if (!_network_proxy_recv_all(ch, &tmp, sizeof tmp) ||
+			    tmp==0 ||
+			    !_network_proxy_recv_all(ch, &bnd_address, tmp))
+				rc = false;
+			else
+				bnd_address[tmp] = '\0';
+		}
+
+		default:
+			g_warning("SOCKS5: unsupported address family in response: %#04x",
+				  s_response.atyp);
+			rc = false;
+		}
+
+		if (!rc ||
+		    !_network_proxy_recv_all(ch, &bnd_port, sizeof bnd_port))
+			goto err;
+
+		bnd_port = ntohs(bnd_port);
+		g_debug("SOCKS5: bound to %s:%u", bnd_address, bnd_port);
+	}
+
+	return true;
+
+err:
+	g_warning("connecting through socks5 proxy failed");
+	return  false;
+}
+
+
+static GIOChannel *
+network_proxy_socks5_connect(struct network_proxy const *proxy, IPADDR const *hint_ip,
+			     char const *address, int port)
+{
+	struct _network_proxy_socks5	*self = container_of(proxy, struct _network_proxy_socks5, proxy);
+	GIOChannel			*ch;
+
+	GIOFlags			old_flags;
+	gchar const			*old_enc;
+	gboolean			old_buf;
+	GError				*err = NULL;
+
+	if (hint_ip)
+		ch = net_connect_ip(hint_ip, self->proxy.port, NULL);
+	else
+		ch = net_connect(self->proxy.host, self->proxy.port, NULL);
+
+	if (!ch)
+		return NULL;
+
+	old_enc   = g_io_channel_get_encoding(ch);
+	old_flags = g_io_channel_get_flags(ch);
+	old_buf   = g_io_channel_get_buffered(ch);
+
+	if (g_io_channel_set_encoding(ch, NULL, &err)!=G_IO_STATUS_NORMAL ||
+	    g_io_channel_set_flags(ch, old_flags & ~G_IO_FLAG_NONBLOCK, &err)!=G_IO_STATUS_NORMAL)
+		goto err;
+
+	g_io_channel_set_buffered(ch, false);
+
+	if (!socks5_connect(self, ch, address, port))
+		goto err;
+
+	g_io_channel_set_buffered(ch, old_buf);
+
+	if (g_io_channel_set_flags(ch, old_flags, &err) !=G_IO_STATUS_NORMAL ||
+	    g_io_channel_set_encoding(ch, old_enc, &err)!=G_IO_STATUS_NORMAL)
+		goto err;
+
+	return ch;
+
+err:
+	if (err) {
+		g_warning("something went wrong while preparing SOCKS5 proxy request: %s",
+			  err->message);
+		g_error_free(err);
+	}
+
+	net_disconnect(ch);
+	return NULL;
+}
+
+struct network_proxy *
+_network_proxy_socks5_create(void)
+{
+	struct _network_proxy_socks5	*res;
+
+	res = g_malloc0(sizeof *res);
+
+	_network_proxy_create(&res->proxy);
+	res->username    = g_strdup(settings_get_str("proxy_username"));
+	res->password    = g_strdup(settings_get_str("proxy_password"));
+
+	res->proxy.destroy = network_proxy_socks5_destroy;
+	res->proxy.connect = network_proxy_socks5_connect;
+	res->proxy.clone   = network_proxy_socks5_clone;
+
+	return &res->proxy;
+}
diff --git a/src/core/network-proxy-socks5.h b/src/core/network-proxy-socks5.h
new file mode 100644
index 0000000..963bad3
--- /dev/null
+++ b/src/core/network-proxy-socks5.h
@@ -0,0 +1,31 @@
+/*	--*- c -*--
+ * Copyright (C) 2008 Enrico Scholz <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 and/or 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef H_IRSSI_SRC_CORE_PROXY_SOCKS5_H
+#define H_IRSSI_SRC_CORE_PROXY_SOCKS5_H
+
+#include "network-proxy.h"
+
+struct _network_proxy_socks5 {
+	struct network_proxy	proxy;
+
+	char const		*username;
+	char const		*password;
+};
+
+struct network_proxy *		_network_proxy_socks5_create(void);
+
+#endif	/* H_IRSSI_SRC_CORE_PROXY_SOCKS5_H */
-- 
1.5.4.1