diff --git a/TODO b/TODO index 7d28a3f0..832c7e05 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,8 @@ + - New proxy code crashes if an invalid proxy_type setting is used + - Remove old socks code + - Lots of warnings at least when using socks5 + - Clean up coding style + 19:36 [IRCNet] [muzzy] more bugs in irssi, apparently the new version: foo splits out, bar joins, bar changes his nick to foo, foo splits again -> Glib warning "is already in split list (how?)" .. :) diff --git a/src/core/Makefile.am b/src/core/Makefile.am index fc32e17e..c664739b 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -31,6 +31,15 @@ libcore_a_SOURCES = \ net-sendbuffer.c \ network.c \ network-openssl.c \ + network-proxy.c \ + network-proxy.h \ + network-proxy-simple.c \ + network-proxy-simple.h \ + network-proxy-http.c \ + network-proxy-http.h \ + network-proxy-socks5.c \ + network-proxy-socks5.h \ + network-proxy-priv.h \ nicklist.c \ nickmatch-cache.c \ pidwait.c \ diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c index 768fd540..0b0fb881 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -551,12 +551,12 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_ return gchan; } - -GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server) +GIOChannel *net_connect_proxy_ssl(const struct network_proxy *proxy, const char *host, int port, + IPADDR *ip, IPADDR *my_ip, SERVER_REC *server) { GIOChannel *handle, *ssl_handle; - handle = net_connect_ip(ip, port, my_ip); + handle = net_connect_proxy(proxy, host, port, ip, my_ip); if (handle == NULL) return NULL; ssl_handle = irssi_ssl_get_iochannel(handle, port, server); diff --git a/src/core/network-proxy-http.c b/src/core/network-proxy-http.c new file mode 100644 index 00000000..2eb504b5 --- /dev/null +++ b/src/core/network-proxy-http.c @@ -0,0 +1,201 @@ +/* + network-proxy-http.c : irssi + + 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-http.h" + +#include +#include +#include +#include + +#include "network.h" +#include "network-proxy-priv.h" + +static void network_proxy_http_destroy(struct network_proxy *proxy) +{ + struct network_proxy_http *self = (struct network_proxy_http *)proxy->privdata; + + g_free(self->password); + + g_free(self); + + _network_proxy_destroy(proxy); + + g_free(proxy); +} + +static struct network_proxy *network_proxy_http_clone(const struct network_proxy *proxy) +{ + struct network_proxy_http *self = (struct network_proxy_http *)proxy->privdata; + struct network_proxy_http *priv; + struct network_proxy *res; + + res = g_malloc0(sizeof(struct network_proxy)); + + _network_proxy_clone(res, proxy); + + priv = g_malloc0(sizeof(struct network_proxy_http)); + res->privdata = (void *)priv; + + priv->password = g_strdup(self->password); + return res; +} + +static bool send_connect(struct network_proxy_http *proxy, GIOChannel *ch, + const char *address, uint16_t port) +{ + char port_str[6]; + + (void)proxy; + sprintf(port_str, "%u", port); + + if (!_network_proxy_send_all(ch, "CONNECT ", -1) || + !_network_proxy_send_all(ch, address, -1) || + !_network_proxy_send_all(ch, ":", -1) || + !_network_proxy_send_all(ch, port_str, -1) || + !_network_proxy_send_all(ch, " HTTP/1.0\r\n\r\n", -1) || + !_network_proxy_flush(ch)) + return false; + + return true; +} + +static int read_response(struct network_proxy_http *proxy, GIOChannel *ch) +{ + GIOStatus status; + GString line = { .str = NULL }; + gsize term_pos; + GError *err = NULL; + int state = 0; + int rc = 0; + gchar *resp = NULL; + + (void)proxy; + for (;;) { + /* TODO: a malicious proxy can DOS us by sending much data + * without a line break */ + while ((status=g_io_channel_read_line_string(ch, &line, &term_pos, + &err))==G_IO_STATUS_AGAIN) + { + /* noop */ + } + + if (status!=G_IO_STATUS_NORMAL) { + g_warning("failed to read HTTP response: %s", err->message); + goto err; + } + + if (state==0) { + if (g_str_has_prefix(line.str, "HTTP/1.0 ")) { + resp = g_strndup(line.str+9, line.len-9-2); + rc = g_ascii_strtoull(resp, NULL, 10); + } else { + g_warning("unexpected HTTP response: '%s'", line.str); + goto err; + } + + /* state=1 ... read additional response headers + * (ignored for now) */ + state=1; + } + + if (line.len==2) /* only the \r\n terminators */ + break; + } + + if (rc!=200) + g_warning("unexpected HTTP response code: %s", resp); + + g_free(resp); + g_free(line.str); + return rc; + +err: + g_free(resp); + g_free(line.str); + return -1; +} + +static GIOChannel *network_proxy_http_connect(const struct network_proxy *proxy, const IPADDR *hint_ip, + const char *address, int port) +{ + struct network_proxy_http *self = (struct network_proxy_http *)proxy->privdata; + GIOChannel *ch; + GIOFlags old_flags; + GError *err = NULL; + const gchar *line_term; + gint line_term_sz; + + if (hint_ip) + ch = net_connect_ip(hint_ip, proxy->port, NULL); + else + ch = net_connect(proxy->host, proxy->port, NULL); + + if (!ch) + return NULL; + + /* set \r\n line delims */ + line_term = g_io_channel_get_line_term(ch, &line_term_sz); + g_io_channel_set_line_term(ch, "\r\n", 2); + + /* set to non-blocking */ + old_flags = g_io_channel_get_flags(ch); + if (g_io_channel_set_flags(ch, old_flags & ~G_IO_FLAG_NONBLOCK, &err)!=G_IO_STATUS_NORMAL) + goto err; + + if (!send_connect(self, ch, address, port) || + read_response(self, ch)!=200) + goto err; + + if (g_io_channel_set_flags(ch, old_flags, &err)!=G_IO_STATUS_NORMAL) + goto err; + + g_io_channel_set_line_term(ch, line_term, line_term_sz); + return ch; +err: + if (err) { + g_warning("something went wrong while preparing HTTP proxy request: %s", + err->message); + g_error_free(err); + } + + net_disconnect(ch); + return NULL; +} + +struct network_proxy *network_proxy_http_create(void) +{ + struct network_proxy *res; + struct network_proxy_http *priv; + + res = g_malloc0(sizeof(struct network_proxy)); + + _network_proxy_create(res); + + priv = g_malloc0(sizeof(struct network_proxy_http)); + res->privdata = (void *)priv; + + priv->password = g_strdup(settings_get_str("proxy_password")); + + res->destroy = network_proxy_http_destroy; + res->connect = network_proxy_http_connect; + res->clone = network_proxy_http_clone; + + return res; +} diff --git a/src/core/network-proxy-http.h b/src/core/network-proxy-http.h new file mode 100644 index 00000000..723aae43 --- /dev/null +++ b/src/core/network-proxy-http.h @@ -0,0 +1,12 @@ +#ifndef H_IRSSI_SRC_CORE_PROXY_HTTP_H +#define H_IRSSI_SRC_CORE_PROXY_HTTP_H + +#include "network-proxy.h" + +struct network_proxy_http { + char *password; +}; + +struct network_proxy *network_proxy_http_create(void); + +#endif diff --git a/src/core/network-proxy-priv.h b/src/core/network-proxy-priv.h new file mode 100644 index 00000000..d7b8050a --- /dev/null +++ b/src/core/network-proxy-priv.h @@ -0,0 +1,100 @@ +#ifndef H_IRSSI_SRC_CORE_PROXY_PRIV_H +#define H_IRSSI_SRC_CORE_PROXY_PRIV_H + +#include "settings.h" +#include + +inline static void _network_proxy_create(struct network_proxy *dst) +{ + // TODO: Initialize all fields, to bring the struct to a known state + dst->privdata = NULL; + dst->port = settings_get_int("proxy_port"); + dst->host = g_strdup(settings_get_str("proxy_address")); +} + +inline static void _network_proxy_clone(struct network_proxy *dst, const struct network_proxy *src) +{ + dst->host = g_strdup(src->host); + dst->port = src->port; + + dst->destroy = src->destroy; + dst->connect = src->connect; + dst->clone = src->clone; +} + +inline static void _network_proxy_destroy(struct network_proxy *proxy) +{ + g_free(proxy->host); +} + +inline static bool _network_proxy_send_all(GIOChannel *ch, const void *buf, ssize_t len) +{ + GError *err = NULL; + gsize written; + GIOStatus status; + + while ((status=g_io_channel_write_chars(ch, buf, len, &written, + &err))==G_IO_STATUS_AGAIN) + continue; + + if (status==G_IO_STATUS_NORMAL) + return true; + + if (err) { + g_warning("failed to send proxy request: %s", err->message); + g_error_free(err); + } + + return false; +} + +inline static bool _network_proxy_recv_all(GIOChannel *ch, void *buf_v, size_t len) +{ + GError *err = NULL; + gchar *buf = buf_v; + + while (len>0) { + GIOStatus status; + gsize l; + + status = g_io_channel_read_chars(ch, buf, len, &l, &err); + if (status==G_IO_STATUS_AGAIN) + continue; + if (status!=G_IO_STATUS_NORMAL) + break; + + buf += l; + len -= l; + } + + if (len==0) + return true; + + if (err) { + g_warning("failed to send proxy request: %s", err->message); + g_error_free(err); + } + + return false; +} + +inline static bool _network_proxy_flush(GIOChannel *ch) +{ + GError *err = NULL; + GIOStatus status; + + while ((status=g_io_channel_flush(ch, &err))==G_IO_STATUS_AGAIN) + continue; + + if (status==G_IO_STATUS_NORMAL) + return true; + + if (err) { + g_warning("failed to flush proxy channel: %s", err->message); + g_error_free(err); + } + + return false; +} + +#endif diff --git a/src/core/network-proxy-simple.c b/src/core/network-proxy-simple.c new file mode 100644 index 00000000..980215b5 --- /dev/null +++ b/src/core/network-proxy-simple.c @@ -0,0 +1,128 @@ +/* + network-proxy-simple.c : irssi + + 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-simple.h" + +#include "network-proxy-priv.h" +#include "network.h" + +static void network_proxy_simple_destroy(struct network_proxy *proxy) +{ + struct network_proxy_simple *self = (struct network_proxy_simple *)proxy->privdata; + + g_free(self->password); + g_free(self->string_after); + g_free(self->string); + + g_free(self); + + _network_proxy_destroy(proxy); + + // We are responsible for the whole proxy struct + g_free(proxy); +} + +static struct network_proxy *network_proxy_simple_clone(const struct network_proxy *proxy) +{ + struct network_proxy_simple *self = (struct network_proxy_simple *)proxy->privdata; + struct network_proxy *res; + struct network_proxy_simple *newself; + + // First make and set the parent struct + res = g_malloc0(sizeof(struct network_proxy)); + _network_proxy_clone(res, proxy); + + // Then allocate and set the private data + newself = g_malloc0(sizeof(struct network_proxy_simple)); + res->privdata = (void *)newself; + + newself->string = g_strdup(self->string); + newself->string_after = g_strdup(self->string_after); + newself->password = g_strdup(self->password); + + return res; +} + +static GIOChannel *network_proxy_simple_connect(const struct network_proxy *proxy, + const IPADDR *hint_ip, char const *address, int port) +{ + if (hint_ip) + return net_connect_ip(hint_ip, proxy->port, NULL); + else + return net_connect(proxy->host, proxy->port, NULL); +} + +static void network_proxy_simple_send_string(const struct network_proxy *proxy, + const struct network_proxy_send_string_info *info) +{ + struct network_proxy_simple *self = (struct network_proxy_simple *)proxy->privdata; + char *cmd; + + if (self->password && self->password[0]) { + cmd = g_strdup_printf("PASS %s", self->password); + info->func(info->obj, cmd); + g_free(cmd); + } + + if (self->string && self->string[0]) { + cmd = g_strdup_printf(self->string, info->host, info->port); + info->func(info->obj, cmd); + g_free(cmd); + } +} + +static void network_proxy_simple_send_string_after(const struct network_proxy *proxy, + const struct network_proxy_send_string_info *info) +{ + struct network_proxy_simple *self = (struct network_proxy_simple *)proxy->privdata; + char *cmd; + + if (self->string_after && self->string_after[0]) { + cmd = g_strdup_printf(self->string_after, info->host, info->port); + info->func(info->obj, cmd); + g_free(cmd); + } +} + +struct network_proxy *network_proxy_simple_create(void) +{ + struct network_proxy *proxy; + struct network_proxy_simple *self; + + proxy = g_malloc0(sizeof(struct network_proxy)); + + // assume it could reset every variable to a known state + _network_proxy_create(proxy); + + self = g_malloc0(sizeof(struct network_proxy_simple)); + proxy->privdata = (void *)self; + + self->string = g_strdup(settings_get_str("proxy_string")); + self->string_after = g_strdup(settings_get_str("proxy_string_after")); + self->password = g_strdup(settings_get_str("proxy_password")); + + proxy->destroy = network_proxy_simple_destroy; + proxy->connect = network_proxy_simple_connect; + proxy->clone = network_proxy_simple_clone; + + proxy->send_string = network_proxy_simple_send_string; + proxy->send_string_after = network_proxy_simple_send_string_after; + + return proxy; +} diff --git a/src/core/network-proxy-simple.h b/src/core/network-proxy-simple.h new file mode 100644 index 00000000..010d8e4a --- /dev/null +++ b/src/core/network-proxy-simple.h @@ -0,0 +1,14 @@ +#ifndef H_IRSSI_SRC_CORE_PROXY_SIMPLE_H +#define H_IRSSI_SRC_CORE_PROXY_SIMPLE_H + +#include "network-proxy.h" + +struct network_proxy_simple { + char *string_after; + char *string; + char *password; +}; + +struct network_proxy *network_proxy_simple_create(void); + +#endif diff --git a/src/core/network-proxy-socks5.c b/src/core/network-proxy-socks5.c new file mode 100644 index 00000000..c3dc1e4c --- /dev/null +++ b/src/core/network-proxy-socks5.c @@ -0,0 +1,343 @@ +/* + network-proxy-socks5.c : irssi + + 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 = (struct network_proxy_socks5 *)proxy->privdata; + + g_free(self->password); + g_free(self->username); + g_free(self); + _network_proxy_destroy(proxy); + g_free(proxy); +} + +static struct network_proxy *network_proxy_socks5_clone(const struct network_proxy *proxy) +{ + struct network_proxy_socks5 *self = (struct network_proxy_socks5 *)proxy->privdata; + struct network_proxy_socks5 *priv; + struct network_proxy *res; + + res = g_malloc0(sizeof(struct network_proxy)); + _network_proxy_clone(res, proxy); + + priv = g_malloc0(sizeof(struct network_proxy_socks5)); + res->privdata = (void *)priv; + + priv->username = g_strdup(self->username); + priv->password = g_strdup(self->password); + return res; +} + +static bool socks5_connect_unauthorized(GIOChannel *ch) +{ + /* nothing to do here */ + (void)ch; + return true; +} + +/* TODO: test this method! */ +static bool socks5_connect_plain(const struct network_proxy_socks5 *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(const struct network_proxy_socks5 *proxy, GIOChannel *ch, + const char *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(const struct network_proxy *proxy, const IPADDR *hint_ip, + const char *address, int port) +{ + struct network_proxy_socks5 *self = (struct network_proxy_socks5 *)proxy->privdata; + GIOChannel *ch; + + GIOFlags old_flags; + const gchar *old_enc; + gboolean old_buf; + GError *err = NULL; + + if (hint_ip) + ch = net_connect_ip(hint_ip, proxy->port, NULL); + else + ch = net_connect(proxy->host, 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 *res; + struct network_proxy_socks5 *priv; + + res = g_malloc0(sizeof(struct network_proxy)); + + _network_proxy_create(res); + + priv = g_malloc0(sizeof(struct network_proxy_socks5)); + res->privdata = (void *)priv; + + priv->username = g_strdup(settings_get_str("proxy_username")); + priv->password = g_strdup(settings_get_str("proxy_password")); + + res->destroy = network_proxy_socks5_destroy; + res->connect = network_proxy_socks5_connect; + res->clone = network_proxy_socks5_clone; + + return res; +} diff --git a/src/core/network-proxy-socks5.h b/src/core/network-proxy-socks5.h new file mode 100644 index 00000000..45431d3a --- /dev/null +++ b/src/core/network-proxy-socks5.h @@ -0,0 +1,13 @@ +#ifndef H_IRSSI_SRC_CORE_PROXY_SOCKS5_H +#define H_IRSSI_SRC_CORE_PROXY_SOCKS5_H + +#include "network-proxy.h" + +struct network_proxy_socks5 { + char *username; + char *password; +}; + +struct network_proxy *network_proxy_socks5_create(void); + +#endif diff --git a/src/core/network-proxy.c b/src/core/network-proxy.c new file mode 100644 index 00000000..f056da6b --- /dev/null +++ b/src/core/network-proxy.c @@ -0,0 +1,43 @@ +/* + network-proxy.c : irssi + + 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.h" +#include +#include "network-proxy-simple.h" +#include "network-proxy-http.h" +#include "network-proxy-socks5.h" + +struct network_proxy *network_proxy_create(const char *type) +{ + if (type==NULL) + return NULL; + + if (strcmp(type, "simple")==0 || type[0]=='\0') + return network_proxy_simple_create(); + + if (strcmp(type, "http")==0) + return network_proxy_http_create(); + + if (strcmp(type, "socks5")==0) + return network_proxy_socks5_create(); + + g_error("unsupported proxy type '%s'", type); + return NULL; +} diff --git a/src/core/network-proxy.h b/src/core/network-proxy.h new file mode 100644 index 00000000..a9fbf936 --- /dev/null +++ b/src/core/network-proxy.h @@ -0,0 +1,65 @@ +#ifndef H_IRSSI_SRC_CORE_PROXY_H +#define H_IRSSI_SRC_CORE_PROXY_H + +#include +#include + +/* helper structure for the send_string*() functions of the network_proxy + * class */ +struct network_proxy_send_string_info { + const char *host; /* hostname of the IRC server */ + uint16_t port; /* portnumber of the IRC server */ + + /* function which is used to send string; usually irc_send_cmd_now() */ + void (*func)(void *obj, const char *); + + /* object for func */ + void *obj; +}; + +struct network_proxy { + /* Contains private data for the chosen proxy type */ + void *privdata; + + /* destroys the network_proxy structure which must not be used anymore + * after; this memberfunction is mandatory */ + void (*destroy)(struct network_proxy *); + + /* connects through the proxy; this memberfunction is mandatory + * + * \arg hint_ip the asynchronously resolved ip of the proxy; when + * NULL, method will resolve it itself + * \arg address the hostname where proxy shall connect to + * \arg port port address where proxy shall connect to + */ + GIOChannel *(*connect)(const struct network_proxy *, const IPADDR *hint_ip, + const char *address, int port); + + /* clones the given network_proxy object; this memberfunction is + * mandatory */ + struct network_proxy *(*clone)(const struct network_proxy *); + + + /* sends a string after connection has been established but before IRC + * authentication begins; this memberfunction is optional + */ + void (*send_string)(const struct network_proxy *, + const struct network_proxy_send_string_info *); + + /* sends a string after connection IRC authentication suceeded; this + * memberfunction is optional + */ + void (*send_string_after)(const struct network_proxy *, + const struct network_proxy_send_string_info *); + + /* hostname of proxy host */ + char *host; + + /* portnumber of proxy */ + int port; +}; + +/* factory method to create a proxy object based upon value of 'type' */ +struct network_proxy *network_proxy_create(const char *type); + +#endif diff --git a/src/core/network.c b/src/core/network.c index 3659ab36..2969902a 100644 --- a/src/core/network.c +++ b/src/core/network.c @@ -20,6 +20,7 @@ #include "module.h" #include "network.h" +#include "network-proxy.h" #include @@ -169,7 +170,7 @@ GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip) } /* Connect to socket with ip address */ -GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) +GIOChannel *net_connect_ip(const IPADDR *ip, int port, IPADDR *my_ip) { union sockaddr_union so; int handle, ret, opt = 1; @@ -226,6 +227,16 @@ GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) return g_io_channel_new(handle); } +/* Connect to socket */ +GIOChannel *net_connect_proxy(const struct network_proxy *proxy, + const char *host, int port, IPADDR *ip, IPADDR *my_ip) +{ + if (proxy) + return proxy->connect(proxy, ip, host, port); + else + return net_connect_ip(ip, port, my_ip); +} + /* Connect to named UNIX socket */ GIOChannel *net_connect_unix(const char *path) { diff --git a/src/core/network.h b/src/core/network.h index fa7e9675..0cdaaf1f 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -39,6 +39,7 @@ struct _IPADDR { #define IPADDR_IS_V6(ip) ((ip)->family != AF_INET) +struct network_proxy; extern IPADDR ip4_any; GIOChannel *g_io_channel_new(int handle); @@ -49,10 +50,13 @@ int net_ip_compare(IPADDR *ip1, IPADDR *ip2); /* Connect to socket */ GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip); /* Connect to socket with ip address and SSL*/ -GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server); +GIOChannel *net_connect_proxy_ssl(const struct network_proxy *proxy, const char *host, int port, + IPADDR *ip, IPADDR *my_ip, SERVER_REC *server); int irssi_ssl_handshake(GIOChannel *handle); /* Connect to socket with ip address */ -GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); +GIOChannel *net_connect_ip(const IPADDR *ip, int port, IPADDR *my_ip); +GIOChannel *net_connect_proxy(const struct network_proxy *proxy, const char *host, int port, + IPADDR *ip, IPADDR *my_ip); /* Connect to named UNIX socket */ GIOChannel *net_connect_unix(const char *path); /* Disconnect socket */ diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h index 17537508..92a57a31 100644 --- a/src/core/server-connect-rec.h +++ b/src/core/server-connect-rec.h @@ -5,10 +5,7 @@ int chat_type; /* chat_protocol_lookup(xx) */ int refcount; -/* if we're connecting via proxy, or just NULLs */ -char *proxy; -int proxy_port; -char *proxy_string, *proxy_string_after, *proxy_password; +struct network_proxy *proxy; unsigned short family; /* 0 = don't care, AF_INET or AF_INET6 */ char *tag; /* try to keep this tag when connected to server */ diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c index 0a08b461..3ee498ed 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -29,6 +29,7 @@ #include "servers-reconnect.h" #include "settings.h" +#include "network-proxy.h" GSList *reconnects; static int last_reconnect_tag; @@ -157,11 +158,7 @@ server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) server_connect_ref(dest); dest->type = module_get_uniq_id("SERVER CONNECT", 0); dest->reconnection = src->reconnection; - dest->proxy = g_strdup(src->proxy); - dest->proxy_port = src->proxy_port; - dest->proxy_string = g_strdup(src->proxy_string); - dest->proxy_string_after = g_strdup(src->proxy_string_after); - dest->proxy_password = g_strdup(src->proxy_password); + dest->proxy = src->proxy ? src->proxy->clone(src->proxy) : NULL; dest->tag = g_strdup(src->tag); diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index 0819ff1a..19b1dc16 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -28,6 +28,7 @@ #include "chatnets.h" #include "servers.h" #include "servers-setup.h" +#include "network-proxy.h" GSList *setupservers; @@ -126,15 +127,6 @@ static void server_setup_fill(SERVER_CONNECT_REC *conn, conn->username = g_strdup(settings_get_str("user_name")); conn->realname = g_strdup(settings_get_str("real_name")); - /* proxy settings */ - if (settings_get_bool("use_proxy")) { - conn->proxy = g_strdup(settings_get_str("proxy_address")); - conn->proxy_port = settings_get_int("proxy_port"); - conn->proxy_string = g_strdup(settings_get_str("proxy_string")); - conn->proxy_string_after = g_strdup(settings_get_str("proxy_string_after")); - conn->proxy_password = g_strdup(settings_get_str("proxy_password")); - } - /* source IP */ if (source_host_ip4 != NULL) { conn->own_ip4 = g_new(IPADDR, 1); @@ -145,6 +137,10 @@ static void server_setup_fill(SERVER_CONNECT_REC *conn, memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR)); } + /* proxy settings */ + if (settings_get_bool("use_proxy")) + conn->proxy = network_proxy_create(settings_get_str("proxy_type")); + signal_emit("server setup fill connect", 1, conn); } @@ -545,7 +541,9 @@ void servers_setup_init(void) settings_add_int("proxy", "proxy_port", 6667); settings_add_str("proxy", "proxy_string", "CONNECT %s %d"); settings_add_str("proxy", "proxy_string_after", ""); + settings_add_str("proxy", "proxy_username", ""); settings_add_str("proxy", "proxy_password", ""); + settings_add_str("proxy", "proxy_type", "simple"); setupservers = NULL; source_host_ip4 = source_host_ip6 = NULL; diff --git a/src/core/servers.c b/src/core/servers.c index 06f82d4d..3abd96bc 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -34,6 +34,7 @@ #include "servers-setup.h" #include "channels.h" #include "queries.h" +#include "network-proxy.h" GSList *servers, *lookup_servers; @@ -221,10 +222,15 @@ static void server_real_connect(SERVER_REC *server, IPADDR *ip, own_ip = ip == NULL ? NULL : (IPADDR_IS_V6(ip) ? server->connrec->own_ip6 : server->connrec->own_ip4); - port = server->connrec->proxy != NULL ? - server->connrec->proxy_port : server->connrec->port; + port = server->connrec->port; handle = server->connrec->use_ssl ? - net_connect_ip_ssl(ip, port, own_ip, server) : net_connect_ip(ip, port, own_ip); + net_connect_proxy_ssl(server->connrec->proxy, + server->connrec->address, port, + ip, own_ip, + server) : + net_connect_proxy(server->connrec->proxy, + server->connrec->address, port, + ip, own_ip); } else { handle = net_connect_unix(unix_socket); } @@ -421,7 +427,7 @@ int server_start_connect(SERVER_REC *server) server->connect_pipe[1] = g_io_channel_new(fd[1]); connect_address = server->connrec->proxy != NULL ? - server->connrec->proxy : server->connrec->address; + server->connrec->proxy->host : server->connrec->address; server->connect_pid = net_gethostbyname_nonblock(connect_address, server->connect_pipe[1], @@ -616,10 +622,8 @@ void server_connect_unref(SERVER_CONNECT_REC *conn) if (conn->connect_handle != NULL) net_disconnect(conn->connect_handle); - g_free_not_null(conn->proxy); - g_free_not_null(conn->proxy_string); - g_free_not_null(conn->proxy_string_after); - g_free_not_null(conn->proxy_password); + if (conn->proxy) + conn->proxy->destroy(conn->proxy); g_free_not_null(conn->tag); g_free_not_null(conn->address); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index 27878989..ccd19aa8 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -37,6 +37,7 @@ #include "servers-reconnect.h" #include "servers-redirect.h" #include "modes.h" +#include "network-proxy.h" #include "settings.h" #include "recode.h" @@ -195,23 +196,19 @@ static void server_init(IRC_SERVER_REC *server) IRC_SERVER_CONNECT_REC *conn; char *address, *ptr, *username, *cmd; GTimeVal now; + const struct network_proxy_send_string_info send_info = { + .host = server->connrec->address, + .port = server->connrec->port, + .func = irc_send_cmd_now_wrapper, + .obj = server + }; g_return_if_fail(server != NULL); conn = server->connrec; - if (conn->proxy != NULL && conn->proxy_password != NULL && - *conn->proxy_password != '\0') { - cmd = g_strdup_printf("PASS %s", conn->proxy_password); - irc_send_cmd_now(server, cmd); - g_free(cmd); - } - - if (conn->proxy != NULL && conn->proxy_string != NULL) { - cmd = g_strdup_printf(conn->proxy_string, conn->address, conn->port); - irc_send_cmd_now(server, cmd); - g_free(cmd); - } + if (conn->proxy && conn->proxy->send_string) + conn->proxy->send_string(conn->proxy, &send_info); if (conn->password != NULL && *conn->password != '\0') { /* send password */ @@ -245,11 +242,8 @@ static void server_init(IRC_SERVER_REC *server) g_free(cmd); g_free(username); - if (conn->proxy != NULL && conn->proxy_string_after != NULL) { - cmd = g_strdup_printf(conn->proxy_string_after, conn->address, conn->port); - irc_send_cmd_now(server, cmd); - g_free(cmd); - } + if (conn->proxy && conn->proxy->send_string_after) + conn->proxy->send_string_after(conn->proxy, &send_info); server->isupport = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); @@ -319,6 +313,11 @@ void irc_server_connect(SERVER_REC *server) } } +void irc_send_cmd_now_wrapper(void *server, const char *cmd) +{ + return irc_send_cmd_now((IRC_SERVER_REC *)server, cmd); +} + /* Returns TRUE if `command' is sent to `target' */ static int command_has_target(const char *cmd, const char *target) { diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 7e4eeabf..c6119df3 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -125,6 +125,8 @@ void irc_server_send_away(IRC_SERVER_REC *server, const char *reason); void irc_server_send_data(IRC_SERVER_REC *server, const char *data, int len); void irc_server_init_isupport(IRC_SERVER_REC *server); +void irc_send_cmd_now_wrapper(void *server, const char *cmd); + void irc_servers_start_cmd_timeout(void); void irc_servers_init(void);