diff --git a/ChangeLog b/ChangeLog index a4757feb7c..aa0c8a6886 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,5 @@ Changes in version 0.1.2.2-alpha - 2006-??-?? o Major features: - - Add server-side support for "reverse" DNS lookups (using PTR records so clients can determine the canonical hostname for a given IPv4 address). This has been specified for a long time, but was @@ -8,6 +7,11 @@ Changes in version 0.1.2.2-alpha - 2006-??-?? servers now announce in their descriptors whether they support eventdns. + o Minor features: + - Check for name servers (like Earthlink's) that hijack failing DNS + requests and replace the 'no such server' answer with a "helpful" + redirect to an advertising-driven search portal. [Resolves bug 330.] + o Security Fixes, minor - If a client asked for a server by name, and we didn't have a descriptor for a named server with that name, we might return an old diff --git a/doc/TODO b/doc/TODO index 40dd1dd454..4ade8fadda 100644 --- a/doc/TODO +++ b/doc/TODO @@ -87,9 +87,9 @@ d - Write limiting; separate token bucket for write - Write-limit directory responses (need to research) N - DNS improvements - - Option to deal with broken DNS of the "ggoogle.com? Ah, you meant + o Option to deal with broken DNS of the "ggoogle.com? Ah, you meant ads.me.com!" variety. -d - Autodetect whether DNS is broken in this way. + o Autodetect whether DNS is broken in this way. - Don't ask reject *:* nodes for DNS unless client wants you to. . Asynchronous DNS o Document and rename SearchDomains, ResolvConf options diff --git a/doc/tor.1.in b/doc/tor.1.in index a162137c62..d73f1436a9 100644 --- a/doc/tor.1.in +++ b/doc/tor.1.in @@ -647,6 +647,15 @@ domain. For example, if this system is configured to believe it is in connected to "www.example.com". This option only effects name lookup for addresses requested by clients. (Defaults to "0".) +.LP +.TP +\fBServerDNSDetectHijacking \fR\fB0\fR|\fB1\fR\fP +When this option is set to 1, we will test periodically to determine whether +our local nameservers have been configured to hijack failing DNS requests +(usually to an advertising site). If they are, we will attempt to correct +this. This option only effects name lookup for addresses requested by +clients; and only takes effect if Tor was built with eventdns support. +(Defaults to "1".) .SH DIRECTORY SERVER OPTIONS .PP diff --git a/src/or/config.c b/src/or/config.c index aaee949384..b30de39258 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -222,6 +222,7 @@ static config_var_t _option_vars[] = { VAR("RunTesting", BOOL, RunTesting, "0"), VAR("SafeLogging", BOOL, SafeLogging, "1"), VAR("SafeSocks", BOOL, SafeSocks, "0"), + VAR("ServerDNSDetectHijacking",BOOL, ServerDNSDetectHijacking,"1"), VAR("ServerDNSResolvConfFile", STRING, ServerDNSResolvConfFile, NULL), VAR("ServerDNSSearchDomains", BOOL, ServerDNSSearchDomains, "0"), VAR("ShutdownWaitLength", INTERVAL, ShutdownWaitLength, "30 seconds"), diff --git a/src/or/dns.c b/src/or/dns.c index 29255ad822..52cbb3daf0 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -122,6 +122,7 @@ static int spawn_dnsworker(void); static int spawn_enough_dnsworkers(void); #else static int configure_nameservers(int force); +static int answer_is_wildcarded(const char *ip); #endif #ifdef DEBUG_DNS_CACHE static void _assert_cache_ok(void); @@ -1330,6 +1331,11 @@ spawn_enough_dnsworkers(void) return 0; } + +void +dns_launch_wildcard_checks(void) +{ +} #else /* !USE_EVENTDNS */ /** Eventdns helper: return true iff the eventdns result err is @@ -1470,9 +1476,19 @@ eventdns_callback(int result, char type, int count, int ttl, void *addresses, status = DNS_RESOLVE_SUCCEEDED; tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); escaped_address = esc_for_log(string_address); - log_debug(LD_EXIT, "eventdns said that %s resolves to %s", - safe_str(escaped_address), - escaped_safe_str(answer_buf)); + + if (answer_is_wildcarded(answer_buf)) { + log_debug(LD_EXIT, "eventdns said that %s resolves to ISP-hijacked " + "address %s; treating as a failure.", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + addr = 0; + status = DNS_RESOLVE_FAILED_PERMANENT; + } else { + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + } tor_free(escaped_address); } else if (type == DNS_PTR && count) { char *escaped_address; @@ -1546,6 +1562,94 @@ launch_resolve(edge_connection_t *exitconn) } return r ? -1 : 0; } + +/** If present, a list of dotted-quad IP addresses that our nameserver + * apparently wants to return in response to requests for nonexistent domains. + */ +static smartlist_t *dns_wildcard_list = NULL; + +/** Callback function when we get an answer (possibly failing) for a request + * for a (hopefully) nonexistent domain. */ +static void +eventdns_wildcard_check_callback(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + static int notice_given = 0; + if (result == DNS_ERR_NONE && type == DNS_IPv4_A && count) { + uint32_t *addrs = addresses; + int i; + char *string_address = arg; + if (!dns_wildcard_list) dns_wildcard_list = smartlist_create(); + for (i = 0; i < count; ++i) { + char answer_buf[INET_NTOA_BUF_LEN+1]; + struct in_addr in; + in.s_addr = addrs[i]; + tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); + if (!smartlist_string_isin(dns_wildcard_list, answer_buf)) + smartlist_add(dns_wildcard_list, tor_strdup(answer_buf)); + } + log(notice_given ? LOG_INFO : LOG_NOTICE, LD_EXIT, + "Your DNS provider gave an answer for \"%s\", which " + "is not supposed to exist. Apparently they are hijacking " + "DNS failures. Trying to correct for this. We've noticed %d bad " + "addresses so far.", string_address, smartlist_len(dns_wildcard_list)); + notice_given = 1; + } + tor_free(arg); +} + +/** Launch a single request for a nonexistent hostname consisting of + * len random (plausible) characters followed by suffix */ +static void +launch_wildcard_check(int len, const char *suffix) +{ + char random_bytes[16], name[64], *addr; + size_t n = (len+1)/2; + int r; + + tor_assert(n <= sizeof(random_bytes)); + + if (crypto_rand(random_bytes, n) < 0) + return; + base32_encode(name, sizeof(name), random_bytes, n); + name[len] = '\0'; + strlcat(name, suffix, sizeof(name)); + + addr = tor_strdup(name); + r = eventdns_resolve_ipv4(name, DNS_QUERY_NO_SEARCH, + eventdns_wildcard_check_callback, addr); + if (r) + tor_free(addr); +} + +#define N_WILDCARD_CHECKS 2 + +/** Launch DNS requests for a few nonexistent hostnames, and see if we can + * catch our nameserver trying to hijack them and map them to a stupid "I + * couldn't find ggoogle.com but maybe you'd like to buy these lovely + * encyclopedias" page. */ +void +dns_launch_wildcard_checks(void) +{ + int i; + if (!get_options()->ServerDNSDetectHijacking) + return; + log_info(LD_EXIT, "Launching checks to see whether our nameservers like " + "to hijack DNS failures."); + for (i = 0; i < N_WILDCARD_CHECKS; ++i) { + /* RFC2606 reserves these */ + launch_wildcard_check(8, ".invalid"); + launch_wildcard_check(8, ".test"); + } +} + +/** Return true iff we have noticed that the dotted-quad ip has been + * returned in response to a request for a nonexistent hostname. */ +static int +answer_is_wildcarded(const char *ip) +{ + return dns_wildcard_list && smartlist_string_isin(dns_wildcard_list, ip); +} #endif /* USE_EVENTDNS */ /** Exit with an assertion if resolve is corrupt. */ diff --git a/src/or/main.c b/src/or/main.c index 984563f2ef..c40d466e61 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -722,6 +722,7 @@ run_scheduled_events(time_t now) static time_t time_to_try_getting_descriptors = 0; static time_t time_to_reset_descriptor_failures = 0; static time_t time_to_add_entropy = 0; + static time_t time_to_check_for_wildcarded_dns = 0; or_options_t *options = get_options(); int i; int have_dir_info; @@ -923,6 +924,20 @@ run_scheduled_events(time_t now) * we'll pass it to poll/select and bad things will happen. */ close_closeable_connections(); + + /** 9. and if we're a server, check whether our DNS is telling stories to + * us. */ + if (server_mode(options) && time_to_check_for_wildcarded_dns < now) { + if (!time_to_check_for_wildcarded_dns) { + time_to_check_for_wildcarded_dns = now + 60 + crypto_rand_int(120); + } else { + dns_launch_wildcard_checks(); + time_to_check_for_wildcarded_dns = now + 12*3600 + + crypto_rand_int(12*3600); + } + } + + } static struct event *timeout_event = NULL; diff --git a/src/or/or.h b/src/or/or.h index e1c63253e7..3d04685288 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1574,6 +1574,8 @@ typedef struct { int ServerDNSSearchDomains; /**< Boolean: If set, we don't force exit * addresses to be FQDNs, but rather search for them in * the local domains. */ + int ServerDNSDetectHijacking; /**< Boolean: If true, check for DNS failure + * hijacking */ char *ServerDNSResolvConfFile; /**< If provided, we configure our internal * resolver from the file here rather than from * /etc/resolv.conf (unix) or the registry (windows) */ @@ -2158,6 +2160,7 @@ void assert_connection_edge_not_dns_pending(edge_connection_t *conn); void assert_all_pending_dns_resolves_ok(void); void dns_cancel_pending_resolve(const char *question); int dns_resolve(edge_connection_t *exitconn); +void dns_launch_wildcard_checks(void); /********************************* hibernate.c **********************/