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
