From bd0187a82523db1b82b90ca103b23e4cc3f53acf Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 17 Aug 2014 17:05:43 +0200
Subject: ZyklonB: add SOCKS 5/4a support
---
zyklonb.c | 455 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 444 insertions(+), 11 deletions(-)
diff --git a/zyklonb.c b/zyklonb.c
index d4e66ca..7c00583 100644
--- a/zyklonb.c
+++ b/zyklonb.c
@@ -22,6 +22,7 @@
#define PROGRAM_VERSION "alpha"
#include "common.c"
+#include
// --- Configuration (application-specific) ------------------------------------
@@ -39,6 +40,11 @@ static struct config_item g_config_table[] =
{ "reconnect", "on", "Whether to reconnect on error" },
{ "reconnect_delay", "5", "Time between reconnecting" },
+ { "socks_host", NULL, "Address of a SOCKS 4a/5 proxy" },
+ { "socks_port", "1080", "SOCKS port number" },
+ { "socks_username", NULL, "SOCKS auth. username" },
+ { "socks_password", NULL, "SOCKS auth. password" },
+
{ "prefix", ":", "The prefix for bot commands" },
{ "admin", NULL, "Host mask for administrators" },
{ "plugins", NULL, "The plugins to load on startup" },
@@ -313,7 +319,7 @@ error_ssl_1:
static bool
irc_establish_connection (struct bot_context *ctx,
- const char *host, const char *port, bool use_ssl, struct error **e)
+ const char *host, const char *port, struct error **e)
{
struct addrinfo gai_hints, *gai_result, *gai_iter;
memset (&gai_hints, 0, sizeof gai_hints);
@@ -374,14 +380,6 @@ irc_establish_connection (struct bot_context *ctx,
}
ctx->irc_fd = sockfd;
- if (use_ssl && !irc_initialize_ssl (ctx, e))
- {
- xclose (ctx->irc_fd);
- ctx->irc_fd = -1;
- return false;
- }
-
- print_status ("connection established");
return true;
}
@@ -624,6 +622,409 @@ setup_recovery_handler (struct bot_context *ctx)
print_error ("sigaction: %s", strerror (errno));
}
+// --- SOCKS 5/4a (blocking implementation) ------------------------------------
+
+// These are awkward protocols. Note that the `username' is used differently
+// in SOCKS 4a and 5. In the former version, it is the username that you can
+// get ident'ed against. In the latter version, it forms a pair with the
+// password field and doesn't need to be an actual user on your machine.
+
+// TODO: make a non-blocking poller-based version of this; we need c-ares
+
+struct socks_addr
+{
+ enum socks_addr_type
+ {
+ SOCKS_IPV4 = 1, ///< IPv4 address
+ SOCKS_DOMAIN = 3, ///< Domain name to be resolved
+ SOCKS_IPV6 = 4 ///< IPv6 address
+ }
+ type; ///< The type of this address
+ union
+ {
+ uint8_t ipv4[4]; ///< IPv4 address, network octet order
+ const char *domain; ///< Domain name
+ uint8_t ipv6[16]; ///< IPv6 address, network octet order
+ }
+ data; ///< The address itself
+};
+
+struct socks_data
+{
+ struct socks_addr address; ///< Target address
+ uint16_t port; ///< Target port
+ const char *username; ///< Authentication username
+ const char *password; ///< Authentication password
+
+ struct socks_addr bound_address; ///< Bound address at the server
+ uint16_t bound_port; ///< Bound port at the server
+};
+
+static bool
+socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
+{
+ int sockfd;
+ for (; addresses; addresses = addresses->ai_next)
+ {
+ sockfd = socket (addresses->ai_family,
+ addresses->ai_socktype, addresses->ai_protocol);
+ if (sockfd == -1)
+ continue;
+ set_cloexec (sockfd);
+
+ int yes = 1;
+ soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
+ &yes, sizeof yes) != -1);
+
+ if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
+ break;
+ xclose (sockfd);
+ }
+ if (!addresses)
+ {
+ error_set (e, "couldn't connect to the SOCKS server");
+ return false;
+ }
+ *fd = sockfd;
+ return true;
+}
+
+#define SOCKS_FAIL(...) \
+ BLOCK_START \
+ error_set (e, __VA_ARGS__); \
+ goto fail; \
+ BLOCK_END
+#define SOCKS_RECV(buf, len) \
+ BLOCK_START \
+ if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
+ SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
+ if (n != (len)) \
+ SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
+ BLOCK_END
+
+static bool
+socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
+ int *fd, struct error **e)
+{
+ int sockfd;
+ if (!socks_get_socket (addresses, &sockfd, e))
+ return false;
+
+ const void *dest_ipv4 = "\x00\x00\x00\x01";
+ const char *dest_domain = NULL;
+
+ char buf[INET6_ADDRSTRLEN];
+ switch (data->address.type)
+ {
+ case SOCKS_IPV4:
+ dest_ipv4 = data->address.data.ipv4;
+ break;
+ case SOCKS_IPV6:
+ // About the best thing we can do, not sure if it works anywhere at all
+ if (!inet_ntop (AF_INET6, &data->address.data.ipv6, buf, sizeof buf))
+ SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
+ dest_domain = buf;
+ break;
+ case SOCKS_DOMAIN:
+ dest_domain = data->address.data.domain;
+ }
+
+ struct str req;
+ str_init (&req);
+ str_append_c (&req, 4); // version
+ str_append_c (&req, 1); // connect
+
+ str_append_c (&req, data->port >> 8); // higher bits of port
+ str_append_c (&req, data->port); // lower bits of port
+ str_append_data (&req, dest_ipv4, 4); // destination address
+
+ if (data->username)
+ str_append (&req, data->username);
+ str_append_c (&req, '\0');
+
+ if (dest_domain)
+ {
+ str_append (&req, dest_domain);
+ str_append_c (&req, '\0');
+ }
+
+ ssize_t n = send (sockfd, req.str, req.len, 0);
+ str_free (&req);
+ if (n == -1)
+ SOCKS_FAIL ("%s: %s", "send", strerror (errno));
+
+ uint8_t resp[8];
+ SOCKS_RECV (resp, sizeof resp);
+ if (resp[0] != 0)
+ SOCKS_FAIL ("protocol error");
+
+ switch (resp[1])
+ {
+ case 90:
+ break;
+ case 91:
+ SOCKS_FAIL ("request rejected or failed");
+ case 92:
+ SOCKS_FAIL ("%s: %s", "request rejected",
+ "SOCKS server cannot connect to identd on the client");
+ case 93:
+ SOCKS_FAIL ("%s: %s", "request rejected",
+ "identd reports different user-id");
+ default:
+ SOCKS_FAIL ("protocol error");
+ }
+
+ *fd = sockfd;
+ return true;
+
+fail:
+ xclose (sockfd);
+ return false;
+}
+
+#undef SOCKS_FAIL
+#define SOCKS_FAIL(...) \
+ BLOCK_START \
+ error_set (e, __VA_ARGS__); \
+ return false; \
+ BLOCK_END
+
+static bool
+socks_5_userpass_auth (int sockfd, struct socks_data *data, struct error **e)
+{
+ size_t ulen = strlen (data->username);
+ if (ulen > 255)
+ ulen = 255;
+
+ size_t plen = strlen (data->password);
+ if (plen > 255)
+ plen = 255;
+
+ uint8_t req[3 + ulen + plen], *p = req;
+ *p++ = 0x01; // version
+ *p++ = ulen; // username length
+ memcpy (p, data->username, ulen);
+ p += ulen;
+ *p++ = plen; // password length
+ memcpy (p, data->password, plen);
+ p += plen;
+
+ ssize_t n = send (sockfd, req, p - req, 0);
+ if (n == -1)
+ SOCKS_FAIL ("%s: %s", "send", strerror (errno));
+
+ uint8_t resp[2];
+ SOCKS_RECV (resp, sizeof resp);
+ if (resp[0] != 0x01)
+ SOCKS_FAIL ("protocol error");
+ if (resp[1] != 0x00)
+ SOCKS_FAIL ("authentication failure");
+ return true;
+}
+
+static bool
+socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
+{
+ bool can_auth = data->username && data->password;
+
+ uint8_t hello[4];
+ hello[0] = 0x05; // version
+ hello[1] = 1 + can_auth; // number of authentication methods
+ hello[2] = 0x00; // no authentication required
+ hello[3] = 0x02; // username/password
+
+ ssize_t n = send (sockfd, hello, 3 + can_auth, 0);
+ if (n == -1)
+ SOCKS_FAIL ("%s: %s", "send", strerror (errno));
+
+ uint8_t resp[2];
+ SOCKS_RECV (resp, sizeof resp);
+ if (resp[0] != 0x05)
+ SOCKS_FAIL ("protocol error");
+
+ switch (resp[1])
+ {
+ case 0x02:
+ if (!can_auth)
+ SOCKS_FAIL ("protocol error");
+ if (!socks_5_userpass_auth (sockfd, data, e))
+ return false;
+ case 0x00:
+ break;
+ case 0xFF:
+ SOCKS_FAIL ("no acceptable authentication methods");
+ default:
+ SOCKS_FAIL ("protocol error");
+ }
+ return true;
+}
+
+static bool
+socks_5_send_req (int sockfd, struct socks_data *data, struct error **e)
+{
+ uint8_t req[4 + 256 + 2], *p = req;
+ *p++ = 0x05; // version
+ *p++ = 0x01; // connect
+ *p++ = 0x00; // reserved
+ *p++ = data->address.type;
+
+ switch (data->address.type)
+ {
+ case SOCKS_IPV4:
+ memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
+ p += sizeof data->address.data.ipv4;
+ break;
+ case SOCKS_DOMAIN:
+ {
+ size_t dlen = strlen (data->address.data.domain);
+ if (dlen > 255)
+ dlen = 255;
+
+ *p++ = dlen;
+ memcpy (p, data->address.data.domain, dlen);
+ p += dlen;
+ break;
+ }
+ case SOCKS_IPV6:
+ memcpy (p, data->address.data.ipv6, sizeof data->address.data.ipv6);
+ p += sizeof data->address.data.ipv6;
+ break;
+ }
+ *p++ = data->port >> 8;
+ *p++ = data->port;
+
+ if (send (sockfd, req, p - req, 0) == -1)
+ SOCKS_FAIL ("%s: %s", "send", strerror (errno));
+ return true;
+}
+
+static bool
+socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
+{
+ uint8_t resp_header[4];
+ ssize_t n;
+ SOCKS_RECV (resp_header, sizeof resp_header);
+ if (resp_header[0] != 0x05)
+ SOCKS_FAIL ("protocol error");
+
+ switch (resp_header[1])
+ {
+ case 0x00:
+ break;
+ case 0x01: SOCKS_FAIL ("general SOCKS server failure");
+ case 0x02: SOCKS_FAIL ("connection not allowed by ruleset");
+ case 0x03: SOCKS_FAIL ("network unreachable");
+ case 0x04: SOCKS_FAIL ("host unreachable");
+ case 0x05: SOCKS_FAIL ("connection refused");
+ case 0x06: SOCKS_FAIL ("TTL expired");
+ case 0x07: SOCKS_FAIL ("command not supported");
+ case 0x08: SOCKS_FAIL ("address type not supported");
+ default: SOCKS_FAIL ("protocol error");
+ }
+
+ switch ((data->bound_address.type = resp_header[3]))
+ {
+ case SOCKS_IPV4:
+ SOCKS_RECV (data->bound_address.data.ipv4,
+ sizeof data->bound_address.data.ipv4);
+ break;
+ case SOCKS_IPV6:
+ SOCKS_RECV (data->bound_address.data.ipv6,
+ sizeof data->bound_address.data.ipv6);
+ break;
+ case SOCKS_DOMAIN:
+ {
+ uint8_t len;
+ SOCKS_RECV (&len, sizeof len);
+
+ char domain[len + 1];
+ SOCKS_RECV (domain, len);
+ domain[len] = '\0';
+
+ data->bound_address.data.domain = xstrdup (domain);
+ }
+ default:
+ SOCKS_FAIL ("protocol error");
+ }
+
+ uint16_t port;
+ SOCKS_RECV (&port, sizeof port);
+ data->bound_port = ntohs (port);
+ return true;
+}
+
+#undef SOCKS_FAIL
+#undef SOCKS_RECV
+
+static bool
+socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
+ int *fd, struct error **e)
+{
+ int sockfd;
+ if (!socks_get_socket (addresses, &sockfd, e))
+ return false;
+
+ if (!socks_5_auth (sockfd, data, e)
+ || !socks_5_send_req (sockfd, data, e)
+ || !socks_5_process_resp (sockfd, data, e))
+ {
+ xclose (sockfd);
+ return false;
+ }
+
+ *fd = sockfd;
+ return true;
+}
+
+static int
+socks_connect (const char *socks_host, const char *socks_port,
+ const char *host, const char *port,
+ const char *username, const char *password, struct error **e)
+{
+ struct addrinfo gai_hints, *gai_result;
+ memset (&gai_hints, 0, sizeof gai_hints);
+ gai_hints.ai_socktype = SOCK_STREAM;
+
+ int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
+ if (err)
+ {
+ error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
+ return false;
+ }
+
+ unsigned long port_no;
+ const struct servent *serv;
+ if ((serv = getservbyname (port, "tcp")))
+ port_no = (uint16_t) serv->s_port;
+ else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
+ {
+ error_set (e, "invalid port number");
+ return false;
+ }
+
+ struct socks_data data =
+ { .username = username, .password = password, .port = port_no };
+
+ if (inet_pton (AF_INET, host, &data.address.data.ipv4) == 1)
+ data.address.type = SOCKS_IPV4;
+ else if (inet_pton (AF_INET6, host, &data.address.data.ipv6) == 1)
+ data.address.type = SOCKS_IPV6;
+ else
+ {
+ data.address.type = SOCKS_DOMAIN;
+ data.address.data.domain = host;
+ }
+
+ int fd;
+ bool success = socks_5_connect (gai_result, &data, &fd, NULL)
+ || socks_4a_connect (gai_result, &data, &fd, e);
+
+ freeaddrinfo (gai_result);
+ if (data.bound_address.type == SOCKS_DOMAIN)
+ free ((char *) data.bound_address.data.domain);
+ return success ? fd : -1;
+}
+
// --- Plugins -----------------------------------------------------------------
/// The name of the special IRC command for interprocess communication
@@ -1558,12 +1959,17 @@ irc_connect (struct bot_context *ctx, struct error **e)
const char *irc_port = str_map_find (&ctx->config, "irc_port");
const char *ssl_use_str = str_map_find (&ctx->config, "ssl_use");
+ const char *socks_host = str_map_find (&ctx->config, "socks_host");
+ const char *socks_port = str_map_find (&ctx->config, "socks_port");
+ const char *socks_username = str_map_find (&ctx->config, "socks_username");
+ const char *socks_password = str_map_find (&ctx->config, "socks_password");
+
const char *nickname = str_map_find (&ctx->config, "nickname");
const char *username = str_map_find (&ctx->config, "username");
const char *realname = str_map_find (&ctx->config, "realname");
// We have a default value for these
- hard_assert (irc_port && ssl_use_str);
+ hard_assert (irc_port && ssl_use_str && socks_port);
hard_assert (nickname && username && realname);
// TODO: again, get rid of `struct error' in here. The question is: how
@@ -1581,8 +1987,35 @@ irc_connect (struct bot_context *ctx, struct error **e)
return false;
}
- if (!irc_establish_connection (ctx, irc_host, irc_port, use_ssl, e))
+ if (socks_host)
+ {
+ char *address = format_host_port_pair (irc_host, irc_port);
+ char *socks_address = format_host_port_pair (socks_host, socks_port);
+ print_status ("connecting to %s via %s...", address, socks_address);
+ free (socks_address);
+ free (address);
+
+ struct error *error = NULL;
+ int fd = socks_connect (socks_host, socks_port, irc_host, irc_port,
+ socks_username, socks_password, &error);
+ if (fd == -1)
+ {
+ error_set (e, "%s: %s", "SOCKS connection failed", error->message);
+ error_free (error);
+ return false;
+ }
+ ctx->irc_fd = fd;
+ }
+ else if (!irc_establish_connection (ctx, irc_host, irc_port, e))
+ return false;
+
+ if (use_ssl && !irc_initialize_ssl (ctx, e))
+ {
+ xclose (ctx->irc_fd);
+ ctx->irc_fd = -1;
return false;
+ }
+ print_status ("connection established");
// TODO: in exec try: 1/ set blocking, 2/ setsockopt() SO_LINGER,
// (struct linger) { .l_onoff = true; .l_linger = 1 /* 1s should do */; }
--
cgit v1.2.3-70-g09d2