commit 4f1a12e2893f609ac4dee54f21b55a6c4080aea5 Author: David Goulet dgoulet@ev0ke.net Date: Sun Mar 2 17:37:27 2014 -0500
Add SOCKS5 username/password authentication
Using RFC1929, implement username/password authentication for circuit isolation feature of Tor daemon.
This adds SOCKS5Username and SOCKS5Password option to torsocks.conf and also environment variable to control them (TORSOCKS_USERNAME/PASSWORD).
Signed-off-by: David Goulet dgoulet@ev0ke.net --- doc/torsocks.8 | 10 +++++ doc/torsocks.conf | 7 +++ doc/torsocks.conf.5 | 12 ++++++ src/common/config-file.c | 80 ++++++++++++++++++++++++++++++++++ src/common/config-file.h | 19 +++++++++ src/common/defaults.h | 4 ++ src/common/socks5.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-- src/common/socks5.h | 21 ++++++++- src/lib/torsocks.c | 79 +++++++++++++++++++++++++++++++--- 9 files changed, 328 insertions(+), 10 deletions(-)
diff --git a/doc/torsocks.8 b/doc/torsocks.8 index e91c356..eb4c7dd 100644 --- a/doc/torsocks.8 +++ b/doc/torsocks.8 @@ -84,6 +84,16 @@ Control whether or not the time is added to each logging line. (default: 1) .IP TORSOCKS_LOG_FILE_PATH If set, torsocks will log in the file set by this variable. (default: stderr)
+.PP +.IP TORSOCKS_USERNAME +Set the username for the SOCKS5 authentication method. Password MUST be set +also with the variable below. + +.PP +.IP TORSOCKS_PASSWORD +Set the password for the SOCKS5 authentication method. Username MUST be set +also with the variable above. + .SH KNOWN ISSUES
.SS DNS diff --git a/doc/torsocks.conf b/doc/torsocks.conf index f5b4924..7653818 100644 --- a/doc/torsocks.conf +++ b/doc/torsocks.conf @@ -17,3 +17,10 @@ TorPort 9050 # ever need to actually connect to. This is similar to the MapAddress feature # of the main tor daemon. OnionAddrRange 127.42.42.0/24 + +# SOCKS5 Username and Password. This is used to isolate the torsocks connection +# circuit from other streams in Tor. Use with option IsolateSOCKSAuth (on by +# default) in tor(1). TORSOCKS_USERNAME and TORSOCKS_PASSWORD environment +# variable overrides these options. +#SOCKS5Username <username> +#SOCKS5Password <password> diff --git a/doc/torsocks.conf.5 b/doc/torsocks.conf.5 index cdafad0..6cd600f 100644 --- a/doc/torsocks.conf.5 +++ b/doc/torsocks.conf.5 @@ -59,6 +59,18 @@ course, you should pick a block of addresses which you aren't going to ever need to actually connect to. This is similar to the MapAddress feature of the main tor daemon. (default: 127.42.42.0/24)
+.TP +.I SOCKS5Username +Username to use for SOCKS5 authentication method that makes the connections to +Tor to use a different circuit from other existing streams. If set, the +SOCKS5Password must be specified also. (Default: none). + +.TP +.I SOCKS5Password +Password to use for SOCKS5 authentication method that makes the connections to +Tor to use a different circuit from other existing streams. If set, the +SOCKS5Username must be specified also. (Default: none). + .SH EXAMPLE $ export TORSOCKS_CONF_FILE=$PWD/torsocks.conf $ torsocks ssh account@sshserver.com diff --git a/src/common/config-file.c b/src/common/config-file.c index 61a0f90..aaaa663 100644 --- a/src/common/config-file.c +++ b/src/common/config-file.c @@ -35,6 +35,76 @@ static const char *conf_toraddr_str = "TorAddress"; static const char *conf_torport_str = "TorPort"; static const char *conf_onion_str = "OnionAddrRange"; +static const char *conf_socks5_user_str = "SOCKS5Username"; +static const char *conf_socks5_pass_str = "SOCKS5Password"; + +/* + * Once this value reaches 2, it means both user and password for a SOCKS5 + * connection has been set thus use them./ + */ +static unsigned int both_socks5_pass_user_set; + +/* + * Set the SOCKS5 username to the given configuration. + * + * Return 0 on success else a negative value. + */ +int conf_file_set_socks5_user(const char *username, + struct configuration *config) +{ + int ret; + + assert(username); + assert(config); + + if (strlen(username) > sizeof(config->conf_file.socks5_username)) { + ERR("[config] Invalid %s value for %s", username, + conf_socks5_user_str); + ret = -EINVAL; + goto error; + } + + strncpy(config->conf_file.socks5_username, username, strlen(username)); + if (++both_socks5_pass_user_set == 2) { + config->socks5_use_auth = 1; + } + DBG("[config] %s set to %s", conf_socks5_user_str, username); + return 0; + +error: + return ret; +} + +/* + * Set the SOCKS5 password to the given configuration. + * + * Return 0 on success else a negative value. + */ +int conf_file_set_socks5_pass(const char *password, + struct configuration *config) +{ + int ret; + + assert(password); + assert(config); + + if (strlen(password) > sizeof(config->conf_file.socks5_password)) { + ERR("[config] Invalid %s value for %s", password, + conf_socks5_pass_str); + ret = -EINVAL; + goto error; + } + + strncpy(config->conf_file.socks5_password, password, strlen(password)); + if (++both_socks5_pass_user_set == 2) { + config->socks5_use_auth = 1; + } + DBG("[config] %s set to %s", conf_socks5_pass_str, password); + return 0; + +error: + return ret; +}
/* * Set the onion pool address range in the configuration object using the value @@ -203,6 +273,16 @@ static int parse_config_line(const char *line, struct configuration *config) if (ret < 0) { goto error; } + } else if (!strcmp(tokens[0], conf_socks5_user_str)) { + ret = conf_file_set_socks5_user(tokens[1], config); + if (ret < 0) { + goto error; + } + } else if (!strcmp(tokens[0], conf_socks5_pass_str)) { + ret = conf_file_set_socks5_pass(tokens[1], config); + if (ret < 0) { + goto error; + } } else { WARN("Config file contains unknown value: %s", line); } diff --git a/src/common/config-file.h b/src/common/config-file.h index 1fc23bf..04dbf42 100644 --- a/src/common/config-file.h +++ b/src/common/config-file.h @@ -23,6 +23,7 @@ #include <netinet/in.h>
#include "connection.h" +#include "socks5.h"
/* * Represent the values in a configuration file (torsocks.conf). Basically, @@ -42,6 +43,13 @@ struct config_file { */ in_addr_t onion_base; uint8_t onion_mask; + + /* + * Username and password for Tor stream isolation for the SOCKS5 connection + * method. + */ + char socks5_username[SOCKS5_USERNAME_LEN]; + char socks5_password[SOCKS5_PASSWORD_LEN]; };
/* @@ -57,9 +65,20 @@ struct configuration { * Socks5 address so basically where to connect to Tor. */ struct connection_addr socks5_addr; + + /* + * Indicate if we should use SOCKS5 authentication. If this value is set, + * both the username and password in the configuration file MUST be + * initialized to something of len > 0. + */ + unsigned int socks5_use_auth:1; };
int config_file_read(const char *filename, struct configuration *config); void config_file_destroy(struct config_file *conf); +int conf_file_set_socks5_pass(const char *password, + struct configuration *config); +int conf_file_set_socks5_user(const char *username, + struct configuration *config);
#endif /* CONFIG_FILE_H */ diff --git a/src/common/defaults.h b/src/common/defaults.h index b2ad372..fe35a6d 100644 --- a/src/common/defaults.h +++ b/src/common/defaults.h @@ -59,4 +59,8 @@ #define DEFAULT_ONION_ADDR_RANGE "127.42.42.0" #define DEFAULT_ONION_ADDR_MASK "24"
+/* Env. variable for SOCKS5 authentication */ +#define DEFAULT_SOCKS5_USER_ENV "TORSOCKS_USERNAME" +#define DEFAULT_SOCKS5_PASS_ENV "TORSOCKS_PASSWORD" + #endif /* TORSOCKS_DEFAULTS_H */ diff --git a/src/common/socks5.c b/src/common/socks5.c index bea583b..9610f51 100644 --- a/src/common/socks5.c +++ b/src/common/socks5.c @@ -43,7 +43,7 @@ static ssize_t recv_data(int fd, void *buf, size_t len) index = 0; do { read_len = recv(fd, buf + index, read_left, 0); - if (read_len < 0) { + if (read_len <= 0) { ret = -errno; if (errno == EINTR) { /* Try again after interruption. */ @@ -54,6 +54,10 @@ static ssize_t recv_data(int fd, void *buf, size_t len) ret = index; } continue; + } else if (read_len == 0) { + /* Orderly shutdown from Tor daemon. Stop. */ + ret = -1; + goto error; } else { PERROR("recv socks5 data"); goto error; @@ -181,7 +185,7 @@ error: * Return 0 on success or else a negative errno value. */ ATTR_HIDDEN -int socks5_send_method(struct connection *conn) +int socks5_send_method(struct connection *conn, uint8_t type) { int ret = 0; ssize_t ret_send; @@ -192,7 +196,7 @@ int socks5_send_method(struct connection *conn)
msg.ver = SOCKS5_VERSION; msg.nmethods = 0x01; - msg.methods = SOCKS5_NO_AUTH_METHOD; + msg.methods = type;
DBG("Socks5 sending method ver: %d, nmethods 0x%02x, methods 0x%02x", msg.ver, msg.nmethods, msg.methods); @@ -244,6 +248,102 @@ error: }
/* + * Send a username/password request to the given connection connected to the + * SOCKS5 Tor port. + * + * Return 0 on success else a negative errno value. + */ +ATTR_HIDDEN +int socks5_send_user_pass_request(struct connection *conn, + const char *user, const char *pass) +{ + int ret; + size_t data_len, user_len, pass_len; + ssize_t ret_send; + /* + * As stated in rfc1929, 3 bytes for ver, ulen, plen, the maximum len for + * the username and the password. + */ + unsigned char buffer[(3 * sizeof(uint8_t)) + + (SOCKS5_USERNAME_LEN + SOCKS5_PASSWORD_LEN)]; + + assert(conn); + assert(conn->fd >= 0); + assert(user); + assert(pass); + + user_len = strlen(user); + pass_len = strlen(pass); + /* Extra protection. */ + if (user_len > SOCKS5_USERNAME_LEN || + pass_len > SOCKS5_PASSWORD_LEN) { + ret = -EINVAL; + goto error; + } + + /* + * Ok so we have to setup the SOCKS5 payload since the strings are of + * variable len. + */ + buffer[0] = SOCKS5_USER_PASS_VER; + buffer[1] = user_len; + data_len = 2; + memcpy(buffer + data_len, user, user_len); + data_len += user_len; + memcpy(buffer + data_len, &pass_len, 1); + data_len += 1; + memcpy(buffer + data_len, pass, pass_len); + data_len += pass_len; + + ret_send = send_data(conn->fd, buffer, data_len); + if (ret_send < 0) { + ret = ret_send; + goto error; + } + /* Data was sent successfully. */ + ret = 0; + + DBG("Socks5 username %s and password %s sent successfully", user, pass); + +error: + return ret; +} + +/* + * Receive username/password reply for the given connection connected to the + * SOCKS5 Tor port. + * + * Return 0 on success else a negative errno value. + */ +ATTR_HIDDEN +int socks5_recv_user_pass_reply(struct connection *conn) +{ + int ret; + ssize_t ret_recv; + struct socks5_user_pass_reply msg; + + assert(conn); + assert(conn->fd >= 0); + + ret_recv = recv_data(conn->fd, &msg, sizeof(msg)); + if (ret_recv < 0) { + ret = ret_recv; + goto error; + } + + if (msg.status != SOCKS5_REPLY_SUCCESS) { + ret = -EINVAL; + goto error; + } + /* All good */ + ret = 0; + +error: + DBG("Socks5 username/password auth status %d", msg.status); + return ret; +} + +/* * Send a connect request to the SOCKS5 server using the given connection and * the destination address in it pointing to the destination that needs to be * reached through Tor. diff --git a/src/common/socks5.h b/src/common/socks5.h index e2ca52f..d589c14 100644 --- a/src/common/socks5.h +++ b/src/common/socks5.h @@ -32,8 +32,12 @@ * METHOD" [00] is supported and should be used. */ #define SOCKS5_NO_AUTH_METHOD 0x00 +#define SOCKS5_USER_PASS_METHOD 0x02 #define SOCKS5_NO_ACCPT_METHOD 0xFF
+/* SOCKS5 rfc1929 username and password authentication. */ +#define SOCKS5_USER_PASS_VER 0x01 + /* Request command. */ #define SOCKS5_CMD_CONNECT 0x01 #define SOCKS5_CMD_RESOLVE 0xF0 @@ -55,6 +59,10 @@ #define SOCKS5_REPLY_CMD_NOTSUP 0x07 #define SOCKS5_REPLY_ADR_NOTSUP 0x08
+/* As described in rfc1929. */ +#define SOCKS5_USERNAME_LEN 255 +#define SOCKS5_PASSWORD_LEN 255 + /* Request data structure for the method. */ struct socks5_method_req { uint8_t ver; @@ -118,12 +126,23 @@ struct socks5_reply { uint8_t atyp; };
+/* Username/password reply message. */ +struct socks5_user_pass_reply { + uint8_t ver; + uint8_t status; +}; + int socks5_connect(struct connection *conn);
/* Method messaging. */ -int socks5_send_method(struct connection *conn); +int socks5_send_method(struct connection *conn, uint8_t type); int socks5_recv_method(struct connection *conn);
+/* Username/Password request. */ +int socks5_send_user_pass_request(struct connection *conn, + const char *user, const char *pass); +int socks5_recv_user_pass_reply(struct connection *conn); + /* Connect request. */ int socks5_send_connect_request(struct connection *conn); int socks5_recv_connect_reply(struct connection *conn); diff --git a/src/lib/torsocks.c b/src/lib/torsocks.c index 9b3100c..b8f38fb 100644 --- a/src/lib/torsocks.c +++ b/src/lib/torsocks.c @@ -66,6 +66,45 @@ static void clean_exit(int status) }
/* + * Read SOCKS5 username and password environment variable and if found set them + * in the configuration. If we are setuid, return gracefully. + */ +static void read_user_pass_env(void) +{ + int ret; + const char *username, *password; + + if (is_suid) { + goto end; + } + + username = getenv(DEFAULT_SOCKS5_USER_ENV); + password = getenv(DEFAULT_SOCKS5_PASS_ENV); + if (!username && !password) { + goto end; + } + + ret = conf_file_set_socks5_user(username, &tsocks_config); + if (ret < 0) { + goto error; + } + + ret = conf_file_set_socks5_pass(password, &tsocks_config); + if (ret < 0) { + goto error; + } + +end: + return; +error: + /* + * Error while setting user/pass variable. Stop everything so the user can + * be notified and fix the issue. + */ + clean_exit(EXIT_FAILURE); +} + +/* * Initialize torsocks configuration from a given conf file or the default one. */ static void init_config(void) @@ -119,6 +158,9 @@ static void init_config(void) */ clean_exit(EXIT_FAILURE); } + + /* Handle SOCKS5 user/pass env. variables. */ + read_user_pass_env(); }
/* @@ -261,7 +303,8 @@ static void __attribute__((destructor)) tsocks_exit(void) * * Return 0 on success else a negative value. */ -static int setup_tor_connection(struct connection *conn) +static int setup_tor_connection(struct connection *conn, + uint8_t socks5_method) { int ret;
@@ -274,7 +317,7 @@ static int setup_tor_connection(struct connection *conn) goto error; }
- ret = socks5_send_method(conn); + ret = socks5_send_method(conn, socks5_method); if (ret < 0) { goto error; } @@ -330,7 +373,8 @@ error: /* * Initiate a SOCK5 connection to the Tor network using the given connection. * The socks5 API will use the torsocks configuration object to find the tor - * daemon. + * daemon. If a username/password has been set use that method for the SOCKS5 + * connection. * * Return 0 on success or else a negative value being the errno value that * needs to be sent back. @@ -338,16 +382,39 @@ error: int tsocks_connect_to_tor(struct connection *conn) { int ret; + uint8_t socks5_method;
assert(conn);
DBG("Connecting to the Tor network on fd %d", conn->fd);
- ret = setup_tor_connection(conn); + /* Is this configuration is set to use SOCKS5 authentication. */ + if (tsocks_config.socks5_use_auth) { + socks5_method = SOCKS5_USER_PASS_METHOD; + } else { + socks5_method = SOCKS5_NO_AUTH_METHOD; + } + + ret = setup_tor_connection(conn, socks5_method); if (ret < 0) { goto error; }
+ /* For the user/pass method, send the request before connect. */ + if (socks5_method == SOCKS5_USER_PASS_METHOD) { + ret = socks5_send_user_pass_request(conn, + tsocks_config.conf_file.socks5_username, + tsocks_config.conf_file.socks5_password); + if (ret < 0) { + goto error; + } + + ret = socks5_recv_user_pass_reply(conn); + if (ret < 0) { + goto error; + } + } + ret = socks5_send_connect_request(conn); if (ret < 0) { goto error; @@ -422,7 +489,7 @@ int tsocks_tor_resolve(int af, const char *hostname, void *ip_addr) goto error; }
- ret = setup_tor_connection(&conn); + ret = setup_tor_connection(&conn, SOCKS5_NO_AUTH_METHOD); if (ret < 0) { goto end_close; } @@ -470,7 +537,7 @@ int tsocks_tor_resolve_ptr(const char *addr, char **ip, int af) } conn.dest_addr.domain = CONNECTION_DOMAIN_INET;
- ret = setup_tor_connection(&conn); + ret = setup_tor_connection(&conn, SOCKS5_NO_AUTH_METHOD); if (ret < 0) { goto end_close; }
tor-commits@lists.torproject.org