[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