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 **********************/