Browse Source

Merge branch 'bug933_nm_rebased_v2'

Conflicts:
	src/test/test.c
Nick Mathewson 12 years ago
parent
commit
3b88b63826
10 changed files with 361 additions and 32 deletions
  1. 4 0
      changes/bug933
  2. 37 4
      doc/tor.1.txt
  3. 1 1
      src/or/circuituse.c
  4. 49 13
      src/or/config.c
  5. 93 12
      src/or/connection_edge.c
  6. 2 1
      src/or/connection_edge.h
  7. 2 1
      src/or/control.c
  8. 1 0
      src/test/Makefile.am
  9. 2 0
      src/test/test.c
  10. 170 0
      src/test/test_config.c

+ 4 - 0
changes/bug933

@@ -0,0 +1,4 @@
+  o Minor features:
+    - Allow MapAddress directives to specify matches against super-domains,
+      as in 'MapAddress *.torproject.org *.torproject.org.torserver.exit'.
+      Implements issue 933.

+ 37 - 4
doc/tor.1.txt

@@ -691,11 +691,44 @@ The following options are useful only for clients (that is, if
     5050, 5190, 5222, 5223, 6523, 6667, 6697, 8300)
 
 **MapAddress** __address__ __newaddress__::
-    When a request for address arrives to Tor, it will rewrite it to newaddress
+    When a request for address arrives to Tor, it will transform to newaddress
     before processing it. For example, if you always want connections to
-    www.indymedia.org to exit via __torserver__ (where __torserver__ is the
-    nickname of the server), use "MapAddress www.indymedia.org
-    www.indymedia.org.torserver.exit".
+    www.example.com to exit via __torserver__ (where __torserver__ is the
+    nickname of the server), use "MapAddress www.example.com
+    www.example.com.torserver.exit". If the value is prefixed with a
+    "\*.", matches an entire domain. For example, if you
+    always want connections to example.com and any if its subdomains
+    to exit via
+    __torserver__ (where __torserver__ is the nickname of the server), use
+    "MapAddress \*.example.com \*.example.com.torserver.exit". (Note the
+    leading "*." in each part of the directive.) You can also redirect all
+    subdomains of a domain to a single address. For example, "MapAddress
+    *.example.com www.example.com". +
+ +
+    NOTES:
+
+    1. When evaluating MapAddress expressions Tor stops when it hits the most
+    recently added expression that matches the requested address. So if you
+    have the following in your torrc, www.torproject.org will map to 1.1.1.1:
+
+     MapAddress www.torproject.org 2.2.2.2
+     MapAddress www.torproject.org 1.1.1.1
+
+    2. Tor evaluates the MapAddress configuration until it finds no matches. So
+    if you have the following in your torrc, www.torproject.org will map to
+    2.2.2.2:
+
+      MapAddress 1.1.1.1 2.2.2.2
+      MapAddress www.torproject.org 1.1.1.1
+
+    3. The following MapAddress expression is invalid (and will be
+    ignored) because you cannot map from a specific address to a wildcard
+    address:
+
+      MapAddress www.torproject.org *.torproject.org.torserver.exit
+
+    4. Using a wildcard to match only part of a string (as in *ample.com) is
+    also invalid.
 
 **NewCircuitPeriod** __NUM__::
     Every NUM seconds consider whether to build a new circuit. (Default: 30

+ 1 - 1
src/or/circuituse.c

@@ -1687,7 +1687,7 @@ consider_recording_trackhost(const entry_connection_t *conn,
 
   addressmap_register(conn->socks_request->address, new_address,
                       time(NULL) + options->TrackHostExitsExpire,
-                      ADDRMAPSRC_TRACKEXIT);
+                      ADDRMAPSRC_TRACKEXIT, 0, 0);
 }
 
 /** Attempt to attach the connection <b>conn</b> to <b>circ</b>, and send a

+ 49 - 13
src/or/config.c

@@ -4647,24 +4647,60 @@ config_register_addressmaps(const or_options_t *options)
   addressmap_clear_configured();
   elts = smartlist_create();
   for (opt = options->AddressMap; opt; opt = opt->next) {
+    int from_wildcard = 0, to_wildcard = 0;
     smartlist_split_string(elts, opt->value, NULL,
                            SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
-    if (smartlist_len(elts) >= 2) {
-      from = smartlist_get(elts,0);
-      to = smartlist_get(elts,1);
-      if (address_is_invalid_destination(to, 1)) {
-        log_warn(LD_CONFIG,
-                 "Skipping invalid argument '%s' to MapAddress", to);
-      } else {
-        addressmap_register(from, tor_strdup(to), 0, ADDRMAPSRC_TORRC);
-        if (smartlist_len(elts)>2) {
-          log_warn(LD_CONFIG,"Ignoring extra arguments to MapAddress.");
-        }
-      }
-    } else {
+    if (smartlist_len(elts) < 2) {
       log_warn(LD_CONFIG,"MapAddress '%s' has too few arguments. Ignoring.",
                opt->value);
+      goto cleanup;
+    }
+
+    from = smartlist_get(elts,0);
+    to = smartlist_get(elts,1);
+
+    if (to[0] == '.' || from[0] == '.') {
+      log_warn(LD_CONFIG,"MapAddress '%s' is ambiguous - address starts with a"
+              "'.'. Ignoring.",opt->value);
+      goto cleanup;
+    }
+
+    if (!strcmp(to, "*") || !strcmp(from, "*")) {
+      log_warn(LD_CONFIG,"MapAddress '%s' is unsupported - can't remap from "
+               "or to *. Ignoring.",opt->value);
+      goto cleanup;
+    }
+    /* Detect asterisks in expressions of type: '*.example.com' */
+    if (!strncmp(from,"*.",2)) {
+      from += 2;
+      from_wildcard = 1;
     }
