[tor-commits] [torsocks/master] Add SOCKS5 username/password authentication
dgoulet at torproject.org
dgoulet at torproject.org
Fri Apr 4 22:40:27 UTC 2014
commit 4f1a12e2893f609ac4dee54f21b55a6c4080aea5
Author: David Goulet <dgoulet at 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 at 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 at 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;
}
More information about the tor-commits
mailing list