From 213658f117f88eaeb21ffd61451155f451f67604 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 22 Sep 2006 00:43:55 +0000 Subject: [PATCH] r8894@Kushana: nickm | 2006-09-21 18:30:42 -0400 Specify and implement SOCKS5 interface for reverse hostname lookup. svn:r8451 --- ChangeLog | 2 ++ contrib/tor-resolve.py | 65 +++++++++++++++++++++++++++------------- doc/TODO | 7 ++++- doc/socks-extensions.txt | 6 ++++ src/or/buffers.c | 16 +++++++--- src/or/connection_edge.c | 61 +++++++++++++++++++++++++++++-------- src/or/or.h | 1 + 7 files changed, 120 insertions(+), 38 deletions(-) diff --git a/ChangeLog b/ChangeLog index 34597857f7..db03be8fdf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ Changes in version 0.1.2.2-alpha - 2006-??-?? previously never implemented. This is only supported by eventdns; servers now announce in their descriptors whether they support eventdns. + - Specify and implement client-side SOCKS5 interface for reverse DNS + lookups; see doc/socks-extensions.txt for full information. o Minor features: - Check for name servers (like Earthlink's) that hijack failing DNS diff --git a/contrib/tor-resolve.py b/contrib/tor-resolve.py index dd44255bc1..3557c2aa96 100755 --- a/contrib/tor-resolve.py +++ b/contrib/tor-resolve.py @@ -32,15 +32,14 @@ def socks5Hello(): def socks5ParseHello(response): if response != "\x05\x00": raise ValueError("Bizarre socks5 response") -def socks5ResolveRequest(hostname): +def socks5ResolveRequest(hostname, atype=0x03, command=0xF0): version = 5 - command = 0xF0 rsv = 0 port = 0 - atype = 0x03 reqheader = struct.pack("!BBBBB",version, command, rsv, atype, len(hostname)) portstr = struct.pack("!H",port) return "%s%s%s"%(reqheader,hostname,portstr) + def socks5ParseResponse(r): if len(r)<8: return None @@ -49,18 +48,27 @@ def socks5ParseResponse(r): assert rsv==0 if reply != 0x00: return "ERROR",reply - assert atype in (0x01,0x04) - expected_len = 4 + ({1:4,4:16}[atype]) + 2 - if len(r) < expected_len: - return None - elif len(r) > expected_len: - raise ValueError("Overlong socks5 reply!") - addr = r[4:-2] - if atype == 0x01: - return "%d.%d.%d.%d"%tuple(map(ord,addr)) + assert atype in (0x01,0x03,0x04) + if atype != 0x03: + expected_len = 4 + ({1:4,4:16}[atype]) + 2 + if len(r) < expected_len: + return None + elif len(r) > expected_len: + raise ValueError("Overlong socks5 reply!") + addr = r[4:-2] + if atype == 0x01: + return "%d.%d.%d.%d"%tuple(map(ord,addr)) + else: + # not really the right way to format IPv6 + return "IPv6: %s"%(":".join([hex(ord(c)) for c in addr])) else: - # not really the right way to format IPv6 - return "IPv6: %s"%(":".join([hex(ord(c)) for c in addr])) + nul = r.index('\0',4) + return r[4:nul] + +def socks5ResolvePTRRequest(hostname): + return socks5ResolveRequest(socket.inet_aton(hostname), + atype=1, command = 0xF1) + def parseHostAndPort(h): host, port = "localhost", 9050 @@ -80,14 +88,18 @@ def parseHostAndPort(h): return host, port -def resolve(hostname, sockshost, socksport, socksver=4): +def resolve(hostname, sockshost, socksport, socksver=4, reverse=0): assert socksver in (4,5) if socksver == 4: fmt = socks4AResolveRequest parse = socks4AParseResponse - else: + elif not reverse: fmt = socks5ResolveRequest parse = socks5ParseResponse + else: + fmt = socks5ResolvePTRRequest + parse = socks5ParseResponse + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sockshost,socksport)) if socksver == 5: @@ -114,14 +126,25 @@ if __name__ == '__main__': print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]" sys.exit(0) socksver = 4 - if sys.argv[1] in ("-4", "-5"): - socksver = int(sys.argv[1][1]) - del sys.argv[1] - if len(sys.argv) == 4: + reverse = 0 + while sys.argv[1] == '-': + if sys.argv[1] in ("-4", "-5"): + socksver = int(sys.argv[1][1]) + del sys.argv[1] + elif sys.argv[1] == '-x': + reverse = 1 + del sys.argv[1] + elif sys.argv[1] == '--': + break + + if len(sys.argv) >= 4: print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]" sys.exit(0) if len(sys.argv) == 3: sh,sp = parseHostAndPort(sys.argv[2]) else: sh,sp = parseHostAndPort("") - resolve(sys.argv[1], sh, sp, socksver) + + if reverse and socksver == 4: + socksver = 5 + resolve(sys.argv[1], sh, sp, socksver, reverse) diff --git a/doc/TODO b/doc/TODO index f5c8f0dc82..d82da2c3fb 100644 --- a/doc/TODO +++ b/doc/TODO @@ -109,7 +109,12 @@ d - Special-case localhost? o Connect to resolve cells, server-side. o Add element to routerinfo to note routers that aren't using eventdns, so we can avoid sending them reverse DNS etc. - - Add client-side interface + . Add client-side interface + o SOCKS interface: specify + o SOCKS interface: implement + - Cache answers client-side + o Add to Tor-resolve.py + - Add to tor-resolve - Performance improvements diff --git a/doc/socks-extensions.txt b/doc/socks-extensions.txt index 7022af14fc..8040a8b03f 100644 --- a/doc/socks-extensions.txt +++ b/doc/socks-extensions.txt @@ -46,6 +46,12 @@ Tor's extensions to the SOCKS protocol (We support RESOLVE in SOCKS4 too, even though it is unnecessary.) + For SOCKS5 only, we support reverse resolution with a new command value, + "RESOLVE_PTR". In response to a "RESOLVE_PTR" SOCKS5 command with an IPv4 + address as its target, Tor attempts to find the canonical hostname for that + IPv4 record, and returns it in the "server bound address" portion of the + reply. (This was not supported before Tor 0.1.2.2-alpha) + 3. HTTP-resistance Tor checks the first byte of each SOCKS request to see whether it looks diff --git a/src/or/buffers.c b/src/or/buffers.c index b6e775da86..290a81c8b2 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -974,8 +974,9 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return 0; /* not yet */ req->command = (unsigned char) *(buf->cur+1); if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. */ + req->command != SOCKS_COMMAND_RESOLVE && + req->command != SOCKS_COMMAND_RESOLVE_PTR) { + /* not a connect or resolve or a resolve_ptr? we don't support it. */ log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", req->command); return -1; @@ -999,7 +1000,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, strlcpy(req->address,tmpbuf,sizeof(req->address)); req->port = ntohs(*(uint16_t*)(buf->cur+8)); buf_remove_from_front(buf, 10); - if (!addressmap_have_mapping(req->address) && + if (req->command != SOCKS_COMMAND_RESOLVE_PTR && + !addressmap_have_mapping(req->address) && !have_warned_about_unsafe_socks) { log_warn(LD_APP, "Your application (using socks5 on port %d) is giving " @@ -1025,6 +1027,11 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); return -1; } + if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { + log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " + "hostname type. Rejecting."); + return -1; + } memcpy(req->address,buf->cur+5,len); req->address[len] = 0; req->port = ntohs(get_uint16(buf->cur+5+len)); @@ -1059,7 +1066,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, req->command = (unsigned char) *(buf->cur+1); if (req->command != SOCKS_COMMAND_CONNECT && req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. */ + /* not a connect or resolve? we don't support it. (No resolve_ptr with + * socks4.) */ log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", req->command); return -1; diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 3ed8aa9f28..8d6c9273ba 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1120,7 +1120,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return -1; } - if (socks->command == SOCKS_COMMAND_RESOLVE) { + if (socks->command == SOCKS_COMMAND_RESOLVE) { // resolve_ptr XXXX NM uint32_t answer; struct in_addr in; /* Reply to resolves immediately if we can. */ @@ -1181,7 +1181,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, rend_cache_entry_t *entry; int r; - if (socks->command == SOCKS_COMMAND_RESOLVE) { + if (socks->command != SOCKS_COMMAND_CONNECT) { /* if it's a resolve request, fail it right now, rather than * building all the circuits and then realizing it won't work. */ log_warn(LD_APP, @@ -1524,13 +1524,17 @@ int connection_ap_handshake_send_resolve(edge_connection_t *ap_conn, origin_circuit_t *circ) { - int payload_len; + int payload_len, command; const char *string_addr; + char inaddr_buf[32]; + + command = ap_conn->socks_request->command; tor_assert(ap_conn->_base.type == CONN_TYPE_AP); tor_assert(ap_conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); tor_assert(ap_conn->socks_request); - tor_assert(ap_conn->socks_request->command == SOCKS_COMMAND_RESOLVE); + tor_assert(command == SOCKS_COMMAND_RESOLVE || + command == SOCKS_COMMAND_RESOLVE_PTR); tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL); ap_conn->stream_id = get_unique_stream_id_by_circ(circ); @@ -1540,9 +1544,27 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn, return -1; } - string_addr = ap_conn->socks_request->address; - payload_len = strlen(string_addr)+1; - tor_assert(payload_len <= RELAY_PAYLOAD_SIZE); + if (command == SOCKS_COMMAND_RESOLVE) { + string_addr = ap_conn->socks_request->address; + payload_len = strlen(string_addr)+1; + tor_assert(payload_len <= RELAY_PAYLOAD_SIZE); + } else { + struct in_addr in; + uint32_t a; + if (tor_inet_aton(ap_conn->socks_request->address, &in) == 0) { + connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL); + return -1; + } + a = ntohl(in.s_addr); + tor_snprintf(inaddr_buf, sizeof(inaddr_buf), "%d.%d.%d.%d.in-addr.arpa", + (int)(uint8_t)((a )&0xff), + (int)(uint8_t)((a>>8 )&0xff), + (int)(uint8_t)((a>>16)&0xff), + (int)(uint8_t)((a>>24)&0xff)); + string_addr = inaddr_buf; + payload_len = strlen(inaddr_buf)+1; + tor_assert(payload_len <= RELAY_PAYLOAD_SIZE); + } log_debug(LD_APP, "Sending relay cell to begin stream %d.", ap_conn->stream_id); @@ -1625,7 +1647,7 @@ connection_ap_make_bridge(char *address, uint16_t port) } /** Send an answer to an AP connection that has requested a DNS lookup - * via SOCKS. The type should be one of RESOLVED_TYPE_(IPV4|IPV6) or + * via SOCKS. The type should be one of RESOLVED_TYPE_(IPV4|IPV6|HOSTNAME) or * -1 for unreachable; the answer should be in the format specified * in the socks extensions document. **/ @@ -1636,7 +1658,7 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn, const char *answer, int ttl) { - char buf[256]; + char buf[384]; size_t replylen; if (answer_type == RESOLVED_TYPE_IPV4) { @@ -1675,6 +1697,14 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn, memcpy(buf+4, answer, 16); /* address */ set_uint16(buf+20, 0); /* port == 0. */ replylen = 22; + } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) { + buf[1] = SOCKS5_SUCCEEDED; + buf[2] = 0; /* reserved */ + buf[3] = 0x03; /* Domainname address type */ + memcpy(buf+4, answer, answer_len); /* address */ + buf[4+answer_len] = '\0'; + set_uint16(buf+4+answer_len+1, 0); /* port == 0. */ + replylen = 4+answer_len+1+2; } else { buf[1] = SOCKS5_HOST_UNREACHABLE; memset(buf+2, 0, 8); @@ -1699,7 +1729,8 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn, void connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, size_t replylen, - socks5_reply_status_t status) { + socks5_reply_status_t status) +{ char buf[256]; tor_assert(conn->socks_request); /* make sure it's an AP stream */ @@ -2072,7 +2103,7 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) } } - if (conn->socks_request->command != SOCKS_COMMAND_RESOLVE) { + if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) { struct in_addr in; uint32_t addr = 0; addr_policy_result_t r; @@ -2082,7 +2113,13 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) exit->exit_policy); if (r == ADDR_POLICY_REJECTED || r == ADDR_POLICY_PROBABLY_REJECTED) return 0; - } else { + } else { /* Some kind of a resolve. */ + + /* Can't support reverse lookups without eventdns. */ + if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR && + exit->has_old_dnsworkers) + return 0; + /* Don't send DNS requests to non-exit servers by default. */ if (policy_is_reject_star(exit->exit_policy)) return 0; diff --git a/src/or/or.h b/src/or/or.h index 217cb260f0..f8852b2e13 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1612,6 +1612,7 @@ typedef struct { #define MAX_SOCKS_ADDR_LEN 256 #define SOCKS_COMMAND_CONNECT 0x01 #define SOCKS_COMMAND_RESOLVE 0xF0 +#define SOCKS_COMMAND_RESOLVE_PTR 0xF1 /** State of a SOCKS request from a user to an OP */ struct socks_request_t { char socks_version; /**< Which version of SOCKS did the client use? */