+    if (!strncmp(to,"*.",2)) {
+      to += 2;
+      to_wildcard = 1;
+    }
+
+    if (to_wildcard && !from_wildcard) {
+      log_warn(LD_CONFIG,
+                "Skipping invalid argument '%s' to MapAddress: "
+                "can only use wildcard (i.e. '*.') if 'from' address "
+                "uses wildcard also", opt->value);
+      goto cleanup;
+    }
+
+    if (address_is_invalid_destination(to, 1)) {
+      log_warn(LD_CONFIG,
+                "Skipping invalid argument '%s' to MapAddress", opt->value);
+      goto cleanup;
+    }
+
+    addressmap_register(from, tor_strdup(to), 0, ADDRMAPSRC_TORRC,
+                        from_wildcard, to_wildcard);
+
+    if (smartlist_len(elts) > 2)
+      log_warn(LD_CONFIG,"Ignoring extra arguments to MapAddress.");
+
+  cleanup:
     SMARTLIST_FOREACH(elts, char*, cp, tor_free(cp));
     smartlist_clear(elts);
   }

+ 93 - 12
src/or/connection_edge.c

@@ -803,11 +803,18 @@ connection_ap_detach_retriable(entry_connection_t *conn,
  * the configuration file, "1" for mappings set from the control
  * interface, and other values for DNS and TrackHostExit mappings that can
  * expire.)
+ *
+ * A mapping may be 'wildcarded'.  If "src_wildcard" is true, then
+ * any address that ends with a . followed by the key for this entry will
+ * get remapped by it.  If "dst_wildcard" is also true, then only the
+ * matching suffix of such addresses will get replaced by new_address.
  */
 typedef struct {
   char *new_address;
   time_t expires;
   addressmap_entry_source_t source:3;
+  unsigned src_wildcard:1;
+  unsigned dst_wildcard:1;
   short num_resolve_failures;
 } addressmap_entry_t;
 
@@ -1054,6 +1061,37 @@ addressmap_free_all(void)
   virtaddress_reversemap = NULL;
 }
 
