commit c5f350d37f0a6c9ca81965bec71b5bb99e481bc2 Author: David Goulet dgoulet@ev0ke.net Date: Sat Jun 22 15:28:50 2013 -0400
Implement the gethostbyname(3) torsocks call
Signed-off-by: David Goulet dgoulet@ev0ke.net --- src/common/socks5.c | 112 ++++++++++++++++++++++++++++++++++++++++ src/common/socks5.h | 14 ++++- src/lib/torsocks.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib/torsocks.h | 27 ++++++++++ 4 files changed, 288 insertions(+), 6 deletions(-)
diff --git a/src/common/socks5.c b/src/common/socks5.c index eb3baef..4e218e3 100644 --- a/src/common/socks5.c +++ b/src/common/socks5.c @@ -17,6 +17,7 @@
#include <assert.h> #include <errno.h> +#include <inttypes.h>
#include <lib/torsocks.h>
@@ -362,3 +363,114 @@ int socks5_recv_connect_reply(struct connection *conn) error: return ret; } + +/* + * Send a SOCKS5 Tor resolve request for a given hostname using an already + * connected connection. + * + * Return 0 on success or else a negative value. + */ +int socks5_send_resolve_request(const char *hostname, struct connection *conn) +{ + int ret, ret_send; + /* Buffer to send won't go over a full TCP size. */ + char buffer[1500]; + size_t name_len, msg_len, data_len; + struct socks5_request msg; + struct socks5_request_resolve req; + + assert(hostname); + assert(conn); + assert(conn->fd >= 0); + + memset(buffer, 0, sizeof(buffer)); + msg_len = sizeof(msg); + + msg.ver = SOCKS5_VERSION; + msg.cmd = SOCKS5_CMD_RESOLVE; + /* Always zeroed. */ + msg.rsv = 0; + /* By default we use IPv4 address. */ + msg.atyp = SOCKS5_ATYP_DOMAIN; + + name_len = strlen(hostname); + if (name_len > sizeof(req.name)) { + ret = -EINVAL; + goto error; + } + + /* Setup resolve request. */ + req.len = name_len; + memcpy(req.name, hostname, name_len); + + /* Copy final buffer. */ + memcpy(buffer, &msg, msg_len); + memcpy(buffer + msg_len, &req, sizeof(req)); + data_len = msg_len + sizeof(req); + + 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] Resolve for %s sent successfully", hostname); + +error: + return ret; +} + +/* + * Receive a Tor resolve reply on the given connection. The ip address pointer + * is populated with the replied value or else untouched on error. + * + * Return 0 on success else a negative value. + */ +int socks5_recv_resolve_reply(struct connection *conn, uint32_t *ip_addr) +{ + int ret; + ssize_t ret_recv; + struct { + struct socks5_reply msg; + uint32_t addr; + } buffer; + + assert(conn); + assert(conn >= 0); + assert(ip_addr); + + ret_recv = recv_data(conn->fd, &buffer, sizeof(buffer)); + if (ret_recv < 0) { + ret = ret_recv; + goto error; + } + + if (buffer.msg.ver != SOCKS5_VERSION) { + ERR("Bad SOCKS5 version reply"); + ret = -ECONNABORTED; + goto error; + } + + if (buffer.msg.rep != SOCKS5_REPLY_SUCCESS) { + ERR("Unable to resolve. Status reply: %d", buffer.msg.rep); + ret = -ECONNABORTED; + goto error; + } + + if (buffer.msg.atyp == SOCKS5_ATYP_IPV4) { + *ip_addr = buffer.addr; + } else { + ERR("Bad SOCKS5 atyp reply %d", buffer.msg.atyp); + ret = -EINVAL; + goto error; + } + + /* Everything went well and ip_addr has been populated. */ + ret = 0; + DBG("[socks5] Resolve reply received: %" PRIu32, *ip_addr); + +error: + return ret; +} diff --git a/src/common/socks5.h b/src/common/socks5.h index 34935f4..c11c097 100644 --- a/src/common/socks5.h +++ b/src/common/socks5.h @@ -34,8 +34,10 @@ #define SOCKS5_NO_AUTH_METHOD 0x00 #define SOCKS5_NO_ACCPT_METHOD 0xFF
-/* Request to connect. */ +/* Request command. */ #define SOCKS5_CMD_CONNECT 0x01 +#define SOCKS5_CMD_RESOLVE 0xF0 +#define SOCKS5_CMD_RESOLVE_PTR 0xF1
/* Address type. */ #define SOCKS5_ATYP_IPV4 0x01 @@ -94,6 +96,12 @@ struct socks5_request_domain { uint16_t port; };
+/* Use for the Tor resolve command. */ +struct socks5_request_resolve { + uint8_t len; + char name[UINT8_MAX]; +}; + /* Non variable part of a reply. */ struct socks5_reply { uint8_t ver; @@ -112,4 +120,8 @@ int socks5_recv_method(struct connection *conn); int socks5_send_connect_request(struct connection *conn); int socks5_recv_connect_reply(struct connection *conn);
+/* Tor DNS resolve. */ +int socks5_send_resolve_request(const char *hostname, struct connection *conn); +int socks5_recv_resolve_reply(struct connection *conn, uint32_t *ip_addr); + #endif /* TORSOCKS_SOCKS_H */ diff --git a/src/lib/torsocks.c b/src/lib/torsocks.c index 0382d38..f6e3501 100644 --- a/src/lib/torsocks.c +++ b/src/lib/torsocks.c @@ -17,6 +17,7 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+#include <arpa/inet.h> #include <assert.h> #include <dlfcn.h> #include <stdlib.h> @@ -215,6 +216,38 @@ static void __attribute__((destructor)) tsocks_exit(void) }
/* + * Setup a Tor connection meaning initiating the initial SOCKS5 handshake. + * + * Return 0 on success else a negative value. + */ +static int setup_tor_connection(struct connection *conn) +{ + int ret; + + assert(conn); + + DBG("Setting up a connection to the Tor network on fd %d", conn->fd); + + ret = socks5_connect(conn); + if (ret < 0) { + goto error; + } + + ret = socks5_send_method(conn); + if (ret < 0) { + goto error; + } + + ret = socks5_recv_method(conn); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* * 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. @@ -230,31 +263,67 @@ static int connect_to_tor_network(struct connection *conn)
DBG("Connecting to the Tor network on fd %d", conn->fd);
- ret = socks5_connect(conn); + ret = setup_tor_connection(conn); if (ret < 0) { goto error; }
- ret = socks5_send_method(conn); + ret = socks5_send_connect_request(conn); if (ret < 0) { goto error; }
- ret = socks5_recv_method(conn); + ret = socks5_recv_connect_reply(conn); if (ret < 0) { goto error; }
- ret = socks5_send_connect_request(conn); +error: + return ret; +} + +/* + * Resolve a hostname through Tor and set the ip address in the given pointer. + * + * Return 0 on success else a negative value and the result addr is untouched. + */ +static int tor_resolve(const char *hostname, uint32_t *ip_addr) +{ + int ret; + struct connection conn; + + assert(hostname); + assert(ip_addr); + + DBG("Resolving %s on the Tor network", hostname); + + conn.fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (conn.fd < 0) { + PERROR("socket"); + ret = -errno; + goto error; + } + + ret = setup_tor_connection(&conn); if (ret < 0) { goto error; }
- ret = socks5_recv_connect_reply(conn); + ret = socks5_send_resolve_request(hostname, &conn); if (ret < 0) { goto error; }
+ ret = socks5_recv_resolve_reply(&conn, ip_addr); + if (ret < 0) { + goto error; + } + + ret = close(conn.fd); + if (ret < 0) { + PERROR("close"); + } + error: return ret; } @@ -347,3 +416,65 @@ LIBC_CONNECT_DECL TSOCKS_SYM_EXIT_NOT_FOUND); return tsocks_connect(LIBC_CONNECT_ARGS); } + +/* + * Torsocks call for gethostbyname(3). + * + * NOTE: This call is OBSOLETE in the glibc. + */ +LIBC_GETHOSTBYNAME_RET_TYPE tsocks_gethostbyname(LIBC_GETHOSTBYNAME_SIG) +{ + int ret; + uint32_t ip; + const char *ret_str; + + DBG("[gethostbyname] Requesting %s hostname", __name); + + if (!__name) { + h_errno = HOST_NOT_FOUND; + goto error; + } + + /* Resolve the given hostname through Tor. */ + ret = tor_resolve(__name, &ip); + if (ret < 0) { + goto error; + } + + /* Reset static host entry of tsocks. */ + memset(&tsocks_he, 0, sizeof(tsocks_he)); + memset(tsocks_he_addr_list, 0, sizeof(tsocks_he_addr_list)); + memset(tsocks_he_addr, 0, sizeof(tsocks_he_addr)); + + ret_str = inet_ntop(AF_INET, &ip, tsocks_he_addr, sizeof(tsocks_he_addr)); + if (!ret_str) { + PERROR("inet_ntop"); + h_errno = NO_ADDRESS; + goto error; + } + + tsocks_he_addr_list[0] = tsocks_he_addr; + tsocks_he_addr_list[1] = NULL; + + tsocks_he.h_name = (char *) __name; + tsocks_he.h_aliases = NULL; + tsocks_he.h_length = sizeof(in_addr_t); + tsocks_he.h_addrtype = AF_INET; + tsocks_he.h_addr_list = tsocks_he_addr_list; + + DBG("Hostname %s resolved to %s", __name, tsocks_he_addr); + + errno = 0; + return &tsocks_he; + +error: + return NULL; +} + +/* + * Libc hijacked symbol gethostbyname(3). + */ +LIBC_GETHOSTBYNAME_DECL +{ + return tsocks_gethostbyname(LIBC_GETHOSTBYNAME_ARGS); +} diff --git a/src/lib/torsocks.h b/src/lib/torsocks.h index 9ba2955..7581764 100644 --- a/src/lib/torsocks.h +++ b/src/lib/torsocks.h @@ -33,6 +33,7 @@
#if (defined(__linux__) || defined(__FreeBSD__) || defined(__darwin__))
+/* connect(2) */ #include <sys/types.h> #include <sys/socket.h>
@@ -44,6 +45,25 @@ #define LIBC_CONNECT_ARGS \ __sockfd, __addr, __addrlen
+/* gethostbyname(3) */ +#include <netdb.h> + +/* + * The man page specifies that this call can return a pointers to static data + * meaning that the caller needs to copy the returned data and not forced to + * use free(). So, we use static memory here to mimic the libc call and avoid + * memory leaks. This also void the need of hijacking freehostent(3). + */ +struct hostent tsocks_he; +char *tsocks_he_addr_list[2]; +char tsocks_he_addr[INET_ADDRSTRLEN]; + +#define LIBC_GETHOSTBYNAME_NAME gethostbyname +#define LIBC_GETHOSTBYNAME_NAME_STR XSTR(LIBC_GETHOSTBYNAME_NAME) +#define LIBC_GETHOSTBYNAME_RET_TYPE struct hostent * +#define LIBC_GETHOSTBYNAME_SIG const char *__name +#define LIBC_GETHOSTBYNAME_ARGS __name + #else #error "OS not supported." #endif /* __linux__ , __FreeBSD__, __darwin__ */ @@ -58,12 +78,19 @@ TSOCKS_LIBC_DECL(connect, LIBC_CONNECT_RET_TYPE, LIBC_CONNECT_SIG) #define LIBC_CONNECT_DECL \ LIBC_CONNECT_RET_TYPE LIBC_CONNECT_NAME(LIBC_CONNECT_SIG)
+/* gethostbyname(3) */ +TSOCKS_LIBC_DECL(gethostbyname, LIBC_GETHOSTBYNAME_RET_TYPE, + LIBC_GETHOSTBYNAME_SIG) +#define LIBC_GETHOSTBYNAME_DECL LIBC_GETHOSTBYNAME_RET_TYPE \ + LIBC_GETHOSTBYNAME_NAME(LIBC_GETHOSTBYNAME_SIG) + /* * Those are actions to do during the lookup process of libc symbols. For * instance the connect(2) syscall is essential to Torsocks so the function * call exits if not found. */ enum tsocks_sym_action { + TSOCKS_SYM_DO_NOTHING = 0, TSOCKS_SYM_EXIT_NOT_FOUND = 1, };