Browse Source

Document parsing functions in routerlist.c, and separate parsing from non-parsing functions.

svn:r894
Nick Mathewson 21 years ago
parent
commit
17269eff3c
1 changed files with 538 additions and 445 deletions
  1. 538 445
      src/or/routerlist.c

+ 538 - 445
src/or/routerlist.c

@@ -21,8 +21,50 @@ extern or_options_t options; /* command-line and config-file options */
 
 /****************************************************************************/
 
-struct directory_token_t;
-typedef struct directory_token_t directory_token_t;
+/* Enumeration of possible token types.  The ones starting with K_ correspond
+ * to directory 'keywords'.  _SIGNATURE and _PUBLIC_KEY are self-explanitory.
+ * _ERR is an error in the tokenizing process, _EOF is an end-of-file marker,
+ * and _NIL is used to encode not-a-token.
+ */
+typedef enum {
+  K_ACCEPT,
+  K_DIRECTORY_SIGNATURE,
+  K_RECOMMENDED_SOFTWARE,
+  K_REJECT,
+  K_ROUTER,
+  K_SIGNED_DIRECTORY,
+  K_SIGNING_KEY,
+  K_ONION_KEY,
+  K_LINK_KEY,
+  K_ROUTER_SIGNATURE,
+  K_PUBLISHED,
+  K_RUNNING_ROUTERS,
+  K_PLATFORM,
+  _SIGNATURE,
+  _PUBLIC_KEY,
+  _ERR,
+  _EOF,
+  _NIL
+} directory_keyword;
+
+/* Struct containing a directory token. */
+#define MAX_ARGS 1024
+typedef struct directory_token_t {
+  directory_keyword tp;        /* Type of the token. */
+  union {
+    struct {
+      char *args[MAX_ARGS+1];  /* For K_xxx tokens only: an array and count */
+      int n_args;              /*    of arguments provided on the same line */
+    } cmd;
+    char *signature;           /* For _SIGNATURE tokens only. */
+    char *error;               /* For _ERR tokens only. */
+    crypto_pk_env_t *public_key;  /* For _PUBLIC_KEY tokens only. */
+  } val;
+} directory_token_t;
+
+/****************************************************************************/
+
+
 
 /* static function prototypes */
 static int router_set_routerlist_from_string(const char *s);
@@ -37,8 +79,21 @@ static int router_add_exit_policy(routerinfo_t *router,
                                   directory_token_t *tok);
 static int router_resolve_routerlist(routerlist_t *dir);
 
-/****************************************************************************/
 