+/** Try to find a match for AddressMap expressions that use
+ *  wildcard notation such as '*.c.d *.e.f' (so 'a.c.d' will map to 'a.e.f') or
+ *  '*.c.d a.b.c' (so 'a.c.d' will map to a.b.c).
+ *  Return the matching entry in AddressMap or NULL if no match is found.
+ *  For expressions such as '*.c.d *.e.f', truncate <b>address</b> 'a.c.d'
+ *  to 'a' before we return the matching AddressMap entry.
+ *
+ * This function does not handle the case where a pattern of the form "*.c.d"
+ * matches the address c.d -- that's done by the main addressmap_rewrite
+ * function.
+ */
+static addressmap_entry_t *
+addressmap_match_superdomains(char *address)
+{
+  addressmap_entry_t *val;
+  char *cp;
+
+  cp = address;
+  while ((cp = strchr(cp, '.'))) {
+    /* cp now points to a suffix of address that begins with a . */
+    val = strmap_get_lc(addressmap, cp+1);
+    if (val && val->src_wildcard) {
+      if (val->dst_wildcard)
+        *cp = '\0';
+      return val;
+    }
+    ++cp;
+  }
+  return NULL;
+}
+
 /** Look at address, and rewrite it until it doesn't want any
  * more rewrites; but don't get into an infinite loop.
  * Don't write more than maxlen chars into address.  Return true if the
@@ -1066,25 +1104,49 @@ addressmap_rewrite(char *address, size_t maxlen, time_t *expires_out)
 {
   addressmap_entry_t *ent;
   int rewrites;
-  char *cp;
   time_t expires = TIME_MAX;
 
   for (rewrites = 0; rewrites < 16; rewrites++) {
+    int exact_match = 0;
+    char *addr_orig = tor_strdup(escaped_safe_str_client(address));
+
     ent = strmap_get(addressmap, address);
 
     if (!ent || !ent->new_address) {
+      ent = addressmap_match_superdomains(address);
+    } else {
+      if (ent->src_wildcard && !ent->dst_wildcard &&
+          !strcasecmp(address, ent->new_address)) {
+        /* This is a rule like *.example.com example.com, and we just got
+         * "example.com" */
+        tor_free(addr_orig);
+        if (expires_out)
+          *expires_out = expires;
+        return rewrites > 0;
+      }
+
+      exact_match = 1;
+    }
+
+    if (!ent || !ent->new_address) {
+      tor_free(addr_orig);
       if (expires_out)
         *expires_out = expires;
       return (rewrites > 0); /* done, no rewrite needed */
     }
 
-    cp = tor_strdup(escaped_safe_str_client(ent->new_address));
+    if (ent->dst_wildcard && !exact_match) {
+      strlcat(address, ".", maxlen);
+      strlcat(address, ent->new_address, maxlen);
+    } else {
+      strlcpy(address, ent->new_address, maxlen);
+    }
+
     log_info(LD_APP, "Addressmap: rewriting %s to %s",
-             escaped_safe_str_client(address), cp);
+             addr_orig, escaped_safe_str_client(address));
     if (ent->expires > 1 && ent->expires < expires)
       expires = ent->expires;
