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


Reply via email to