+
+static int _router_get_next_token(const char **s, directory_token_t *tok);
+#ifdef DEBUG_ROUTER_TOKENS
+static int router_get_next_token(const char **s, directory_token_t *tok);
+#else
+#define router_get_next_token _router_get_next_token
+#endif
+static int router_get_hash_impl(const char *s, char *digest, 
+                                const char *start_str,
+                                const char *end_str);
+static void router_release_token(directory_token_t *tok);
+
+
+/****************************************************************************/
 routerinfo_t *router_pick_directory_server(void) {
   /* pick a random running router with a positive dir_port */
   int i,j;
@@ -207,7 +262,9 @@ void router_mark_as_down(char *nickname) {
   router->is_running = 0;
 }
 
-/* load the router list */
+/* ------------------------------------------------------------ */
+
+/* Replace the current router list with the one stored in 'routerfile'. */
 int router_set_routerlist_from_file(char *routerfile)
 {
   char *string;
@@ -228,361 +285,226 @@ int router_set_routerlist_from_file(char *routerfile)
   return 0;
 }
 
-typedef enum {
-  K_ACCEPT,
-  K_DIRECTORY_SIGNATURE,
-  K_RECOMMENDED_SOFTWARE,
-  K_REJECT,
-  K_ROUTER,
-  K_SIGNED_DIRECTORY,
-  K_SIGNING_KEY,
-  K_ONION_KEY,
-  K_LINK_KEY,
-  K_ROUTER_SIGNATURE,
-  K_PUBLISHED,
-  K_RUNNING_ROUTERS,
-  K_PLATFORM,
-  _SIGNATURE,
-  _PUBLIC_KEY,
-  _ERR,
-  _EOF,
-  _NIL
-} directory_keyword;
 
-typedef enum {
-  NO_ARGS,
-  ARGS,
-  CONCAT_ARGS,
-} arg_syntax;
- 
+/* Helper function: read routerinfo elements from s, and throw out the
+ * ones that don't parse and resolve.  Replace the current
+ * routerlist. */
+static int router_set_routerlist_from_string(const char *s)
+{
+  if (router_get_list_from_string_impl(&s, &routerlist, -1, NULL)) {
+    log(LOG_WARN, "Error parsing router file");
+    return -1;
+  }
+  if (router_resolve_routerlist(routerlist)) {
+    log(LOG_WARN, "Error resolving routerlist");
+    return -1;
+  }
+  return 0;
+}
 
-struct token_table_ent { char *t; int v; arg_syntax s; };
+/* Set 'digest' to the SHA-1 digest of the hash of the directory in 's'.
+ * Return 0 on success, nonzero on failure.
+ */
+int router_get_dir_hash(const char *s, char *digest)
+{
+  return router_get_hash_impl(s,digest,
+                              "signed-directory","directory-signature");
+}
+/* Set 'digest' to the SHA-1 digest of the hash of the first router in 's'.
+ * Return 0 on success, nonzero on failure.
+ */
+int router_get_router_hash(const char *s, char *digest)
+{
+  return router_get_hash_impl(s,digest,
+                              "router ","router-signature");
+}
 
-static struct token_table_ent token_table[] = {
-  { "accept", K_ACCEPT, ARGS },
-  { "directory-signature", K_DIRECTORY_SIGNATURE, NO_ARGS },
-  { "reject", K_REJECT, ARGS },
-  { "router", K_ROUTER, ARGS },
-  { "recommended-software", K_RECOMMENDED_SOFTWARE, ARGS },
-  { "signed-directory", K_SIGNED_DIRECTORY, NO_ARGS },
-  { "signing-key", K_SIGNING_KEY, NO_ARGS },
-  { "onion-key", K_ONION_KEY, NO_ARGS },
-  { "link-key", K_LINK_KEY, NO_ARGS },
-  { "router-signature", K_ROUTER_SIGNATURE, NO_ARGS },
-  { "published", K_PUBLISHED, CONCAT_ARGS },
-  { "running-routers", K_RUNNING_ROUTERS, ARGS },
-  { "platform", K_PLATFORM, ARGS },
-  { NULL, -1 }
-};
+/* return 0 if myversion is in versionlist. Else return -1.  (versionlist 
+ * contains a comma-separated list of versions.) */
+int compare_recommended_versions(const char *myversion, 
+                                 const char *versionlist) {
+  int len_myversion = strlen(myversion);
+  char *comma;
+  const char *end = versionlist + strlen(versionlist);
 
-#define MAX_ARGS 1024
-struct directory_token_t {
-  directory_keyword tp;
-  union {
-    struct {
-      char *args[MAX_ARGS+1];
-      int n_args;
-    } cmd;
-    char *signature;
-    char *error;
-    crypto_pk_env_t *public_key;
-  } val;
-};
+  log_fn(LOG_DEBUG,"checking '%s' in '%s'.", myversion, versionlist);
+  
+  for(;;) {
+    comma = strchr(versionlist, ',');
+    if( ((comma ? comma : end) - versionlist == len_myversion) &&
+       !strncmp(versionlist, myversion, len_myversion))
+      /* only do strncmp if the length matches */
+      return 0; /* success, it's there */
+    if(!comma)
+      return -1; /* nope */
+    versionlist = comma+1;
+  }
+}
 
-/* Free any malloced resources allocated for a token.  Don't call this if
-   you inherit the reference to those resources.
+/* Replace the current routerlist with the routers stored in the directory
+ * 's'.  If pkey is provided, make sure that 's' is signed with pkey.
  */
-static void
-router_release_token(directory_token_t *tok)
+int router_set_routerlist_from_directory(const char *s, crypto_pk_env_t *pkey)
 {
-  int i;
-  switch (tok->tp) 
-    {
-    case _SIGNATURE:
-      free(tok->val.signature);
-      break;
-    case _PUBLIC_KEY:
-      crypto_free_pk_env(tok->val.public_key);
-      break;
-    case _ERR:
-    case _EOF:
-    case _NIL:
-      break;
-    default:
-      for (i = 0; i < tok->val.cmd.n_args; ++i) {
-        tor_free(tok->val.cmd.args[i]);
-      }
+  if (router_get_routerlist_from_directory_impl(s, &routerlist, pkey)) {
+    log_fn(LOG_WARN, "Couldn't parse directory.");
+    return -1;
+  }
+  if (router_resolve_routerlist(routerlist)) {
+    log_fn(LOG_WARN, "Error resolving routerlist");
+    return -1;
+  }
+  if (compare_recommended_versions(VERSION, routerlist->software_versions) < 0) {
+    log(options.IgnoreVersion ? LOG_WARN : LOG_ERR,
+        "You are running Tor version %s, which is not recommended.\n"
+       "Please upgrade to one of %s.",
+        VERSION, routerlist->software_versions);
+    if(options.IgnoreVersion) {
+      log(LOG_WARN, "IgnoreVersion is set. If it breaks, we told you so.");
+    } else {
+      fflush(0);
+      exit(0);
     }
-  tok->tp = _NIL;
+  }
+
+  return 0;
 }
 
+/* Helper function: resolve the hostname for 'router' */
 static int
-_router_get_next_token(const char **s, directory_token_t *tok) {
-  const char *next;
-  crypto_pk_env_t *pkey = NULL;
-  char *signature = NULL;
-  int i, done;
+router_resolve(routerinfo_t *router)
+{
+  struct hostent *rent;
 
-  tok->tp = _ERR;
-  tok->val.error = "";
+  rent = (struct hostent *)gethostbyname(router->address);
+  if (!rent) {
+    log_fn(LOG_WARN,"Could not get address for router %s.",router->address);
+    return -1; 
+  }
+  assert(rent->h_length == 4);
+  memcpy(&router->addr, rent->h_addr,rent->h_length);
+  router->addr = ntohl(router->addr); /* get it back into host order */
 
-  router_release_token(tok);
-  
-  *s = eat_whitespace(*s);
-  if (!**s) {
-    tok->tp = _EOF;
-    return 0;
-  } else if (**s == '-') {
-    next = strchr(*s, '\n');
-    if (! next) { tok->val.error = "No newline at EOF"; return -1; }
-    ++next;
-    if (! strncmp(*s, OR_PUBLICKEY_BEGIN_TAG, next-*s)) {
-      next = strstr(*s, OR_PUBLICKEY_END_TAG);
-      if (!next) { tok->val.error = "No public key end tag found"; return -1; }
-      next = strchr(next, '\n'); /* Part of OR_PUBLICKEY_END_TAG; can't fail.*/
-      ++next;
-      if (!(pkey = crypto_new_pk_env(CRYPTO_PK_RSA))) 
-        return -1;
-      if (crypto_pk_read_public_key_from_string(pkey, *s, next-*s)) {
-        crypto_free_pk_env(pkey);
-        tok->val.error = "Couldn't parse public key.";
-        return -1;
-      }
-      tok->tp = _PUBLIC_KEY;
-      tok->val.public_key = pkey;
-      *s = next;
-      return 0;
-    } else if (! strncmp(*s, OR_SIGNATURE_BEGIN_TAG, next-*s)) {
-      /* Advance past newline; can't fail. */
-      *s = strchr(*s, '\n'); 
-      ++*s;
-      /* Find end of base64'd data */
-      next = strstr(*s, OR_SIGNATURE_END_TAG);
-      if (!next) { tok->val.error = "No signature end tag found"; return -1; }
+  return 0;
+}
 
-      signature = tor_malloc(256);
-      i = base64_decode(signature, 256, *s, next-*s);
-      if (i<0) {
-        free(signature);
-        tok->val.error = "Error decoding signature."; return -1;
-      } else if (i != 128) {
-        free(signature);
-        tok->val.error = "Bad length on decoded signature."; return -1;
-      }
-      tok->tp = _SIGNATURE;
-      tok->val.signature = signature;
+/* Helper function: resolve every router in rl. */
+static int
+router_resolve_routerlist(routerlist_t *rl)
+{
+  int i, max, remove;
+  if (!rl)
+    rl = routerlist;
 
-      next = strchr(next, '\n'); /* Part of OR_SIGNATURE_END_TAG; can't fail.*/
-      *s = next+1;
-      return 0;
-    } else {
-      tok->val.error = "Unrecognized begin line"; return -1;
-    }
-  } else {
-    next = find_whitespace(*s);
-    if (!next) {
-      tok->val.error = "Unexpected EOF"; return -1;
+  max = rl->n_routers;
+  for (i = 0; i < max; ++i) {
+    remove = 0;
+    if (router_resolve(rl->routers[i])) {
+      log_fn(LOG_WARN, "Couldn't resolve router %s; not using",
+             rl->routers[i]->address);
+      remove = 1;
+    } else if (options.Nickname &&
+               !strcmp(rl->routers[i]->nickname, options.Nickname)) {
+      remove = 1;
     }
-    for (i = 0 ; token_table[i].t ; ++i) {
-      if (!strncmp(token_table[i].t, *s, next-*s)) {
-        tok->tp = token_table[i].v;
-
-        if (token_table[i].s == ARGS) {
-          i = 0;
-          done = (*next == '\n');
-          *s = eat_whitespace_no_nl(next);
-          while (**s != '\n' && i < MAX_ARGS && !done) {
-            next = find_whitespace(*s);
-            if (*next == '\n')
-              done = 1;
-            tok->val.cmd.args[i++] = tor_strndup(*s,next-*s);
-            *s = eat_whitespace_no_nl(next+1);
-          }
-          tok->val.cmd.n_args = i;
-          if (i >= MAX_ARGS) {
-            /* XXX free args[0..i] */
-            tok->tp = _ERR;
-            tok->val.error = "Too many arguments"; return -1;
-          }
-        } else if (token_table[i].s == CONCAT_ARGS) {
-          *s = eat_whitespace_no_nl(next);
-          next = strchr(*s, '\n');
-          if (!next) {
-            tok->tp = _ERR;
-            tok->val.error = "Unexpected EOF"; return -1;
-          }
-          tok->val.cmd.args[0] = tor_strndup(*s,next-*s);
-          tok->val.cmd.n_args = 1;
-          *s = eat_whitespace_no_nl(next+1);
-        } else {
-          *s = eat_whitespace_no_nl(next);
-          if (**s != '\n') {
-            tok->tp = _ERR;
-            tok->val.error = "Unexpected arguments"; return -1;
-          }
-          tok->val.cmd.n_args = 0;
-          *s = eat_whitespace_no_nl(*s+1);
-        }
-        return 0;
-      }
+    if (remove) {
+      routerinfo_free(rl->routers[i]);
+      rl->routers[i] = rl->routers[--max];
+      --rl->n_routers;
+      --i;
     }
-    tok->val.error = "Unrecognized command"; return -1;
   }
-}
 
-#ifdef DEBUG_ROUTER_TOKENS
-static void 
-router_dump_token(directory_token_t *tok) {
-  int i;
-  switch(tok->tp) 
-    {
-    case _SIGNATURE:
-      puts("(signature)");
-      return;
-    case _PUBLIC_KEY:
-      puts("(public key)");
-      return;
-    case _ERR:
-      printf("(Error: %s\n)", tok->val.error);
-      return;
-    case _EOF:
-      puts("EOF");
-      return;
-    case K_ACCEPT: printf("Accept"); break;
-    case K_DIRECTORY_SIGNATURE: printf("Directory-Signature"); break;
-    case K_REJECT: printf("Reject"); break;
-    case K_RECOMMENDED_SOFTWARE: printf("Server-Software"); break;
-    case K_ROUTER: printf("Router"); break;
-    case K_SIGNED_DIRECTORY: printf("Signed-Directory"); break;
-    case K_SIGNING_KEY: printf("Signing-Key"); break;
-    case K_ONION_KEY: printf("Onion-key"); break;
-    case K_LINK_KEY: printf("Link-key"); break;
-    case K_ROUTER_SIGNATURE: printf("Router-signature"); break;
-    case K_PUBLISHED: printf("Published"); break;
-    case K_RUNNING_ROUTERS: printf("Running-routers"); break;
-    case K_PLATFORM: printf("Platform"); break;
-    default:
-      printf("?????? %d\n", tok->tp); return;
-    }
-  for (i = 0; i < tok->val.cmd.n_args; ++i) {
-    printf(" \"%s\"", tok->val.cmd.args[i]);
-  }
-  printf("\n");
-  return;
-}
-static int
-router_get_next_token(char **s, directory_token_t *tok) {
-  int i;
-  i = _router_get_next_token(s, tok);
-  router_dump_token(tok);
-  return i;
+  return 0;
 }
-#else
-#define router_get_next_token _router_get_next_token
-#endif
 
-/* read routerinfo elements from s, and throw out the ones that
- * don't parse and resolve. */
-static int router_set_routerlist_from_string(const char *s)
+/* Addr is 0 for "IP unknown".
+ *
+ * Returns -1 for 'rejected', 0 for accepted, 1 for 'maybe' (since IP is
+ * unknown.
+ */
+int router_supports_exit_address(uint32_t addr, uint16_t port,
+                                 routerinfo_t *router)
 {
-  if (router_get_list_from_string_impl(&s, &routerlist, -1, NULL)) {
-    log(LOG_WARN, "Error parsing router file");
-    return -1;
-  }
-  if (router_resolve_routerlist(routerlist)) {
-    log(LOG_WARN, "Error resolving routerlist");
-    return -1;
-  }
-  return 0;
+  return router_compare_addr_to_exit_policy(addr, port, router->exit_policy);
 }
 
-static int router_get_hash_impl(const char *s, char *digest, 
-                                const char *start_str,
-                                const char *end_str)
+/* Addr is 0 for "IP unknown".
+ *
+ * Returns -1 for 'rejected', 0 for accepted, 1 for 'maybe' (since IP is
+ * unknown.
+ */
+int router_compare_addr_to_exit_policy(uint32_t addr, uint16_t port,
+                                       struct exit_policy_t *policy)
 {
-  char *start, *end;
-  start = strstr(s, start_str);
-  if (!start) {
-    log_fn(LOG_WARN,"couldn't find \"%s\"",start_str);
-    return -1;
-  }
-  end = strstr(start+strlen(start_str), end_str);
-  if (!end) {
-    log_fn(LOG_WARN,"couldn't find \"%s\"",end_str);
-    return -1;
-  }
-  end = strchr(end, '\n');
-  if (!end) {
-    log_fn(LOG_WARN,"couldn't find EOL");
-    return -1;
-  }
-  ++end;
+  int maybe_reject = 0;
+  int match = 0;
+  struct in_addr in;
+  struct exit_policy_t *tmpe;
 
-  if (crypto_SHA_digest(start, end-start, digest)) {
-    log_fn(LOG_WARN,"couldn't compute digest");
-    return -1;
+  for(tmpe=policy; tmpe; tmpe=tmpe->next) {
+    log_fn(LOG_DEBUG,"Considering exit policy %s", tmpe->string);
+    if (!addr) {
+      /* Address is unknown. */
+      if (tmpe->msk == 0 && (!tmpe || port == tmpe->prt)) {
+        /* The exit policy is accept/reject *:port */
+        match = 1;
+      } else if ((!tmpe->prt || port == tmpe->prt) && 
+                 tmpe->policy_type == EXIT_POLICY_REJECT) {
+        /* The exit policy is reject ???:port */
+        maybe_reject = 1;
+      }
+    } else {
+      /* Address is known */
+      if ( (addr & tmpe->msk) == (tmpe->addr & tmpe->msk) &&
+           (!tmpe->prt || port == tmpe->prt) ) {
+        /* Exact match for the policy */
+        match = 1;
+      }
+    }
+    if (match) {
+      in.s_addr = htonl(addr);
+      log_fn(LOG_INFO,"Address %s:%d matches exit policy '%s'",
+             inet_ntoa(in), port, tmpe->string);
+      if(tmpe->policy_type == EXIT_POLICY_ACCEPT)
+        return 0;
+      else
+        return -1;
+    }
   }
-
-  return 0;
-}
-
-int router_get_dir_hash(const char *s, char *digest)
-{
-  return router_get_hash_impl(s,digest,
-                              "signed-directory","directory-signature");
-}
-int router_get_router_hash(const char *s, char *digest)
-{
-  return router_get_hash_impl(s,digest,
-                              "router ","router-signature");
+  if (maybe_reject)
+    return 1;
+  else
+    return 0; /* accept all by default. */
 }
 
-/* return 0 if myversion is in start. Else return -1. */
-int compare_recommended_versions(const char *myversion, const char *start) {
-  int len_myversion = strlen(myversion);
-  char *comma;
-  const char *end = start + strlen(start);
+/* return 1 if all running routers will reject addr:port, return 0 if
+   any might accept it. */
+int router_exit_policy_all_routers_reject(uint32_t addr, uint16_t port) {
+  int i;
+  routerinfo_t *router;
 
-  log_fn(LOG_DEBUG,"checking '%s' in '%s'.", myversion, start);
-  
-  for(;;) {
-    comma = strchr(start, ',');
-    if( ((comma ? comma : end) - start == len_myversion) &&
-       !strncmp(start, myversion, len_myversion))
-      /* only do strncmp if the length matches */
-      return 0; /* success, it's there */
-    if(!comma)
-      return -1; /* nope */
-    start = comma+1;
+  for (i=0;i<routerlist->n_routers;i++) {
+    router = routerlist->routers[i];
+    if (router->is_running && router_compare_addr_to_exit_policy(addr,
+        port, router->exit_policy) >= 0)
+      return 0; /* this one could be ok. good enough. */
   }
+  return 1; /* all will reject. */
 }
 
-int router_set_routerlist_from_directory(const char *s, crypto_pk_env_t *pkey)
-{
-  if (router_get_routerlist_from_directory_impl(s, &routerlist, pkey)) {
-    log_fn(LOG_WARN, "Couldn't parse directory.");
-    return -1;
-  }
-  if (router_resolve_routerlist(routerlist)) {
-    log_fn(LOG_WARN, "Error resolving routerlist");
-    return -1;
-  }
-  if (compare_recommended_versions(VERSION, routerlist->software_versions) < 0) {
-    log(options.IgnoreVersion ? LOG_WARN : LOG_ERR,
-        "You are running Tor version %s, which is not recommended.\n"
-       "Please upgrade to one of %s.",
-        VERSION, routerlist->software_versions);
-    if(options.IgnoreVersion) {
-      log(LOG_WARN, "IgnoreVersion is set. If it breaks, we told you so.");
-    } else {
-      fflush(0);
-      exit(0);
-    }
-  }
-
-  return 0;
+int router_exit_policy_rejects_all(routerinfo_t *router) {
+  if (router_compare_addr_to_exit_policy(0, 0, router->exit_policy) < 0)
+    return 1; /* yes, rejects all */
+  else
+    return 0; /* no, might accept some */
 }
 
+ 
+/* Helper function: parse a directory from 's' and, when done, store the
+ * resulting routerlist in *dest, freeing the old value if necessary.
+ * If pkey is provided, we check the directory signature with pkey.
+ */
 static int
 router_get_routerlist_from_directory_impl(const char *s, routerlist_t **dest,
                                           crypto_pk_env_t *pkey)
@@ -598,20 +520,26 @@ router_get_routerlist_from_directory_impl(const char *s, routerlist_t **dest,
   int n_good_nicknames = 0;
   int i;
   
+  /* Local helper macro: get the next token from s (advancing s) and
+   * bail on failure. */
 #define NEXT_TOK()                                                      \
   do {                                                                  \
     if (router_get_next_token(&s, &tok)) {                              \
-      log_fn(LOG_WARN, "Error reading directory: %s", tok.val.error);\
-      return -1;                                                        \
+      log_fn(LOG_WARN, "Error reading directory: %s", tok.val.error);   \
+      goto err;                                                         \
     } } while (0)
+  /* Local helper macro: bail if the most recently read token is not of
+   * the given type. */
 #define TOK_IS(type,name)                                               \
   do {                                                                  \
     if (tok.tp != type) {                                               \
       router_release_token(&tok);                                       \
-      log_fn(LOG_WARN, "Error reading directory: expected %s", name);\
-      return -1;                                                        \
+      log_fn(LOG_WARN, "Error reading directory: expected %s", name);   \
+      goto err;                                                         \
     } } while(0)
+  /* Local helper macro: Number of args in most recent token. */
 #define N_ARGS tok.val.cmd.n_args
+  /* Local helper macro: Array of args to most recent token. */
 #define ARGS tok.val.cmd.args
 
   tok.tp = _NIL;
@@ -650,6 +578,8 @@ router_get_routerlist_from_directory_impl(const char *s, routerlist_t **dest,
   memcpy(good_nickname_lst, ARGS, n_good_nicknames*sizeof(char *));
   N_ARGS = 0; /* Don't free the strings in good_nickname_lst. */
 
+  /* Read the router list from s, advancing s up past the end of the last
+   * router. */
   if (router_get_list_from_string_impl(&s, &new_dir,
                                        n_good_nicknames, 
                                        (const char**)good_nickname_lst)) {
@@ -691,12 +621,9 @@ router_get_routerlist_from_directory_impl(const char *s, routerlist_t **dest,
   router_release_token(&tok);
   if (new_dir)
     routerlist_free(new_dir);
-  if (versions)
-    tor_free(versions);
-  if (n_good_nicknames) {
-    for (i = 0; i < n_good_nicknames; ++i) {
-      tor_free(good_nickname_lst[i]);
-    }
+  tor_free(versions);
+  for (i = 0; i < n_good_nicknames; ++i) {
+    tor_free(good_nickname_lst[i]);
   }
   return -1;
 #undef NEXT_TOK
@@ -705,6 +632,13 @@ router_get_routerlist_from_directory_impl(const char *s, routerlist_t **dest,
 #undef N_ARGS
 }
 
+/* Helper function: Given a string *s containing a concatenated
+ * sequence of router descriptors, parses them and stores the result
+ * in *dest.  If good_nickname_lst is provided, then routers whose
+ * nicknames are not listed are marked as nonrunning.  Advances *s to
+ * a point immediately following the last router entry.  Returns 0 on
+ * success and -1 on failure.
+ */
 static int
 router_get_list_from_string_impl(const char **s, routerlist_t **dest,
                                  int n_good_nicknames,
@@ -717,10 +651,12 @@ router_get_list_from_string_impl(const char **s, routerlist_t **dest,
 
   assert(s && *s);
 
-  rarray = (routerinfo_t **)tor_malloc((sizeof(routerinfo_t *))*MAX_ROUTERS_IN_DIR);
+  rarray = (routerinfo_t **)
+    tor_malloc((sizeof(routerinfo_t *))*MAX_ROUTERS_IN_DIR);
 
   while (1) {
     *s = eat_whitespace(*s);
+    /* Don't start parsing the rest of *s unless it contains a router. */
     if (strncmp(*s, "router ", 7)!=0)
       break;
     router = router_get_entry_from_string(s);
@@ -760,55 +696,11 @@ router_get_list_from_string_impl(const char **s, routerlist_t **dest,
   return 0;
 }
 
-static int
-router_resolve(routerinfo_t *router)
-{
-  struct hostent *rent;
 
-  rent = (struct hostent *)gethostbyname(router->address);
-  if (!rent) {
-    log_fn(LOG_WARN,"Could not get address for router %s.",router->address);
-    return -1; 
-  }
-  assert(rent->h_length == 4);
-  memcpy(&router->addr, rent->h_addr,rent->h_length);
-  router->addr = ntohl(router->addr); /* get it back into host order */
-
-  return 0;
-}
-
-static int
-router_resolve_routerlist(routerlist_t *rl)
-{
-  int i, max, remove;
-  if (!rl)
-    rl = routerlist;
-
-  max = rl->n_routers;
-  for (i = 0; i < max; ++i) {
-    remove = 0;
-    if (router_resolve(rl->routers[i])) {
-      log_fn(LOG_WARN, "Couldn't resolve router %s; not using",
-             rl->routers[i]->address);
-      remove = 1;
-    } else if (options.Nickname &&
-               !strcmp(rl->routers[i]->nickname, options.Nickname)) {
-      remove = 1;
-    }
-    if (remove) {
-      routerinfo_free(rl->routers[i]);
-      rl->routers[i] = rl->routers[--max];
-      --rl->n_routers;
-      --i;
-    }
-  }
-
-  return 0;
-}
-
-/* reads a single router entry from s.
- * updates s so it points to after the router it just read.
- * mallocs a new router and returns it if all goes well, else returns NULL.
+/* Helper function: reads a single router entry from *s, and advances
+ * updates *s so it points to just after the router it just read.
+ * mallocs a new router and returns it if all goes well, else returns
+ * NULL.
  */
 routerinfo_t *router_get_entry_from_string(const char**s) {
   routerinfo_t *router = NULL;
@@ -819,6 +711,8 @@ routerinfo_t *router_get_entry_from_string(const char**s) {
   struct tm published;
   int t;
 
+  /* Helper macro: read the next token from *s, advance *s, and bail
+     if there's an error */
 #define NEXT_TOKEN()                                                     \
   do { if (router_get_next_token(s, tok)) {                              \
       log_fn(LOG_WARN, "Error reading directory: %s", tok->val.error);\
@@ -971,6 +865,8 @@ err:
 #undef NEXT_TOKEN
 }
 
+/* Parse the exit policy in the string 's' and add it to 'router'.
+ */
 int
 router_add_exit_policy_from_string(routerinfo_t *router, const char *s)
 {
@@ -982,6 +878,7 @@ router_add_exit_policy_from_string(routerinfo_t *router, const char *s)
 
   tok.tp = _NIL;
 
+  /* *s might not end with \n, so we need to extend it with one. */
   len = strlen(s);
   cp = tmp = tor_malloc(len+2);
   for (idx = 0; idx < len; ++idx) {
@@ -999,18 +896,25 @@ router_add_exit_policy_from_string(routerinfo_t *router, const char *s)
     free(tmp);
     return -1;
   }
+
+  /* Now that we've gotten an exit policy, add it to the router. */
   r = router_add_exit_policy(router, &tok);
   free(tmp);
   return r;
 }
 
+/* Given a K_ACCEPT or K_REJECT token and a router, create a new exit_policy_t
+ * corresponding to the token, and add it to 'router' */
 static int router_add_exit_policy(routerinfo_t *router,
                                   directory_token_t *tok) {
+
   struct exit_policy_t *tmpe, *newe;
   struct in_addr in;
   char *arg, *address, *mask, *port, *endptr;
   int bits;
 
+  assert(tok->tp == K_REJECT || tok->tp == K_ACCEPT);
+
   if (tok->val.cmd.n_args != 1)
     return -1;
   arg = tok->val.cmd.args[0];
@@ -1022,7 +926,6 @@ static int router_add_exit_policy(routerinfo_t *router,
     strcpy(newe->string, "reject ");
     newe->policy_type = EXIT_POLICY_REJECT;
   } else {
-    assert(tok->tp == K_ACCEPT);
     strcpy(newe->string, "accept ");
     newe->policy_type = EXIT_POLICY_ACCEPT;
   }
@@ -1105,88 +1008,278 @@ policy_read_failed:
   return -1;
 }
 
-/* Addr is 0 for "IP unknown".
- *
- * Returns -1 for 'rejected', 0 for accepted, 1 for 'maybe' (since IP is
- * unknown.
+/* ------------------------------------------------------------ */
+/* Tokenizer for router descriptors and directories. */
+
+/* Every keyword takes either... */
+typedef enum {
+  NO_ARGS,     /*    (1) no arguments, ever */
+  ARGS,        /*    (2) a list of arguments separated by spaces */
+  CONCAT_ARGS, /* or (3) the rest of the line, treated as a single argument. */
+} arg_syntax;
+
+/* Table mapping keywods to token value and to argument rules. */
+static struct { char *t; int v; arg_syntax s; } token_table[] = {
+  { "accept", K_ACCEPT, ARGS },
+  { "directory-signature", K_DIRECTORY_SIGNATURE, NO_ARGS },
+  { "reject", K_REJECT, ARGS },
+  { "router", K_ROUTER, ARGS },
+  { "recommended-software", K_RECOMMENDED_SOFTWARE, ARGS },
+  { "signed-directory", K_SIGNED_DIRECTORY, NO_ARGS },
+  { "signing-key", K_SIGNING_KEY, NO_ARGS },
+  { "onion-key", K_ONION_KEY, NO_ARGS },
+  { "link-key", K_LINK_KEY, NO_ARGS },
+  { "router-signature", K_ROUTER_SIGNATURE, NO_ARGS },
+  { "published", K_PUBLISHED, CONCAT_ARGS },
+  { "running-routers", K_RUNNING_ROUTERS, ARGS },
+  { "platform", K_PLATFORM, ARGS },
+  { NULL, -1 }
+};
+
+/* Free any malloced resources allocated for a token.  Does not free
+ *  the token itself.
  */
-int router_supports_exit_address(uint32_t addr, uint16_t port,
-                                 routerinfo_t *router)
+static void
+router_release_token(directory_token_t *tok)
 {
-  return router_compare_addr_to_exit_policy(addr, port, router->exit_policy);
+  int i;
+  switch (tok->tp) 
+    {
+    case _SIGNATURE:
+      free(tok->val.signature);
+      break;
+    case _PUBLIC_KEY:
+      crypto_free_pk_env(tok->val.public_key);
+      break;
+    case _ERR:
+    case _EOF:
+    case _NIL:
+      break;
+    default:
+      for (i = 0; i < tok->val.cmd.n_args; ++i) {
+        tor_free(tok->val.cmd.args[i]);
+      }
+    }
+  tok->tp = _NIL;
 }
 
-/* Addr is 0 for "IP unknown".
+/* Helper function: read the next token from *s, and stores it into *tok.
+ * If *tok already contains a token (tok->tp != _NIL), free the resources
+ * held by *tok.  Advance *s to a point immediately after the token.
+ *
  *
- * Returns -1 for 'rejected', 0 for accepted, 1 for 'maybe' (since IP is
- * unknown.
  */
-int router_compare_addr_to_exit_policy(uint32_t addr, uint16_t port,
-                                       struct exit_policy_t *policy)
-{
-  int maybe_reject = 0;
-  int match = 0;
-  struct in_addr in;
-  struct exit_policy_t *tmpe;
+static int
+_router_get_next_token(const char **s, directory_token_t *tok) {
+  const char *next;
+  crypto_pk_env_t *pkey = NULL;
+  char *signature = NULL;
+  int i, done;
 
-  for(tmpe=policy; tmpe; tmpe=tmpe->next) {
-    log_fn(LOG_DEBUG,"Considering exit policy %s", tmpe->string);
-    if (!addr) {
-      /* Address is unknown. */
-      if (tmpe->msk == 0 && (!tmpe || port == tmpe->prt)) {
-        /* The exit policy is accept/reject *:port */
-        match = 1;
-      } else if ((!tmpe->prt || port == tmpe->prt) && 
-                 tmpe->policy_type == EXIT_POLICY_REJECT) {
-        /* The exit policy is reject ???:port */
-        maybe_reject = 1;
+  tok->tp = _ERR;
+  tok->val.error = "";
+
+  router_release_token(tok);
+  
+  *s = eat_whitespace(*s);
+  if (!**s) {
+    tok->tp = _EOF;
+    return 0;
+  } else if (**s == '-') {
+    next = strchr(*s, '\n');
+    if (! next) { tok->val.error = "No newline at EOF"; return -1; }
+    ++next;
+    if (! strncmp(*s, OR_PUBLICKEY_BEGIN_TAG, next-*s)) {
+      /* We have a ----BEGIN PUBLIC KEY----- */
+      next = strstr(*s, OR_PUBLICKEY_END_TAG);
+      if (!next) { tok->val.error = "No public key end tag found"; return -1; }
+      next = strchr(next, '\n'); /* Part of OR_PUBLICKEY_END_TAG; can't fail.*/
+      ++next;
+      if (!(pkey = crypto_new_pk_env(CRYPTO_PK_RSA))) 
+        return -1;
+      if (crypto_pk_read_public_key_from_string(pkey, *s, next-*s)) {
+        crypto_free_pk_env(pkey);
+        tok->val.error = "Couldn't parse public key.";
+        return -1;
       }
-    } else {
-      /* Address is known */
-      if ( (addr & tmpe->msk) == (tmpe->addr & tmpe->msk) &&
-           (!tmpe->prt || port == tmpe->prt) ) {
-        /* Exact match for the policy */
-        match = 1;
+      tok->tp = _PUBLIC_KEY;
+      tok->val.public_key = pkey;
+      *s = next;
+      return 0;
+    } else if (! strncmp(*s, OR_SIGNATURE_BEGIN_TAG, next-*s)) {
+      /* We have a -----BEGIN SIGNATURE----- */
+      /* Advance past newline; can't fail. */
+      *s = strchr(*s, '\n'); 
+      ++*s;
+      /* Find end of base64'd data */
+      next = strstr(*s, OR_SIGNATURE_END_TAG);
+      if (!next) { tok->val.error = "No signature end tag found"; return -1; }
+
+      signature = tor_malloc(256);
+      i = base64_decode(signature, 256, *s, next-*s);
+      if (i<0) {
+        free(signature);
+        tok->val.error = "Error decoding signature."; return -1;
+      } else if (i != 128) {
+        free(signature);
+        tok->val.error = "Bad length on decoded signature."; return -1;
       }
+      tok->tp = _SIGNATURE;
+      tok->val.signature = signature;
+
+      next = strchr(next, '\n'); /* Part of OR_SIGNATURE_END_TAG; can't fail.*/
+      *s = next+1;
+      return 0;
+    } else {
+      tok->val.error = "Unrecognized begin line"; return -1;
     }
-    if (match) {
-      in.s_addr = htonl(addr);
-      log_fn(LOG_INFO,"Address %s:%d matches exit policy '%s'",
-             inet_ntoa(in), port, tmpe->string);
-      if(tmpe->policy_type == EXIT_POLICY_ACCEPT)
+  } else {
+    next = find_whitespace(*s);
+    if (!next) {
+      tok->val.error = "Unexpected EOF"; return -1;
+    }
+    /* It's a keyword... but which one? */
+    for (i = 0 ; token_table[i].t ; ++i) {
+      if (!strncmp(token_table[i].t, *s, next-*s)) {
+        /* We've found the keyword. */
+        tok->tp = token_table[i].v;
+
+        if (token_table[i].s == ARGS) {
+          /* This keyword takes multiple arguments. */
+          i = 0;
+          done = (*next == '\n');
+          *s = eat_whitespace_no_nl(next);
+          while (**s != '\n' && i < MAX_ARGS && !done) {
+            next = find_whitespace(*s);
+            if (*next == '\n')
+              done = 1;
+            tok->val.cmd.args[i++] = tor_strndup(*s,next-*s);
+            *s = eat_whitespace_no_nl(next+1);
+          }
+          tok->val.cmd.n_args = i;
+          if (i >= MAX_ARGS) {
+            /* XXX free args[0..i] */
+            tok->tp = _ERR;
+            tok->val.error = "Too many arguments"; return -1;
+          }
+        } else if (token_table[i].s == CONCAT_ARGS) {
+          /* The keyword takes the line as a single argument */
+          *s = eat_whitespace_no_nl(next);
+          next = strchr(*s, '\n');
+          if (!next) {
+            tok->tp = _ERR;
+            tok->val.error = "Unexpected EOF"; return -1;
+          }
+          tok->val.cmd.args[0] = tor_strndup(*s,next-*s);
+          tok->val.cmd.n_args = 1;
+          *s = eat_whitespace_no_nl(next+1);
+        } else {
+          /* The keyword takes no arguments. */
+          *s = eat_whitespace_no_nl(next);
+          if (**s != '\n') {
+            tok->tp = _ERR;
+            tok->val.error = "Unexpected arguments"; return -1;
+          }
+          tok->val.cmd.n_args = 0;
+          *s = eat_whitespace_no_nl(*s+1);
+        }
         return 0;
-      else
-        return -1;
+      }
     }
+    tok->val.error = "Unrecognized command"; return -1;
   }
-  if (maybe_reject)
-    return 1;
-  else
-    return 0; /* accept all by default. */
 }
 
-/* return 1 if all running routers will reject addr:port, return 0 if
-   any might accept it. */
-int router_exit_policy_all_routers_reject(uint32_t addr, uint16_t port) {
+#ifdef DEBUG_ROUTER_TOKENS
+static void 
+router_dump_token(directory_token_t *tok) {
   int i;
-  routerinfo_t *router;
-
-  for (i=0;i<routerlist->n_routers;i++) {
-    router = routerlist->routers[i];
-    if (router->is_running && router_compare_addr_to_exit_policy(addr,
-        port, router->exit_policy) >= 0)
-      return 0; /* this one could be ok. good enough. */
+  switch(tok->tp) 
+    {
+    case _SIGNATURE:
+      puts("(signature)");
+      return;
+    case _PUBLIC_KEY:
+      puts("(public key)");
+      return;
+    case _ERR:
+      printf("(Error: %s\n)", tok->val.error);
+      return;
+    case _EOF:
+      puts("EOF");
+      return;
+    case K_ACCEPT: printf("Accept"); break;
+    case K_DIRECTORY_SIGNATURE: printf("Directory-Signature"); break;
+    case K_REJECT: printf("Reject"); break;
+    case K_RECOMMENDED_SOFTWARE: printf("Server-Software"); break;
+    case K_ROUTER: printf("Router"); break;
+    case K_SIGNED_DIRECTORY: printf("Signed-Directory"); break;
+    case K_SIGNING_KEY: printf("Signing-Key"); break;
+    case K_ONION_KEY: printf("Onion-key"); break;
+    case K_LINK_KEY: printf("Link-key"); break;
+    case K_ROUTER_SIGNATURE: printf("Router-signature"); break;
+    case K_PUBLISHED: printf("Published"); break;
+    case K_RUNNING_ROUTERS: printf("Running-routers"); break;
+    case K_PLATFORM: printf("Platform"); break;
+    default:
+      printf("?????? %d\n", tok->tp); return;
+    }
+  for (i = 0; i < tok->val.cmd.n_args; ++i) {
+    printf(" \"%s\"", tok->val.cmd.args[i]);
   }
-  return 1; /* all will reject. */
+  printf("\n");
+  return;
+}
+static int
+router_get_next_token(const char **s, directory_token_t *tok) {
+  int i;
+  i = _router_get_next_token(s, tok);
+  router_dump_token(tok);
+  return i;
 }
+#else
+#define router_get_next_token _router_get_next_token
+#endif
 
-int router_exit_policy_rejects_all(routerinfo_t *router) {
-  if (router_compare_addr_to_exit_policy(0, 0, router->exit_policy) < 0)
-    return 1; /* yes, rejects all */
-  else
-    return 0; /* no, might accept some */
+/* Compute the SHA digest of the substring of s taken from the first
+ * occurrence of start_str through the first newline after the first
+ * subsequent occurrence of end_str; store the 20-byte result in 'digest';
+ * return 0 on success.
+ *
+ * If no such substring exists, return -1.
+ */
+static int router_get_hash_impl(const char *s, char *digest, 
+                                const char *start_str,
+                                const char *end_str)
+{
+  char *start, *end;
+  start = strstr(s, start_str);
+  if (!start) {
+    log_fn(LOG_WARN,"couldn't find \"%s\"",start_str);
+    return -1;
+  }
+  end = strstr(start+strlen(start_str), end_str);
+  if (!end) {
+    log_fn(LOG_WARN,"couldn't find \"%s\"",end_str);
+    return -1;
+  }
+  end = strchr(end, '\n');
+  if (!end) {
+    log_fn(LOG_WARN,"couldn't find EOL");
+    return -1;
+  }
+  ++end;
+
+  if (crypto_SHA_digest(start, end-start, digest)) {
+    log_fn(LOG_WARN,"couldn't compute digest");
+    return -1;
+  }
+
+  return 0;
 }
 
+
+
 /*
   Local Variables:
   mode:c