123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- /*
- This file is part of GNUnet.
- Copyright (C) 2009-2013 GNUnet e.V.
- GNUnet is free software: you can redistribute it and/or modify it
- under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
- GNUnet 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
- Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- SPDX-License-Identifier: AGPL3.0-or-later
- */
- /**
- * @file util/socks.c
- * @brief SOCKS5 connection support
- * @author Jeffrey Burdges
- *
- * These routines should be called only on newly active connections.
- */
- #include "platform.h"
- #include "gnunet_util_lib.h"
- #define LOG(kind, ...) GNUNET_log_from (kind, "util-socks", __VA_ARGS__)
- #define LOG_STRERROR(kind, syscall) \
- GNUNET_log_from_strerror (kind, "util-socks", syscall)
- /* SOCKS5 authentication methods */
- #define SOCKS5_AUTH_REJECT 0xFF /* No acceptable auth method */
- #define SOCKS5_AUTH_NOAUTH 0x00 /* without authentication */
- #define SOCKS5_AUTH_GSSAPI 0x01 /* GSSAPI */
- #define SOCKS5_AUTH_USERPASS 0x02 /* User/Password */
- #define SOCKS5_AUTH_CHAP 0x03 /* Challenge-Handshake Auth Proto. */
- #define SOCKS5_AUTH_EAP 0x05 /* Extensible Authentication Proto. */
- #define SOCKS5_AUTH_MAF 0x08 /* Multi-Authentication Framework */
- /* SOCKS5 connection responses */
- #define SOCKS5_REP_SUCCEEDED 0x00 /* succeeded */
- #define SOCKS5_REP_FAIL 0x01 /* general SOCKS serer failure */
- #define SOCKS5_REP_NALLOWED 0x02 /* connection not allowed by ruleset */
- #define SOCKS5_REP_NUNREACH 0x03 /* Network unreachable */
- #define SOCKS5_REP_HUNREACH 0x04 /* Host unreachable */
- #define SOCKS5_REP_REFUSED 0x05 /* connection refused */
- #define SOCKS5_REP_EXPIRED 0x06 /* TTL expired */
- #define SOCKS5_REP_CNOTSUP 0x07 /* Command not supported */
- #define SOCKS5_REP_ANOTSUP 0x08 /* Address not supported */
- #define SOCKS5_REP_INVADDR 0x09 /* Invalid address */
- const char *
- SOCKS5_REP_names (int rep)
- {
- switch (rep)
- {
- case SOCKS5_REP_SUCCEEDED:
- return "succeeded";
- case SOCKS5_REP_FAIL:
- return "general SOCKS server failure";
- case SOCKS5_REP_NALLOWED:
- return "connection not allowed by ruleset";
- case SOCKS5_REP_NUNREACH:
- return "Network unreachable";
- case SOCKS5_REP_HUNREACH:
- return "Host unreachable";
- case SOCKS5_REP_REFUSED:
- return "connection refused";
- case SOCKS5_REP_EXPIRED:
- return "TTL expired";
- case SOCKS5_REP_CNOTSUP:
- return "Command not supported";
- case SOCKS5_REP_ANOTSUP:
- return "Address not supported";
- case SOCKS5_REP_INVADDR:
- return "Invalid address";
- default:
- return NULL;
- }
- };
- /**
- * Encode a string for the SOCKS5 protocol by prefixing it a byte stating its
- * length and stripping the trailing zero byte. Truncates any string longer
- * than 255 bytes.
- *
- * @param b buffer to contain the encoded string
- * @param s string to encode
- * @return pointer to the end of the encoded string in the buffer
- */
- unsigned char *
- SOCK5_proto_string (unsigned char *b, const char *s)
- {
- size_t l = strlen (s);
- if (l > 255)
- {
- LOG (GNUNET_ERROR_TYPE_WARNING,
- "SOCKS5 cannot handle hostnames, usernames, or passwords over 255 bytes, truncating.\n");
- l = 255;
- }
- *(b++) = (unsigned char) l;
- memcpy (b, s, l);
- return b + l;
- }
- #define SOCKS5_step_greet 0
- #define SOCKS5_step_auth 1
- #define SOCKS5_step_cmd 2
- #define SOCKS5_step_done 3
- /**
- * State of the SOCKS5 handshake.
- */
- struct GNUNET_SOCKS_Handshake
- {
- /**
- * Connection handle used for SOCKS5
- */
- struct GNUNET_CONNECTION_Handle *socks5_connection;
- /**
- * Connection handle initially returned to client
- */
- struct GNUNET_CONNECTION_Handle *target_connection;
- /**
- * Transmission handle on socks5_connection.
- */
- struct GNUNET_CONNECTION_TransmitHandle *th;
- /**
- * Our stage in the SOCKS5 handshake
- */
- int step;
- /**
- * Precomputed SOCKS5 handshake output buffer
- */
- unsigned char outbuf[1024];
- /**
- * Pointers delineating protoocol steps in the output buffer
- */
- unsigned char *(outstep[4]);
- /**
- * SOCKS5 handshake input buffer
- */
- unsigned char inbuf[1024];
- /**
- * Pointers delimiting the current step in the input buffer
- */
- unsigned char *instart;
- unsigned char *inend;
- };
- /* Registering prototypes */
- void
- register_reciever (struct GNUNET_SOCKS_Handshake *ih, int want);
- /* In fact, the client sends first rule in GNUnet suggests one could take
- * large mac read sizes without fear of screwing up the proxied protocol,
- * but we make a proper SOCKS5 client. */
- #define register_reciever_wants(ih) ((SOCKS5_step_cmd == ih->step) ? 10 : 2)
- struct GNUNET_CONNECTION_TransmitHandle *
- register_sender (struct GNUNET_SOCKS_Handshake *ih);
- /**
- * Conclude the SOCKS5 handshake successfully.
- *
- * @param ih SOCKS5 handshake, consumed here.
- * @param c open unused connection, consumed here.
- * @return Connection handle that becomes usable when the handshake completes.
- */
- void
- SOCKS5_handshake_done (struct GNUNET_SOCKS_Handshake *ih)
- {
- GNUNET_CONNECTION_acivate_proxied (ih->target_connection);
- }
- /**
- * Read one step in the SOCKS5 handshake.
- *
- * @param ih SOCKS5 Handshake
- */
- void
- SOCKS5_handshake_step (struct GNUNET_SOCKS_Handshake *ih)
- {
- unsigned char *b = ih->instart;
- size_t available = ih->inend - b;
- int want = register_reciever_wants (ih);
- if (available < want)
- {
- register_reciever (ih, want - available);
- return;
- }
- GNUNET_assert (SOCKS5_step_done > ih->step && ih->step >= 0);
- switch (ih->step)
- {
- case SOCKS5_step_greet: /* SOCKS5 server's greeting */
- if (b[0] != 5)
- {
- LOG (GNUNET_ERROR_TYPE_ERROR, "Not a SOCKS5 server\n");
- GNUNET_assert (0);
- }
- switch (b[1])
- {
- case SOCKS5_AUTH_NOAUTH:
- ih->step = SOCKS5_step_cmd; /* no authentication to do */
- break;
- case SOCKS5_AUTH_USERPASS:
- ih->step = SOCKS5_step_auth;
- break;
- case SOCKS5_AUTH_REJECT:
- LOG (GNUNET_ERROR_TYPE_ERROR, "No authentication method accepted\n");
- return;
- default:
- LOG (GNUNET_ERROR_TYPE_ERROR,
- "Not a SOCKS5 server / Nonsensical authentication\n");
- return;
- }
- b += 2;
- break;
- case SOCKS5_step_auth: /* SOCKS5 server's response to authentication */
- if (b[1] != 0)
- {
- LOG (GNUNET_ERROR_TYPE_ERROR, "SOCKS5 authentication failed\n");
- GNUNET_assert (0);
- }
- ih->step = SOCKS5_step_cmd;
- b += 2;
- break;
- case SOCKS5_step_cmd: /* SOCKS5 server's response to command */
- if (b[0] != 5)
- {
- LOG (GNUNET_ERROR_TYPE_ERROR, "SOCKS5 protocol error\n");
- GNUNET_assert (0);
- }
- if (0 != b[1])
- {
- LOG (GNUNET_ERROR_TYPE_ERROR,
- "SOCKS5 connection error : %s\n",
- SOCKS5_REP_names (b[1]));
- return;
- }
- b += 3;
- /* There is no reason to verify host and port afaik. */
- switch (*(b++))
- {
- case 1: /* IPv4 */
- b += sizeof(struct in_addr); /* 4 */
- break;
- case 4: /* IPv6 */
- b += sizeof(struct in6_addr); /* 16 */
- break;
- case 3: /* hostname */
- b += *b;
- break;
- }
- b += 2; /* port */
- if (b > ih->inend)
- {
- register_reciever (ih, b - ih->inend);
- return;
- }
- ih->step = SOCKS5_step_done;
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "SOCKS5 server : %s\n",
- SOCKS5_REP_names (b[1]));
- ih->instart = b;
- SOCKS5_handshake_done (ih);
- return;
- case SOCKS5_step_done:
- GNUNET_assert (0);
- }
- ih->instart = b;
- /* Do not reschedule the sender unless we're done reading.
- * I imagine this lets us avoid ever cancelling the transmit handle. */
- register_sender (ih);
- }
- /**
- * Callback to read from the SOCKS5 proxy.
- *
- * @param client the service
- * @param handler function to call with the message
- * @param handler_cls closure for @a handler
- */
- void
- receiver (void *cls,
- const void *buf,
- size_t available,
- const struct sockaddr *addr,
- socklen_t addrlen,
- int errCode)
- {
- struct GNUNET_SOCKS_Handshake *ih = cls;
- GNUNET_assert (&ih->inend[available] < &ih->inbuf[1024]);
- GNUNET_memcpy (ih->inend, buf, available);
- ih->inend += available;
- SOCKS5_handshake_step (ih);
- }
- /**
- * Register callback to read from the SOCKS5 proxy.
- *
- * @param client the service
- * @param handler function to call with the message
- * @param handler_cls closure for @a handler
- */
- void
- register_reciever (struct GNUNET_SOCKS_Handshake *ih, int want)
- {
- GNUNET_CONNECTION_receive (ih->socks5_connection,
- want,
- GNUNET_TIME_relative_get_minute_ (),
- &receiver,
- ih);
- }
- /**
- * Register SOCKS5 handshake sender
- *
- * @param cls closure (SOCKS handshake)
- * @param size number of bytes available in @a buf
- * @param buf where the callee should write the message
- * @return number of bytes written to @a buf
- */
- size_t
- transmit_ready (void *cls, size_t size, void *buf)
- {
- struct GNUNET_SOCKS_Handshake *ih = cls;
- /* connection.c has many routines that call us with buf == NULL :
- * signal_transmit_error() - DNS, etc. active
- * connect_fail_continuation()
- * connect_probe_continuation() - timeout
- * try_connect_using_address() - DNS failure/timeout
- * transmit_timeout() - retry failed?
- * GNUNET_CONNECTION_notify_transmit_ready() can schedule :
- * transmit_timeout() - DNS still working
- * connect_error() - DNS done but no socket?
- * transmit_ready() - scheduler shutdown or timeout, or signal_transmit_error()
- * We'd need to dig into the scheduler to guess at the reason, as
- * connection.c tells us nothing itself, but mostly its timouts.
- * Initially, we'll simply ignore this and leave massive timeouts, but
- * maybe that should change for error handling pruposes. It appears that
- * successful operations, including DNS resolution, do not use this. */if (NULL == buf)
- {
- if (0 == ih->step)
- {
- LOG (GNUNET_ERROR_TYPE_WARNING,
- "Timeout contacting SOCKS server, retrying indefinitely, but probably hopeless.\n");
- register_sender (ih);
- }
- else
- {
- LOG (GNUNET_ERROR_TYPE_ERROR,
- "Timeout during mid SOCKS handshake (step %u), probably not a SOCKS server.\n",
- ih->step);
- GNUNET_break (0);
- }
- return 0;
- }
- GNUNET_assert ((1024 >= size) && (size > 0));
- GNUNET_assert ((SOCKS5_step_done > ih->step) && (ih->step >= 0));
- unsigned char *b = ih->outstep[ih->step];
- unsigned char *e = ih->outstep[ih->step + 1];
- GNUNET_assert (e <= &ih->outbuf[1024]);
- unsigned int l = e - b;
- GNUNET_assert (size >= l);
- GNUNET_memcpy (buf, b, l);
- register_reciever (ih, register_reciever_wants (ih));
- return l;
- }
- /**
- * Register SOCKS5 handshake sender
- *
- * @param ih handshake
- * @return non-NULL if the notify callback was queued,
- * NULL if we are already going to notify someone else (busy)
- */
- struct GNUNET_CONNECTION_TransmitHandle *
- register_sender (struct GNUNET_SOCKS_Handshake *ih)
- {
- struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_MINUTES;
- GNUNET_assert (SOCKS5_step_done > ih->step);
- GNUNET_assert (ih->step >= 0);
- if (0 == ih->step)
- timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 3);
- unsigned char *b = ih->outstep[ih->step];
- unsigned char *e = ih->outstep[ih->step + 1];
- GNUNET_assert (ih->outbuf <= b && b < e && e < &ih->outbuf[1024]);
- ih->th = GNUNET_CONNECTION_notify_transmit_ready (ih->socks5_connection,
- e - b,
- timeout,
- &transmit_ready,
- ih);
- return ih->th;
- }
- /**
- * Initialize a SOCKS5 handshake for authentication via username and
- * password. Tor uses SOCKS username and password authentication to assign
- * programs unique circuits.
- *
- * @param user username for the proxy
- * @param pass password for the proxy
- * @return Valid SOCKS5 hanbdshake handle
- */
- struct GNUNET_SOCKS_Handshake *
- GNUNET_SOCKS_init_handshake (const char *user, const char *pass)
- {
- struct GNUNET_SOCKS_Handshake *ih =
- GNUNET_new (struct GNUNET_SOCKS_Handshake);
- unsigned char *b = ih->outbuf;
- ih->outstep[SOCKS5_step_greet] = b;
- *(b++) = 5; /* SOCKS5 */
- unsigned char *n = b++;
- *n = 1; /* Number of authentication methods */
- /* We support no authentication even when requesting authentication,
- * but this appears harmless, given the way that Tor uses authentication.
- * And some SOCKS5 servers might require this. */
- *(b++) = SOCKS5_AUTH_NOAUTH;
- if (NULL != user)
- {
- *(b++) = SOCKS5_AUTH_USERPASS;
- (*n)++;
- }
- /* There is no apparent reason to support authentication methods beyond
- * username and password since afaik Tor does not support them. */
- /* We authenticate with an empty username and password if the server demands
- * them but we do not have any. */
- if (user == NULL)
- user = "";
- if (pass == NULL)
- pass = "";
- ih->outstep[SOCKS5_step_auth] = b;
- *(b++) = 1; /* subnegotiation ver.: 1 */
- b = SOCK5_proto_string (b, user);
- b = SOCK5_proto_string (b, pass);
- ih->outstep[SOCKS5_step_cmd] = b;
- ih->inend = ih->instart = ih->inbuf;
- return ih;
- }
- /**
- * Initialize a SOCKS5 handshake without authentication, thereby possibly
- * sharing a Tor circuit with another process.
- *
- * @return Valid SOCKS5 hanbdshake handle
- */
- struct GNUNET_SOCKS_Handshake *
- GNUNET_SOCKS_init_handshake_noauth ()
- {
- return GNUNET_SOCKS_init_handshake (NULL, NULL);
- }
- /**
- * Build request that the SOCKS5 proxy open a TCP/IP stream to the given host
- * and port.
- *
- * @param ih SOCKS5 handshake
- * @param hostname
- * @param port
- */
- void
- GNUNET_SOCKS_set_handshake_destination (struct GNUNET_SOCKS_Handshake *ih,
- const char *host,
- uint16_t port)
- {
- union
- {
- struct in_addr in4;
- struct in6_addr in6;
- } ia;
- unsigned char *b = ih->outstep[SOCKS5_step_cmd];
- *(b++) = 5; /* SOCKS5 */
- *(b++) = 1; /* Establish a TCP/IP stream */
- *(b++) = 0; /* reserved */
- /* Specify destination */
- if (1 == inet_pton (AF_INET, host, &ia.in4))
- {
- *(b++) = 1; /* IPv4 */
- GNUNET_memcpy (b, &ia.in4, sizeof(struct in_addr));
- b += sizeof(struct in_addr); /* 4 */
- }
- else if (1 == inet_pton (AF_INET6, host, &ia.in6))
- {
- *(b++) = 4; /* IPv6 */
- GNUNET_memcpy (b, &ia.in6, sizeof(struct in6_addr));
- b += sizeof(struct in6_addr); /* 16 */
- }
- else
- {
- *(b++) = 3; /* hostname */
- b = SOCK5_proto_string (b, host);
- }
- /* Specify port */
- *(uint16_t *) b = htons (port);
- b += 2;
- ih->outstep[SOCKS5_step_done] = b;
- }
- /**
- * Run a SOCKS5 handshake on an open but unused TCP connection.
- *
- * @param ih SOCKS5 handshake, consumed here.
- * @param c open unused connection, consumed here.
- * @return Connection handle that becomes usable when the SOCKS5 handshake completes.
- */
- struct GNUNET_CONNECTION_Handle *
- GNUNET_SOCKS_run_handshake (struct GNUNET_SOCKS_Handshake *ih,
- struct GNUNET_CONNECTION_Handle *c)
- {
- ih->socks5_connection = c;
- ih->target_connection = GNUNET_CONNECTION_create_proxied_from_handshake (c);
- register_sender (ih);
- return ih->target_connection;
- }
- /**
- * Check if a SOCKS proxy is required by a service. Do not use local service
- * if a SOCKS proxy port is configured as this could deanonymize a user.
- *
- * @param service_name name of service to connect to
- * @param cfg configuration to use
- * @return GNUNET_YES if so, GNUNET_NO if not
- */
- int
- GNUNET_SOCKS_check_service (const char *service_name,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
- {
- return GNUNET_CONFIGURATION_have_value (cfg, service_name, "SOCKSPORT") ||
- GNUNET_CONFIGURATION_have_value (cfg, service_name, "SOCKSHOST");
- }
- /**
- * Try to connect to a service configured to use a SOCKS5 proxy.
- *
- * @param service_name name of service to connect to
- * @param cfg configuration to use
- * @return Connection handle that becomes usable when the handshake completes.
- * NULL if SOCKS not configured or not configured properly
- */
- struct GNUNET_CONNECTION_Handle *
- GNUNET_SOCKS_do_connect (const char *service_name,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
- {
- struct GNUNET_SOCKS_Handshake *ih;
- struct GNUNET_CONNECTION_Handle *socks5; /* *proxied */
- char *host0;
- char *host1;
- char *user;
- char *pass;
- unsigned long long port0;
- unsigned long long port1;
- if (GNUNET_YES != GNUNET_SOCKS_check_service (service_name, cfg))
- return NULL;
- if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg,
- service_name,
- "SOCKSPORT",
- &port0))
- port0 = 9050;
- /* A typical Tor client should usually try port 9150 for the TBB too, but
- * GNUnet can probably assume a system Tor installation. */
- if ((port0 > 65535) || (port0 <= 0))
- {
- LOG (GNUNET_ERROR_TYPE_WARNING,
- _ (
- "Attempting to use invalid port %d as SOCKS proxy for service `%s'.\n"),
- port0,
- service_name);
- return NULL;
- }
- if ((GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg,
- service_name,
- "PORT",
- &port1)) ||
- (port1 > 65535) || (port1 <= 0) ||
- (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg,
- service_name,
- "HOSTNAME",
- &host1)))
- {
- LOG (GNUNET_ERROR_TYPE_WARNING,
- _ (
- "Attempting to proxy service `%s' to invalid port %d or hostname.\n"),
- service_name,
- port1);
- return NULL;
- }
- /* Appeared to still work after host0 corrupted, so either test case is broken, or
- this whole routine is not being called. */
- if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg,
- service_name,
- "SOCKSHOST",
- &host0))
- host0 = NULL;
- socks5 = GNUNET_CONNECTION_create_from_connect (cfg,
- (host0 != NULL) ? host0
- : "127.0.0.1",
- port0);
- GNUNET_free (host0);
- /* Sets to NULL if they do not exist */
- (void) GNUNET_CONFIGURATION_get_value_string (cfg,
- service_name,
- "SOCKSUSER",
- &user);
- (void) GNUNET_CONFIGURATION_get_value_string (cfg,
- service_name,
- "SOCKSPASS",
- &pass);
- ih = GNUNET_SOCKS_init_handshake (user, pass);
- GNUNET_free (user);
- GNUNET_free (pass);
- GNUNET_SOCKS_set_handshake_destination (ih, host1, port1);
- GNUNET_free (host1);
- return GNUNET_SOCKS_run_handshake (ih, socks5);
- }
- /* socks.c */
|