-    tor_free(cp);
-    strlcpy(address, ent->new_address, maxlen);
+    tor_free(addr_orig);
   }
   log_warn(LD_CONFIG,
            "Loop detected: we've rewritten %s 16 times! Using it as-is.",
@@ -1148,17 +1210,34 @@ addressmap_have_mapping(const char *address, int update_expiry)
  * <b>new_address</b> should be a newly dup'ed string, which we'll use or
  * free as appropriate. We will leave address alone.
  *
- * If <b>new_address</b> is NULL, or equal to <b>address</b>, remove
- * any mappings that exist from <b>address</b>.
- */
+ * If <b>wildcard_addr</b> is true, then the mapping will match any address
+ * equal to <b>address</b>, or any address ending with a period followed by
+ * <b>address</b>.  If <b>wildcard_addr</b> and <b>wildcard_new_addr</b> are
+ * both true, the mapping will rewrite addresses that end with
+ * ".<b>address</b>" into ones that end with ".<b>new_address</b>."
+ *
+ * If <b>new_address</b> is NULL, or <b>new_address</b> is equal to
+ * <b>address</b> and <b>wildcard_addr</b> is equal to
+ * <b>wildcard_new_addr</b>, remove any mappings that exist from
+ * <b>address</b>.
+ *
+ *
+ * It is an error to set <b>wildcard_new_addr</b> if <b>wildcard_addr</b> is
+ * not set. */
 void
 addressmap_register(const char *address, char *new_address, time_t expires,
-                    addressmap_entry_source_t source)
+                    addressmap_entry_source_t source,
+                    const int wildcard_addr,
+                    const int wildcard_new_addr)
 {
   addressmap_entry_t *ent;
 
+  if (wildcard_new_addr)
+    tor_assert(wildcard_addr);
+
   ent = strmap_get(addressmap, address);
-  if (!new_address || !strcasecmp(address,new_address)) {
+  if (!new_address || (!strcasecmp(address,new_address) &&
+                       wildcard_addr == wildcard_new_addr)) {
     /* Remove the mapping, if any. */
     tor_free(new_address);
     if (ent) {
@@ -1193,6 +1272,8 @@ addressmap_register(const char *address, char *new_address, time_t expires,
   ent->expires = expires==2 ? 1 : expires;
   ent->num_resolve_failures = 0;
   ent->source = source;
+  ent->src_wildcard = wildcard_addr ? 1 : 0;
+  ent->dst_wildcard = wildcard_new_addr ? 1 : 0;
 
   log_info(LD_CONFIG, "Addressmap: (re)mapped '%s' to '%s'",
            safe_str_client(address),
@@ -1277,7 +1358,7 @@ client_dns_set_addressmap_impl(const char *address, const char *name,
                  "%s", name);
   }
   addressmap_register(extendedaddress, tor_strdup(extendedval),
-                      time(NULL) + ttl, ADDRMAPSRC_DNS);
+                      time(NULL) + ttl, ADDRMAPSRC_DNS, 0, 0);
 }
 
 /** Record the fact that <b>address</b> resolved to <b>val</b>.
@@ -1529,7 +1610,7 @@ addressmap_register_virtual_address(int type, char *new_address)
   log_info(LD_APP, "Registering map from %s to %s", *addrp, new_address);
   if (vent_needs_to_be_added)
     strmap_set(virtaddress_reversemap, new_address, vent);
-  addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP);
+  addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0);
 
 #if 0
   {

+ 2 - 1
src/or/connection_edge.h

@@ -78,7 +78,8 @@ int addressmap_rewrite(char *address, size_t maxlen, time_t *expires_out);
 int addressmap_have_mapping(const char *address, int update_timeout);
 
 void addressmap_register(const char *address, char *new_address,
-                         time_t expires, addressmap_entry_source_t source);
+                         time_t expires, addressmap_entry_source_t source,
+                         int address_wildcard, int new_address_wildcard);
 int parse_virtual_addr_network(const char *val, int validate_only,
                                char **msg);
 int client_dns_incr_failures(const char *address);

+ 2 - 1
src/or/control.c

@@ -1331,7 +1331,8 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len,
           smartlist_add(reply, ans);
         }
       } else {
-        addressmap_register(from, tor_strdup(to), 1, ADDRMAPSRC_CONTROLLER);
+        addressmap_register(from, tor_strdup(to), 1,
+                            ADDRMAPSRC_CONTROLLER, 0, 0);
         tor_snprintf(ans, anslen, "250-%s", line);
         smartlist_add(reply, ans);
       }

+ 1 - 0
src/test/Makefile.am

@@ -21,6 +21,7 @@ test_SOURCES = \
 	test_microdesc.c \
 	test_pt.c \
 	test_util.c \
+	test_config.c \
 	tinytest.c
 
 bench_SOURCES = \

+ 2 - 0
src/test/test.c

@@ -1844,6 +1844,7 @@ extern struct testcase_t util_tests[];
 extern struct testcase_t dir_tests[];
 extern struct testcase_t microdesc_tests[];
 extern struct testcase_t pt_tests[];
+extern struct testcase_t config_tests[];
 
 static struct testgroup_t testgroups[] = {
   { "", test_array },
@@ -1855,6 +1856,7 @@ static struct testgroup_t testgroups[] = {
   { "dir/", dir_tests },
   { "dir/md/", microdesc_tests },
   { "pt/", pt_tests },
+  { "config/", config_tests },
   END_OF_GROUPS
 };
 

+ 170 - 0
src/test/test_config.c

@@ -0,0 +1,170 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2010, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "or.h"
+#include "config.h"
+#include "connection_edge.h"
+#include "test.h"
+
+static void
+test_config_addressmap(void *arg)
+{
+  char buf[1024];
+  char address[256];
+  time_t expires = TIME_MAX;
+  (void)arg;
+
+  strlcpy(buf, "MapAddress .invalidwildcard.com *.torserver.exit\n" // invalid
+          "MapAddress *invalidasterisk.com *.torserver.exit\n" // invalid
+          "MapAddress *.google.com *.torserver.exit\n"
+          "MapAddress *.yahoo.com *.google.com.torserver.exit\n"
+          "MapAddress *.cn.com www.cnn.com\n"
+          "MapAddress *.cnn.com www.cnn.com\n"
+          "MapAddress ex.com www.cnn.com\n"
+          "MapAddress ey.com *.cnn.com\n"
+          "MapAddress www.torproject.org 1.1.1.1\n"
+          "MapAddress other.torproject.org "
+            "this.torproject.org.otherserver.exit\n"
+          "MapAddress test.torproject.org 2.2.2.2\n"
+          "MapAddress www.google.com 3.3.3.3\n"
+          "MapAddress www.example.org 4.4.4.4\n"
+          "MapAddress 4.4.4.4 7.7.7.7\n"
+          "MapAddress 4.4.4.4 5.5.5.5\n"
+          "MapAddress www.infiniteloop.org 6.6.6.6\n"
+          "MapAddress 6.6.6.6 www.infiniteloop.org\n"
+          , sizeof(buf));
+
+  config_get_lines(buf, &(get_options_mutable()->AddressMap), 0);
+  config_register_addressmaps(get_options());
+
+  /* MapAddress .invalidwildcard.com .torserver.exit  - no match */
+  strlcpy(address, "www.invalidwildcard.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  /* MapAddress *invalidasterisk.com .torserver.exit  - no match */
+  strlcpy(address, "www.invalidasterisk.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  /* Where no mapping for FQDN match on top-level domain */
+  /* MapAddress .google.com .torserver.exit */
+  strlcpy(address, "reader.google.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "reader.torserver.exit");
+
+  /* MapAddress *.yahoo.com *.google.com.torserver.exit */
+  strlcpy(address, "reader.yahoo.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "reader.google.com.torserver.exit");
+
+  /*MapAddress *.cnn.com www.cnn.com */
+  strlcpy(address, "cnn.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "www.cnn.com");
+
+  /* MapAddress .cn.com www.cnn.com */
+  strlcpy(address, "www.cn.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "www.cnn.com");
+
+  /* MapAddress ex.com www.cnn.com  - no match */
+  strlcpy(address, "www.ex.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  /* MapAddress ey.com *.cnn.com - invalid expression */
+  strlcpy(address, "ey.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  /* Where mapping for FQDN match on FQDN */
+  strlcpy(address, "www.google.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "3.3.3.3");
+
+  strlcpy(address, "www.torproject.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "1.1.1.1");
+
+  strlcpy(address, "other.torproject.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "this.torproject.org.otherserver.exit");
+
+  strlcpy(address, "test.torproject.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "2.2.2.2");
+
+  /* Test a chain of address mappings and the order in which they were added:
+          "MapAddress www.example.org 4.4.4.4"
+          "MapAddress 4.4.4.4 7.7.7.7"
+          "MapAddress 4.4.4.4 5.5.5.5"
+  */
+  strlcpy(address, "www.example.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "5.5.5.5");
+
+  /* Test infinite address mapping results in no change */
+  strlcpy(address, "www.infiniteloop.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "www.infiniteloop.org");
+
+  /* Test we don't find false positives */
+  strlcpy(address, "www.example.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  /* Test top-level-domain matching a bit harder */
+  addressmap_clear_configured();
+  strlcpy(buf, "MapAddress *.com *.torserver.exit\n"
+          "MapAddress *.torproject.org 1.1.1.1\n"
+          "MapAddress *.net 2.2.2.2\n"
+          , sizeof(buf));
+  config_get_lines(buf, &(get_options_mutable()->AddressMap), 0);
+  config_register_addressmaps(get_options());
+
+  strlcpy(address, "www.abc.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "www.abc.torserver.exit");
+
+  strlcpy(address, "www.def.com", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "www.def.torserver.exit");
+
+  strlcpy(address, "www.torproject.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "1.1.1.1");
+
+  strlcpy(address, "test.torproject.org", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "1.1.1.1");
+
+  strlcpy(address, "torproject.net", sizeof(address));
+  test_assert(addressmap_rewrite(address, sizeof(address), &expires));
+  test_streq(address, "2.2.2.2");
+
+  /* We don't support '*' as a mapping directive */
+  addressmap_clear_configured();
+  strlcpy(buf, "MapAddress * *.torserver.exit\n", sizeof(buf));
+  config_get_lines(buf, &(get_options_mutable()->AddressMap), 0);
+  config_register_addressmaps(get_options());
+
+  strlcpy(address, "www.abc.com", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  strlcpy(address, "www.def.net", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+  strlcpy(address, "www.torproject.org", sizeof(address));
+  test_assert(!addressmap_rewrite(address, sizeof(address), &expires));
+
+done:
+  ;
+}
+
+#define CONFIG_TEST(name, flags)                          \
+  { #name, test_config_ ## name, flags, NULL, NULL }
+
+struct testcase_t config_tests[] = {
+  CONFIG_TEST(addressmap, 0),
+  END_OF_TESTCASES
+};
+