From 4d4bdc1e6ab4fd380499876b8efd67b5823748d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 11 Apr 2015 21:09:06 +0200 Subject: Move the SOCKS code to common.c --- common.c | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) (limited to 'common.c') diff --git a/common.c b/common.c index 5f87d8c..288c127 100644 --- a/common.c +++ b/common.c @@ -29,6 +29,7 @@ #define print_debug_data ((void *) LOG_DEBUG) #include "liberty/liberty.c" +#include // --- Logging ----------------------------------------------------------------- @@ -49,3 +50,409 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt, if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0) syslog (prio, "%s%s", quote, buf); } + +// --- 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; +// either use c-ares or (even better) start another thread to do resolution + +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); + break; + } + 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) +{ + int result = -1; + struct addrinfo gai_hints, *gai_result; + memset (&gai_hints, 0, sizeof gai_hints); + gai_hints.ai_socktype = SOCK_STREAM; + + unsigned long port_no; + const struct servent *serv; + if ((serv = getservbyname (port, "tcp"))) + port_no = (uint16_t) ntohs (serv->s_port); + else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX) + { + error_set (e, "invalid port number"); + goto fail; + } + + int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result); + if (err) + { + error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err)); + goto fail; + } + + 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; + } + + if (!socks_5_connect (gai_result, &data, &result, NULL)) + socks_4a_connect (gai_result, &data, &result, e); + + if (data.bound_address.type == SOCKS_DOMAIN) + free ((char *) data.bound_address.data.domain); + freeaddrinfo (gai_result); +fail: + return result; +} -- cgit v1.2.3