Commit 75472c19 authored by Christopher Davis's avatar Christopher Davis Committed by Nick Mathewson
Browse files

Enable Tor to connect through SOCKS 4/5 proxies

Added a sanity check in config.c and a check in directory.c
directory_initiate_command_rend() to catch any direct connection attempts
when a socks proxy is configured.
parent aa6dc9cf
......@@ -292,6 +292,25 @@ HTTPS proxy authentication that Tor supports; feel free to submit a
patch if you want it to support others.
.LP
.TP
\fBSocks4Proxy\fR \fIhost\fR[:\fIport\fR]\fP
Tor will make all OR connections through the SOCKS 4 proxy at host:port
(or host:1080 if port is not specified).
.LP
.TP
\fBSocks5Proxy\fR \fIhost\fR[:\fIport\fR]\fP
Tor will make all OR connections through the SOCKS 5 proxy at host:port
(or host:1080 if port is not specified).
.LP
.TP
\fBSocks5ProxyUsername\fR \fIusername\fP
.LP
.TP
\fBSocks5ProxyPassword\fR \fIpassword\fP
If defined, authenticate to the SOCKS 5 server using username and password
in accordance to RFC 1929. Both username and password must be between 1 and 255
characters.
.LP
.TP
\fBKeepalivePeriod \fR\fINUM\fP
To keep firewalls from expiring connections, send a padding keepalive
cell every NUM seconds on open connections that are in use. If the
......
......@@ -1611,6 +1611,177 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
}
}
/** Inspect a reply from SOCKS server stored in <b>buf</b> according
* to <b>state</b>, removing the protocol data upon success. Return 0 on
* incomplete response, 1 on success and -1 on error, in which case
* <b>reason</b> is set to a descriptive message (free() when finished
* with it).
*
* As a special case, 2 is returned when user/pass is required
* during SOCKS5 handshake and user/pass is configured.
*/
int
fetch_from_buf_socks_client(buf_t *buf, int state, char **reason)
{
unsigned char *data;
size_t addrlen;
if (buf->datalen < 2)
return 0;
buf_pullup(buf, 128, 0);
tor_assert(buf->head && buf->head->datalen >= 2);
data = (unsigned char *) buf->head->data;
switch (state) {
case PROXY_SOCKS4_WANT_CONNECT_OK:
/* Wait for the complete response */
if (buf->head->datalen < 8)
return 0;
if (data[1] != 0x5a) {
switch (data[1]) {
case 0x5b:
*reason = tor_strdup("server rejected connection");
break;
case 0x5c:
*reason = tor_strdup("server cannot connect to identd "
"on this client");
break;
case 0x5d:
*reason = tor_strdup("user id does not match identd");
break;
default:
*reason = tor_strdup("invalid SOCKS 4 response code");
break;
}
return -1;
}
/* Success */
buf_remove_from_front(buf, 8);
return 1;
case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
/* we don't have any credentials */
if (data[1] != 0x00) {
*reason = tor_strdup("server doesn't support any of our "
"available authentication methods");
return -1;
}
log_info(LD_NET, "SOCKS 5 client: continuing without authentication");
buf_clear(buf);
return 1;
case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
/* we have a username and password. return 1 if we can proceed without
* providing authentication, or 2 otherwise. */
switch (data[1]) {
case 0x00:
log_info(LD_NET, "SOCKS 5 client: we have auth details but server "
"doesn't require authentication.");
buf_clear(buf);
return 1;
case 0x02:
log_info(LD_NET, "SOCKS 5 client: need authentication.");
buf_clear(buf);
return 2;
/* fall through */
}
*reason = tor_strdup("server doesn't support any of our available "
"authentication methods");
return -1;
case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
/* handle server reply to rfc1929 authentication */
if (data[1] != 0x00) {
*reason = tor_strdup("authentication failed");
return -1;
}
log_info(LD_NET, "SOCKS 5 client: authentication successful.");
buf_clear(buf);
return 1;
case PROXY_SOCKS5_WANT_CONNECT_OK:
/* response is variable length. BND.ADDR, etc, isn't needed
* (don't bother with buf_pullup()), but make sure to eat all
* the data used */
/* wait for address type field to arrive */
if (buf->datalen < 4)
return 0;
switch (data[3]) {
case 0x01: /* ip4 */
addrlen = 4;
break;
case 0x04: /* ip6 */
addrlen = 16;
break;
case 0x03: /* fqdn (can this happen here?) */
if (buf->datalen < 5)
return 0;
addrlen = 1 + data[4];
break;
default:
*reason = tor_strdup("invalid response to connect request");
return -1;
}
/* wait for address and port */
if (buf->datalen < 6 + addrlen)
return 0;
if (data[1] != 0x00) {
switch (data[1]) {
case 0x01:
*reason = tor_strdup("general SOCKS server failure");
break;
case 0x02:
*reason = tor_strdup("connection not allowed by ruleset");
break;
case 0x03:
*reason = tor_strdup("Network unreachable");
break;
case 0x04:
*reason = tor_strdup("Host unreachable");
break;
case 0x05:
*reason = tor_strdup("Connection refused");
break;
case 0x06:
*reason = tor_strdup("TTL expired");
break;
case 0x07:
*reason = tor_strdup("Command not supported");
break;
case 0x08:
*reason = tor_strdup("Address type not supported");
break;
default:
*reason = tor_strdup("unknown reason");
break;
}
return -1;
}
buf_remove_from_front(buf, 6 + addrlen);
return 1;
}
/* shouldn't get here... */
tor_assert(0);
return -1;
}
/** Return 1 iff buf looks more like it has an (obsolete) v0 controller
* command on it than any valid v1 controller command. */
int
......
......@@ -240,6 +240,10 @@ static config_var_t _option_vars[] = {
V(HttpProxyAuthenticator, STRING, NULL),
V(HttpsProxy, STRING, NULL),
V(HttpsProxyAuthenticator, STRING, NULL),
V(Socks4Proxy, STRING, NULL),
V(Socks5Proxy, STRING, NULL),
V(Socks5ProxyUsername, STRING, NULL),
V(Socks5ProxyPassword, STRING, NULL),
OBSOLETE("IgnoreVersion"),
V(KeepalivePeriod, INTERVAL, "5 minutes"),
VAR("Log", LINELIST, Logs, NULL),
......@@ -3409,6 +3413,42 @@ options_validate(or_options_t *old_options, or_options_t *options,
REJECT("HttpsProxyAuthenticator is too long (>= 48 chars).");
}
if (options->Socks4Proxy) { /* parse it now */
if (parse_addr_port(LOG_WARN, options->Socks4Proxy, NULL,
&options->Socks4ProxyAddr,
&options->Socks4ProxyPort) <0)
REJECT("Socks4Proxy failed to parse or resolve. Please fix.");
if (options->Socks4ProxyPort == 0) { /* give it a default */
options->Socks4ProxyPort = 1080;
}
}
if (options->Socks5Proxy) { /* parse it now */
if (parse_addr_port(LOG_WARN, options->Socks5Proxy, NULL,
&options->Socks5ProxyAddr,
&options->Socks5ProxyPort) <0)
REJECT("Socks5Proxy failed to parse or resolve. Please fix.");
if (options->Socks5ProxyPort == 0) { /* give it a default */
options->Socks5ProxyPort = 1080;
}
}
if (options->Socks5ProxyUsername) {
size_t len;
len = strlen(options->Socks5ProxyUsername);
if (len < 1 || len > 255)
REJECT("Socks5ProxyUsername must be between 1 and 255 characters.");
if (!options->Socks5ProxyPassword)
REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
len = strlen(options->Socks5ProxyPassword);
if (len < 1 || len > 255)
REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
} else if (options->Socks5ProxyPassword)
REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
if (options->HashedControlPassword) {
smartlist_t *sl = decode_hashed_passwords(options->HashedControlPassword);
if (!sl) {
......@@ -3557,6 +3597,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (options->PreferTunneledDirConns && !options->TunnelDirConns)
REJECT("Must set TunnelDirConns if PreferTunneledDirConns is set.");
if ((options->Socks4Proxy || options->Socks5Proxy) &&
!options->HttpProxy && !options->PreferTunneledDirConns)
REJECT("When Socks4Proxy or Socks5Proxy is configured, "
"PreferTunneledDirConns and TunnelDirConns must both be "
"set to 1, or HttpProxy must be configured.");
if (options->AutomapHostsSuffixes) {
SMARTLIST_FOREACH(options->AutomapHostsSuffixes, char *, suf,
{
......
......@@ -32,6 +32,10 @@ static int connection_process_inbuf(connection_t *conn, int package_partial);
static void client_check_address_changed(int sock);
static void set_constrained_socket_buffers(int sock, int size);
static const char *connection_proxy_state_to_string(int state);
static int connection_read_https_proxy_response(connection_t *conn);
static void connection_send_socks5_connect(connection_t *conn);
/** The last IPv4 address that our network interface seemed to have been
* binding to, in host order. We use this to detect when our IP changes. */
static uint32_t last_interface_ip = 0;
......@@ -92,8 +96,7 @@ conn_state_to_string(int type, int state)
case CONN_TYPE_OR:
switch (state) {
case OR_CONN_STATE_CONNECTING: return "connect()ing";
case OR_CONN_STATE_PROXY_FLUSHING: return "proxy flushing";
case OR_CONN_STATE_PROXY_READING: return "proxy reading";
case OR_CONN_STATE_PROXY_HANDSHAKING: return "handshaking (proxy)";
case OR_CONN_STATE_TLS_HANDSHAKING: return "handshaking (TLS)";
case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING:
return "renegotiating (TLS)";
......@@ -1289,6 +1292,353 @@ connection_connect(connection_t *conn, const char *address,
return inprogress ? 0 : 1;
}
/** Convert state number to string representation for logging purposes.
*/
static const char *
connection_proxy_state_to_string(int state)
{
static const char *unknown = "???";
static const char *states[] = {
"PROXY_NONE",
"PROXY_HTTPS_WANT_CONNECT_OK",
"PROXY_SOCKS4_WANT_CONNECT_OK",
"PROXY_SOCKS5_WANT_AUTH_METHOD_NONE",
"PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929",
"PROXY_SOCKS5_WANT_AUTH_RFC1929_OK",
"PROXY_SOCKS5_WANT_CONNECT_OK",
"PROXY_CONNECTED",
};
if (state < PROXY_NONE || state > PROXY_CONNECTED)
return unknown;
return states[state];
}
/** Write a proxy request of <b>type</b> (socks4, socks5, https) to conn
* for conn->addr:conn->port, authenticating with the auth details given
* in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies
* support authentication.
*
* Returns -1 if conn->addr is incompatible with the proxy protocol, and
* 0 otherwise.
*
* Use connection_read_proxy_handshake() to complete the handshake.
*/
int
connection_proxy_connect(connection_t *conn, int type)
{
or_options_t *options;
tor_assert(conn);
options = get_options();
switch (type) {
case PROXY_CONNECT: {
char buf[1024];
char *base64_authenticator=NULL;
const char *authenticator = options->HttpsProxyAuthenticator;
/* Send HTTP CONNECT and authentication (if available) in
* one request */
if (authenticator) {
base64_authenticator = alloc_http_authenticator(authenticator);
if (!base64_authenticator)
log_warn(LD_OR, "Encoding https authenticator failed");
}
if (base64_authenticator) {
tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n"
"Proxy-Authorization: Basic %s\r\n\r\n",
fmt_addr(&conn->addr),
conn->port, base64_authenticator);
tor_free(base64_authenticator);
} else {
tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n",
fmt_addr(&conn->addr), conn->port);
}
connection_write_to_buf(buf, strlen(buf), conn);
conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK;
break;
}
case PROXY_SOCKS4: {
unsigned char buf[9];
uint16_t portn;
uint32_t ip4addr;
/* Send a SOCKS4 connect request with empty user id */
if (tor_addr_family(&conn->addr) != AF_INET) {
log_warn(LD_NET, "SOCKS4 client is incompatible with with IPv6");
return -1;
}
ip4addr = tor_addr_to_ipv4n(&conn->addr);
portn = htons(conn->port);
buf[0] = 4; /* version */
buf[1] = SOCKS_COMMAND_CONNECT; /* command */
memcpy(buf + 2, &portn, 2); /* port */
memcpy(buf + 4, &ip4addr, 4); /* addr */
buf[8] = 0; /* userid (empty) */
connection_write_to_buf((char *)buf, sizeof buf, conn);
conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK;
break;
}
case PROXY_SOCKS5: {
unsigned char buf[4]; /* fields: vers, num methods, method list */
/* Send a SOCKS5 greeting (connect request must wait) */
buf[0] = 5; /* version */
/* number of auth methods */
if (options->Socks5ProxyUsername) {
buf[1] = 2;
buf[2] = 0x00; /* no authentication */
buf[3] = 0x02; /* rfc1929 Username/Passwd auth */
conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929;
} else {
buf[1] = 1;
buf[2] = 0x00; /* no authentication */
conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE;
}
connection_write_to_buf((char *)buf, 2 + buf[1], conn);
break;
}
default:
log_err(LD_BUG, "Invalid proxy protocol, %d", type);
tor_fragile_assert();
return -1;
}
log_debug(LD_NET, "set state %s",
connection_proxy_state_to_string(conn->proxy_state));
return 0;
}
/** Read conn's inbuf. If the http response from the proxy is all
* here, make sure it's good news, then return 1. If it's bad news,
* return -1. Else return 0 and hope for better luck next time.
*/
static int
connection_read_https_proxy_response(connection_t *conn)
{
char *headers;
char *reason=NULL;
int status_code;
time_t date_header;
switch (fetch_from_buf_http(conn->inbuf,
&headers, MAX_HEADERS_SIZE,
NULL, NULL, 10000, 0)) {
case -1: /* overflow */
log_warn(LD_PROTOCOL,
"Your https proxy sent back an oversized response. Closing.");
return -1;
case 0:
log_info(LD_NET,"https proxy response not all here yet. Waiting.");
return 0;
/* case 1, fall through */
}
if (parse_http_response(headers, &status_code, &date_header,
NULL, &reason) < 0) {
log_warn(LD_NET,
"Unparseable headers from proxy (connecting to '%s'). Closing.",
conn->address);
tor_free(headers);
return -1;
}
if (!reason) reason = tor_strdup("[no reason given]");
if (status_code == 200) {
log_info(LD_NET,
"HTTPS connect to '%s' successful! (200 %s) Starting TLS.",
conn->address, escaped(reason));
tor_free(reason);
return 1;
}
/* else, bad news on the status code */
log_warn(LD_NET,
"The https proxy sent back an unexpected status code %d (%s). "
"Closing.",
status_code, escaped(reason));
tor_free(reason);
return -1;
}
/** Send SOCKS5 CONNECT command to <b>conn</b>, copying <b>conn->addr</b>
* and <b>conn->port</b> into the request.
*/
static void
connection_send_socks5_connect(connection_t *conn)
{
unsigned char buf[1024];
size_t reqsize = 6;
uint16_t port = htons(conn->port);
buf[0] = 5; /* version */
buf[1] = SOCKS_COMMAND_CONNECT; /* command */
buf[2] = 0; /* reserved */
if (tor_addr_family(&conn->addr) == AF_INET) {
uint32_t addr = tor_addr_to_ipv4n(&conn->addr);
buf[3] = 1;
reqsize += 4;
memcpy(buf + 4, &addr, 4);
memcpy(buf + 8, &port, 2);
} else { /* AF_INET6 */
buf[3] = 4;
reqsize += 16;
memcpy(buf + 4, tor_addr_to_in6(&conn->addr), 16);
memcpy(buf + 20, &port, 2);
}
connection_write_to_buf((char *)buf, reqsize, conn);
conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK;
}
/** Call this from connection_*_process_inbuf() to advance the proxy
* handshake.
*
* No matter what proxy protocol is used, if this function returns 1, the
* handshake is complete, and the data remaining on inbuf may contain the
* start of the communication with the requested server.
*
* Returns 0 if the current buffer contains an incomplete response, and -1
* on error.
*/
int
connection_read_proxy_handshake(connection_t *conn)
{
int ret = 0;
char *reason = NULL;
log_debug(LD_NET, "enter state %s",
connection_proxy_state_to_string(conn->proxy_state));
switch (conn->proxy_state) {
case PROXY_HTTPS_WANT_CONNECT_OK:
ret = connection_read_https_proxy_response(conn);
if (ret == 1)
conn->proxy_state = PROXY_CONNECTED;
break;
case PROXY_SOCKS4_WANT_CONNECT_OK:
ret = fetch_from_buf_socks_client(conn->inbuf,
conn->proxy_state,
&reason);
if (ret == 1)
conn->proxy_state = PROXY_CONNECTED;
break;
case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
ret = fetch_from_buf_socks_client(conn->inbuf,
conn->proxy_state,
&reason);
/* no auth needed, do connect */
if (ret == 1) {
connection_send_socks5_connect(conn);
ret = 0;
}
break;
case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
ret = fetch_from_buf_socks_client(conn->inbuf,
conn->proxy_state,
&reason);
/* send auth if needed, otherwise do connect */
if (ret == 1) {
connection_send_socks5_connect(conn);
ret = 0;
} else if (ret == 2) {
unsigned char buf[1024];
size_t reqsize, usize, psize;
const char *user, *pass;
user = get_options()->Socks5ProxyUsername;
pass = get_options()->Socks5ProxyPassword;
tor_assert(user && pass);
/* XXX len of user and pass must be <= 255 !!! */
usize = strlen(user);
psize = strlen(pass);
tor_assert(usize <= 255 && psize <= 255);
reqsize = 3 + usize + psize;
buf[0] = 1; /* negotiation version */
buf[1] = usize;
memcpy(buf + 2, user, usize);
buf[2 + usize] = psize;
memcpy(buf + 3 + usize, pass, psize);
connection_write_to_buf((char *)buf, reqsize, conn);
conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK;
ret = 0;
}
break;
case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
ret = fetch_from_buf_socks_client(conn->inbuf,
conn->proxy_state,
&reason);
/* send the connect request */
if (ret == 1) {
connection_send_socks5_connect(conn);
ret = 0;
}
break;
case PROXY_SOCKS5_WANT_CONNECT_OK:
ret = fetch_from_buf_socks_client(conn->inbuf,
conn->proxy_state,
&reason);
if (ret == 1)
conn->proxy_state = PROXY_CONNECTED;
break;
default:
log_err(LD_BUG, "Invalid proxy_state for reading, %d",
conn->proxy_state);
tor_fragile_assert();
ret = -1;