From 7ef54cc29f23cc7723f62b36735093e4e3f2a2a7 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 14 Apr 2016 11:21:25 +0100 Subject: [PATCH] PROXY/SOCKS5: added methods for SOCKS5 proxies From original patch by Enrico Scholz 26 Feb 2008 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' --- src/core/network-proxy-socks5.c | 338 ++++++++++++++++++++++++++++++++ src/core/network-proxy-socks5.h | 31 +++ 2 files changed, 369 insertions(+) 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 00000000..64a8f510 --- /dev/null +++ b/src/core/network-proxy-socks5.c @@ -0,0 +1,338 @@ +/* --*- c -*-- + * Copyright (C) 2008 Enrico Scholz + * + * 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 . + */ + +#include "module.h" +#include "network-proxy-socks5.h" + +#include +#include + +#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 00000000..963bad3d --- /dev/null +++ b/src/core/network-proxy-socks5.h @@ -0,0 +1,31 @@ +/* --*- c -*-- + * Copyright (C) 2008 Enrico Scholz + * + * 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 . + */ + +#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 */