[tor-commits] [tor/master] Merge branch 'bug30781_034' into bug30781_035

nickm at torproject.org nickm at torproject.org
Tue Jun 11 12:42:04 UTC 2019


commit c8c2e2b8fcc4d99adce600da8210a5e9ae653daf
Merge: e5deb2bbc dc8e3cd5c
Author: teor <teor at torproject.org>
Date:   Thu Jun 6 09:55:44 2019 +1000

    Merge branch 'bug30781_034' into bug30781_035
    
    Moved fix from src/or/routerparse.c to src/feature/dirparse/routerparse.c.

 changes/bug30781                   | 4 ++++
 src/feature/dirparse/routerparse.c | 3 +++
 2 files changed, 7 insertions(+)

diff --cc src/feature/dirparse/routerparse.c
index a81930263,000000000..e44fbf77f
mode 100644,000000..100644
--- a/src/feature/dirparse/routerparse.c
+++ b/src/feature/dirparse/routerparse.c
@@@ -1,1242 -1,0 +1,1245 @@@
 +/* Copyright (c) 2001 Matej Pfajfar.
 + * Copyright (c) 2001-2004, Roger Dingledine.
 + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 + * Copyright (c) 2007-2019, The Tor Project, Inc. */
 +/* See LICENSE for licensing information */
 +
 +/**
 + * \file routerparse.c
 + * \brief Code to parse and validate router descriptors, consenus directories,
 + *   and similar objects.
 + *
 + * The objects parsed by this module use a common text-based metaformat,
 + * documented in dir-spec.txt in torspec.git.  This module is itself divided
 + * into two major kinds of function: code to handle the metaformat, and code
 + * to convert from particular instances of the metaformat into the
 + * objects that Tor uses.
 + *
 + * The generic parsing code works by calling a table-based tokenizer on the
 + * input string.  Each token corresponds to a single line with a token, plus
 + * optional arguments on that line, plus an optional base-64 encoded object
 + * after that line.  Each token has a definition in a table of token_rule_t
 + * entries that describes how many arguments it can take, whether it takes an
 + * object, how many times it may appear, whether it must appear first, and so
 + * on.
 + *
 + * The tokenizer function tokenize_string() converts its string input into a
 + * smartlist full of instances of directory_token_t, according to a provided
 + * table of token_rule_t.
 + *
 + * The generic parts of this module additionally include functions for
 + * finding the start and end of signed information inside a signed object, and
 + * computing the digest that will be signed.
 + *
 + * There are also functions for saving objects to disk that have caused
 + * parsing to fail.
 + *
 + * The specific parts of this module describe conversions between
 + * particular lists of directory_token_t and particular objects.  The
 + * kinds of objects that can be parsed here are:
 + *  <ul>
 + *  <li>router descriptors (managed from routerlist.c)
 + *  <li>extra-info documents (managed from routerlist.c)
 + *  <li>microdescriptors (managed from microdesc.c)
 + *  <li>vote and consensus networkstatus documents, and the routerstatus_t
 + *    objects that they comprise (managed from networkstatus.c)
 + *  <li>detached-signature objects used by authorities for gathering
 + *    signatures on the networkstatus consensus (managed from dirvote.c)
 + *  <li>authority key certificates (managed from routerlist.c)
 + *  <li>hidden service descriptors (managed from rendcommon.c and rendcache.c)
 + * </ul>
 + **/
 +
 +#define EXPOSE_ROUTERDESC_TOKEN_TABLE
 +
 +#include "core/or/or.h"
 +#include "app/config/config.h"
 +#include "core/or/policies.h"
 +#include "core/or/versions.h"
 +#include "feature/dirparse/parsecommon.h"
 +#include "feature/dirparse/policy_parse.h"
 +#include "feature/dirparse/routerparse.h"
 +#include "feature/dirparse/sigcommon.h"
 +#include "feature/dirparse/unparseable.h"
 +#include "feature/nodelist/describe.h"
 +#include "feature/nodelist/nickname.h"
 +#include "feature/nodelist/routerinfo.h"
 +#include "feature/nodelist/routerlist.h"
 +#include "feature/nodelist/torcert.h"
 +#include "feature/relay/router.h"
 +#include "lib/crypt_ops/crypto_curve25519.h"
 +#include "lib/crypt_ops/crypto_ed25519.h"
 +#include "lib/crypt_ops/crypto_format.h"
 +#include "lib/memarea/memarea.h"
 +#include "lib/sandbox/sandbox.h"
 +
 +#include "core/or/addr_policy_st.h"
 +#include "feature/nodelist/extrainfo_st.h"
 +#include "feature/nodelist/routerinfo_st.h"
 +#include "feature/nodelist/routerlist_st.h"
 +
 +/****************************************************************************/
 +
 +/** List of tokens recognized in router descriptors */
 +const token_rule_t routerdesc_token_table[] = {
 +  T0N("reject",              K_REJECT,              ARGS,    NO_OBJ ),
 +  T0N("accept",              K_ACCEPT,              ARGS,    NO_OBJ ),
 +  T0N("reject6",             K_REJECT6,             ARGS,    NO_OBJ ),
 +  T0N("accept6",             K_ACCEPT6,             ARGS,    NO_OBJ ),
 +  T1_START( "router",        K_ROUTER,              GE(5),   NO_OBJ ),
 +  T01("ipv6-policy",         K_IPV6_POLICY,         CONCAT_ARGS, NO_OBJ),
 +  T1( "signing-key",         K_SIGNING_KEY,         NO_ARGS, NEED_KEY_1024 ),
 +  T1( "onion-key",           K_ONION_KEY,           NO_ARGS, NEED_KEY_1024 ),
 +  T01("ntor-onion-key",      K_ONION_KEY_NTOR,      GE(1), NO_OBJ ),
 +  T1_END( "router-signature",    K_ROUTER_SIGNATURE,    NO_ARGS, NEED_OBJ ),
 +  T1( "published",           K_PUBLISHED,       CONCAT_ARGS, NO_OBJ ),
 +  T01("uptime",              K_UPTIME,              GE(1),   NO_OBJ ),
 +  T01("fingerprint",         K_FINGERPRINT,     CONCAT_ARGS, NO_OBJ ),
 +  T01("hibernating",         K_HIBERNATING,         GE(1),   NO_OBJ ),
 +  T01("platform",            K_PLATFORM,        CONCAT_ARGS, NO_OBJ ),
 +  T01("proto",               K_PROTO,           CONCAT_ARGS, NO_OBJ ),
 +  T01("contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ ),
 +  T01("read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ ),
 +  T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
 +  T01("extra-info-digest",   K_EXTRA_INFO_DIGEST,   GE(1),   NO_OBJ ),
 +  T01("hidden-service-dir",  K_HIDDEN_SERVICE_DIR,  NO_ARGS, NO_OBJ ),
 +  T01("identity-ed25519",    K_IDENTITY_ED25519,    NO_ARGS, NEED_OBJ ),
 +  T01("master-key-ed25519",  K_MASTER_KEY_ED25519,  GE(1),   NO_OBJ ),
 +  T01("router-sig-ed25519",  K_ROUTER_SIG_ED25519,  GE(1),   NO_OBJ ),
 +  T01("onion-key-crosscert", K_ONION_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),
 +  T01("ntor-onion-key-crosscert", K_NTOR_ONION_KEY_CROSSCERT,
 +                                                    EQ(1),   NEED_OBJ ),
 +
 +  T01("allow-single-hop-exits",K_ALLOW_SINGLE_HOP_EXITS,    NO_ARGS, NO_OBJ ),
 +
 +  T01("family",              K_FAMILY,              ARGS,    NO_OBJ ),
 +  T01("caches-extra-info",   K_CACHES_EXTRA_INFO,   NO_ARGS, NO_OBJ ),
 +  T0N("or-address",          K_OR_ADDRESS,          GE(1),   NO_OBJ ),
 +
 +  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
 +  T1( "bandwidth",           K_BANDWIDTH,           GE(3),   NO_OBJ ),
 +  A01("@purpose",            A_PURPOSE,             GE(1),   NO_OBJ ),
 +  T01("tunnelled-dir-server",K_DIR_TUNNELLED,       NO_ARGS, NO_OBJ ),
 +
 +  END_OF_TABLE
 +};
 +
 +/** List of tokens recognized in extra-info documents. */
 +static token_rule_t extrainfo_token_table[] = {
 +  T1_END( "router-signature",    K_ROUTER_SIGNATURE,    NO_ARGS, NEED_OBJ ),
 +  T1( "published",           K_PUBLISHED,       CONCAT_ARGS, NO_OBJ ),
 +  T01("identity-ed25519",    K_IDENTITY_ED25519,    NO_ARGS, NEED_OBJ ),
 +  T01("router-sig-ed25519",  K_ROUTER_SIG_ED25519,  GE(1),   NO_OBJ ),
 +  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
 +  T01("read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ ),
 +  T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
 +  T01("dirreq-stats-end",    K_DIRREQ_END,          ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-ips",       K_DIRREQ_V2_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-ips",       K_DIRREQ_V3_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-reqs",      K_DIRREQ_V2_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-reqs",      K_DIRREQ_V3_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-share",     K_DIRREQ_V2_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-share",     K_DIRREQ_V3_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-resp",      K_DIRREQ_V2_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-resp",      K_DIRREQ_V3_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-direct-dl", K_DIRREQ_V2_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-direct-dl", K_DIRREQ_V3_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-tunneled-dl", K_DIRREQ_V2_TUN,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-tunneled-dl", K_DIRREQ_V3_TUN,     ARGS,    NO_OBJ ),
 +  T01("entry-stats-end",     K_ENTRY_END,           ARGS,    NO_OBJ ),
 +  T01("entry-ips",           K_ENTRY_IPS,           ARGS,    NO_OBJ ),
 +  T01("cell-stats-end",      K_CELL_END,            ARGS,    NO_OBJ ),
 +  T01("cell-processed-cells", K_CELL_PROCESSED,     ARGS,    NO_OBJ ),
 +  T01("cell-queued-cells",   K_CELL_QUEUED,         ARGS,    NO_OBJ ),
 +  T01("cell-time-in-queue",  K_CELL_TIME,           ARGS,    NO_OBJ ),
 +  T01("cell-circuits-per-decile", K_CELL_CIRCS,     ARGS,    NO_OBJ ),
 +  T01("exit-stats-end",      K_EXIT_END,            ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-written", K_EXIT_WRITTEN,     ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-read", K_EXIT_READ,           ARGS,    NO_OBJ ),
 +  T01("exit-streams-opened", K_EXIT_OPENED,         ARGS,    NO_OBJ ),
 +
 +  T1_START( "extra-info",          K_EXTRA_INFO,          GE(2),   NO_OBJ ),
 +
 +  END_OF_TABLE
 +};
 +
 +#undef T
 +
 +/* static function prototypes */
 +static int router_add_exit_policy(routerinfo_t *router,directory_token_t *tok);
 +static smartlist_t *find_all_exitpolicy(smartlist_t *s);
 +
 +/** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
 + * <b>s</b>. Return 0 on success, -1 on failure.
 + */
 +int
 +router_get_router_hash(const char *s, size_t s_len, char *digest)
 +{
 +  return router_get_hash_impl(s, s_len, digest,
 +                              "router ","\nrouter-signature", '\n',
 +                              DIGEST_SHA1);
 +}
 +
 +/** Set <b>digest</b> to the SHA-1 digest of the hash of the <b>s_len</b>-byte
 + * extrainfo string at <b>s</b>.  Return 0 on success, -1 on failure. */
 +int
 +router_get_extrainfo_hash(const char *s, size_t s_len, char *digest)
 +{
 +  return router_get_hash_impl(s, s_len, digest, "extra-info",
 +                              "\nrouter-signature",'\n', DIGEST_SHA1);
 +}
 +
 +/** Helper: move *<b>s_ptr</b> ahead to the next router, the next extra-info,
 + * or to the first of the annotations proceeding the next router or
 + * extra-info---whichever comes first.  Set <b>is_extrainfo_out</b> to true if
 + * we found an extrainfo, or false if found a router. Do not scan beyond
 + * <b>eos</b>.  Return -1 if we found nothing; 0 if we found something. */
 +static int
 +find_start_of_next_router_or_extrainfo(const char **s_ptr,
 +                                       const char *eos,
 +                                       int *is_extrainfo_out)
 +{
 +  const char *annotations = NULL;
 +  const char *s = *s_ptr;
 +
 +  s = eat_whitespace_eos(s, eos);
 +
 +  while (s < eos-32) {  /* 32 gives enough room for a the first keyword. */
 +    /* We're at the start of a line. */
 +    tor_assert(*s != '\n');
 +
 +    if (*s == '@' && !annotations) {
 +      annotations = s;
 +    } else if (*s == 'r' && !strcmpstart(s, "router ")) {
 +      *s_ptr = annotations ? annotations : s;
 +      *is_extrainfo_out = 0;
 +      return 0;
 +    } else if (*s == 'e' && !strcmpstart(s, "extra-info ")) {
 +      *s_ptr = annotations ? annotations : s;
 +      *is_extrainfo_out = 1;
 +      return 0;
 +    }
 +
 +    if (!(s = memchr(s+1, '\n', eos-(s+1))))
 +      break;
 +    s = eat_whitespace_eos(s, eos);
 +  }
 +  return -1;
 +}
 +
 +/** Given a string *<b>s</b> containing a concatenated sequence of router
 + * descriptors (or extra-info documents if <b>want_extrainfo</b> is set),
 + * parses them and stores the result in <b>dest</b>. All routers are marked
 + * running and valid. Advances *s to a point immediately following the last
 + * router entry. Ignore any trailing router entries that are not complete.
 + *
 + * If <b>saved_location</b> isn't SAVED_IN_CACHE, make a local copy of each
 + * descriptor in the signed_descriptor_body field of each routerinfo_t.  If it
 + * isn't SAVED_NOWHERE, remember the offset of each descriptor.
 + *
 + * Returns 0 on success and -1 on failure.  Adds a digest to
 + * <b>invalid_digests_out</b> for every entry that was unparseable or
 + * invalid. (This may cause duplicate entries.)
 + */
 +int
 +router_parse_list_from_string(const char **s, const char *eos,
 +                              smartlist_t *dest,
 +                              saved_location_t saved_location,
 +                              int want_extrainfo,
 +                              int allow_annotations,
 +                              const char *prepend_annotations,
 +                              smartlist_t *invalid_digests_out)
 +{
 +  routerinfo_t *router;
 +  extrainfo_t *extrainfo;
 +  signed_descriptor_t *signed_desc = NULL;
 +  void *elt;
 +  const char *end, *start;
 +  int have_extrainfo;
 +
 +  tor_assert(s);
 +  tor_assert(*s);
 +  tor_assert(dest);
 +
 +  start = *s;
 +  if (!eos)
 +    eos = *s + strlen(*s);
 +
 +  tor_assert(eos >= *s);
 +
 +  while (1) {
 +    char raw_digest[DIGEST_LEN];
 +    int have_raw_digest = 0;
 +    int dl_again = 0;
 +    if (find_start_of_next_router_or_extrainfo(s, eos, &have_extrainfo) < 0)
 +      break;
 +
 +    end = tor_memstr(*s, eos-*s, "\nrouter-signature");
 +    if (end)
 +      end = tor_memstr(end, eos-end, "\n-----END SIGNATURE-----\n");
 +    if (end)
 +      end += strlen("\n-----END SIGNATURE-----\n");
 +
 +    if (!end)
 +      break;
 +
 +    elt = NULL;
 +
 +    if (have_extrainfo && want_extrainfo) {
 +      routerlist_t *rl = router_get_routerlist();
 +      have_raw_digest = router_get_extrainfo_hash(*s, end-*s, raw_digest) == 0;
 +      extrainfo = extrainfo_parse_entry_from_string(*s, end,
 +                                       saved_location != SAVED_IN_CACHE,
 +                                       rl->identity_map, &dl_again);
 +      if (extrainfo) {
 +        signed_desc = &extrainfo->cache_info;
 +        elt = extrainfo;
 +      }
 +    } else if (!have_extrainfo && !want_extrainfo) {
 +      have_raw_digest = router_get_router_hash(*s, end-*s, raw_digest) == 0;
 +      router = router_parse_entry_from_string(*s, end,
 +                                              saved_location != SAVED_IN_CACHE,
 +                                              allow_annotations,
 +                                              prepend_annotations, &dl_again);
 +      if (router) {
 +        log_debug(LD_DIR, "Read router '%s', purpose '%s'",
 +                  router_describe(router),
 +                  router_purpose_to_string(router->purpose));
 +        signed_desc = &router->cache_info;
 +        elt = router;
 +      }
 +    }
 +    if (! elt && ! dl_again && have_raw_digest && invalid_digests_out) {
 +      smartlist_add(invalid_digests_out, tor_memdup(raw_digest, DIGEST_LEN));
 +    }
 +    if (!elt) {
 +      *s = end;
 +      continue;
 +    }
 +    if (saved_location != SAVED_NOWHERE) {
 +      tor_assert(signed_desc);
 +      signed_desc->saved_location = saved_location;
 +      signed_desc->saved_offset = *s - start;
 +    }
 +    *s = end;
 +    smartlist_add(dest, elt);
 +  }
 +
 +  return 0;
 +}
 +
 +/** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's
 + * with at least one argument (use GE(1) in setup). If found, store
 + * address and port number to <b>addr_out</b> and
 + * <b>port_out</b>. Return number of OR ports found. */
 +int
 +find_single_ipv6_orport(const smartlist_t *list,
 +                        tor_addr_t *addr_out,
 +                        uint16_t *port_out)
 +{
 +  int ret = 0;
 +  tor_assert(list != NULL);
 +  tor_assert(addr_out != NULL);
 +  tor_assert(port_out != NULL);
 +
 +  SMARTLIST_FOREACH_BEGIN(list, directory_token_t *, t) {
 +    tor_addr_t a;
 +    maskbits_t bits;
 +    uint16_t port_min, port_max;
 +    tor_assert(t->n_args >= 1);
 +    /* XXXX Prop186 the full spec allows much more than this. */
 +    if (tor_addr_parse_mask_ports(t->args[0], 0,
 +                                  &a, &bits, &port_min,
 +                                  &port_max) == AF_INET6 &&
 +        bits == 128 &&
 +        port_min == port_max) {
 +      /* Okay, this is one we can understand. Use it and ignore
 +         any potential more addresses in list. */
 +      tor_addr_copy(addr_out, &a);
 +      *port_out = port_min;
 +      ret = 1;
 +      break;
 +    }
 +  } SMARTLIST_FOREACH_END(t);
 +
 +  return ret;
 +}
 +
 +/** Helper function: reads a single router entry from *<b>s</b> ...
 + * *<b>end</b>.  Mallocs a new router and returns it if all goes well, else
 + * returns NULL.  If <b>cache_copy</b> is true, duplicate the contents of
 + * s through end into the signed_descriptor_body of the resulting
 + * routerinfo_t.
 + *
 + * If <b>end</b> is NULL, <b>s</b> must be properly NUL-terminated.
 + *
 + * If <b>allow_annotations</b>, it's okay to encounter annotations in <b>s</b>
 + * before the router; if it's false, reject the router if it's annotated.  If
 + * <b>prepend_annotations</b> is set, it should contain some annotations:
 + * append them to the front of the router before parsing it, and keep them
 + * around when caching the router.
 + *
 + * Only one of allow_annotations and prepend_annotations may be set.
 + *
 + * If <b>can_dl_again_out</b> is provided, set *<b>can_dl_again_out</b> to 1
 + * if it's okay to try to download a descriptor with this same digest again,
 + * and 0 if it isn't.  (It might not be okay to download it again if part of
 + * the part covered by the digest is invalid.)
 + */
 +routerinfo_t *
 +router_parse_entry_from_string(const char *s, const char *end,
 +                               int cache_copy, int allow_annotations,
 +                               const char *prepend_annotations,
 +                               int *can_dl_again_out)
 +{
 +  routerinfo_t *router = NULL;
 +  char digest[128];
 +  smartlist_t *tokens = NULL, *exit_policy_tokens = NULL;
 +  directory_token_t *tok;
 +  struct in_addr in;
 +  const char *start_of_annotations, *cp, *s_dup = s;
 +  size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
 +  int ok = 1;
 +  memarea_t *area = NULL;
 +  tor_cert_t *ntor_cc_cert = NULL;
 +  /* Do not set this to '1' until we have parsed everything that we intend to
 +   * parse that's covered by the hash. */
 +  int can_dl_again = 0;
 +  crypto_pk_t *rsa_pubkey = NULL;
 +
 +  tor_assert(!allow_annotations || !prepend_annotations);
 +
 +  if (!end) {
 +    end = s + strlen(s);
 +  }
 +
 +  /* point 'end' to a point immediately after the final newline. */
 +  while (end > s+2 && *(end-1) == '\n' && *(end-2) == '\n')
 +    --end;
 +
 +  area = memarea_new();
 +  tokens = smartlist_new();
 +  if (prepend_annotations) {
 +    if (tokenize_string(area,prepend_annotations,NULL,tokens,
 +                        routerdesc_token_table,TS_NOCHECK)) {
 +      log_warn(LD_DIR, "Error tokenizing router descriptor (annotations).");
 +      goto err;
 +    }
 +  }
 +
 +  start_of_annotations = s;
 +  cp = tor_memstr(s, end-s, "\nrouter ");
 +  if (!cp) {
 +    if (end-s < 7 || strcmpstart(s, "router ")) {
 +      log_warn(LD_DIR, "No router keyword found.");
 +      goto err;
 +    }
 +  } else {
 +    s = cp+1;
 +  }
 +
 +  if (start_of_annotations != s) { /* We have annotations */
 +    if (allow_annotations) {
 +      if (tokenize_string(area,start_of_annotations,s,tokens,
 +                          routerdesc_token_table,TS_NOCHECK)) {
 +        log_warn(LD_DIR, "Error tokenizing router descriptor (annotations).");
 +        goto err;
 +      }
 +    } else {
 +      log_warn(LD_DIR, "Found unexpected annotations on router descriptor not "
 +               "loaded from disk.  Dropping it.");
 +      goto err;
 +    }
 +  }
 +
 +  if (router_get_router_hash(s, end - s, digest) < 0) {
 +    log_warn(LD_DIR, "Couldn't compute router hash.");
 +    goto err;
 +  }
 +  {
 +    int flags = 0;
 +    if (allow_annotations)
 +      flags |= TS_ANNOTATIONS_OK;
 +    if (prepend_annotations)
 +      flags |= TS_ANNOTATIONS_OK|TS_NO_NEW_ANNOTATIONS;
 +
 +    if (tokenize_string(area,s,end,tokens,routerdesc_token_table, flags)) {
 +      log_warn(LD_DIR, "Error tokenizing router descriptor.");
 +      goto err;
 +    }
 +  }
 +
 +  if (smartlist_len(tokens) < 2) {
 +    log_warn(LD_DIR, "Impossibly short router descriptor.");
 +    goto err;
 +  }
 +
 +  tok = find_by_keyword(tokens, K_ROUTER);
 +  const int router_token_pos = smartlist_pos(tokens, tok);
 +  tor_assert(tok->n_args >= 5);
 +
 +  router = tor_malloc_zero(sizeof(routerinfo_t));
 +  router->cert_expiration_time = TIME_MAX;
 +  router->cache_info.routerlist_index = -1;
 +  router->cache_info.annotations_len = s-start_of_annotations + prepend_len;
 +  router->cache_info.signed_descriptor_len = end-s;
 +  if (cache_copy) {
 +    size_t len = router->cache_info.signed_descriptor_len +
 +                 router->cache_info.annotations_len;
 +    char *signed_body =
 +      router->cache_info.signed_descriptor_body = tor_malloc(len+1);
 +    if (prepend_annotations) {
 +      memcpy(signed_body, prepend_annotations, prepend_len);
 +      signed_body += prepend_len;
 +    }
 +    /* This assertion will always succeed.
 +     * len == signed_desc_len + annotations_len
 +     *     == end-s + s-start_of_annotations + prepend_len
 +     *     == end-start_of_annotations + prepend_len
 +     * We already wrote prepend_len bytes into the buffer; now we're
 +     * writing end-start_of_annotations -NM. */
 +    tor_assert(signed_body+(end-start_of_annotations) ==
 +               router->cache_info.signed_descriptor_body+len);
 +    memcpy(signed_body, start_of_annotations, end-start_of_annotations);
 +    router->cache_info.signed_descriptor_body[len] = '\0';
 +    tor_assert(strlen(router->cache_info.signed_descriptor_body) == len);
 +  }
 +  memcpy(router->cache_info.signed_descriptor_digest, digest, DIGEST_LEN);
 +
 +  router->nickname = tor_strdup(tok->args[0]);
 +  if (!is_legal_nickname(router->nickname)) {
 +    log_warn(LD_DIR,"Router nickname is invalid");
 +    goto err;
 +  }
 +  if (!tor_inet_aton(tok->args[1], &in)) {
 +    log_warn(LD_DIR,"Router address is not an IP address.");
 +    goto err;
 +  }
 +  router->addr = ntohl(in.s_addr);
 +
 +  router->or_port =
 +    (uint16_t) tor_parse_long(tok->args[2],10,0,65535,&ok,NULL);
 +  if (!ok) {
 +    log_warn(LD_DIR,"Invalid OR port %s", escaped(tok->args[2]));
 +    goto err;
 +  }
 +  router->dir_port =
 +    (uint16_t) tor_parse_long(tok->args[4],10,0,65535,&ok,NULL);
 +  if (!ok) {
 +    log_warn(LD_DIR,"Invalid dir port %s", escaped(tok->args[4]));
 +    goto err;
 +  }
 +
 +  tok = find_by_keyword(tokens, K_BANDWIDTH);
 +  tor_assert(tok->n_args >= 3);
 +  router->bandwidthrate = (int)
 +    tor_parse_long(tok->args[0],10,1,INT_MAX,&ok,NULL);
 +
 +  if (!ok) {
 +    log_warn(LD_DIR, "bandwidthrate %s unreadable or 0. Failing.",
 +             escaped(tok->args[0]));
 +    goto err;
 +  }
 +  router->bandwidthburst =
 +    (int) tor_parse_long(tok->args[1],10,0,INT_MAX,&ok,NULL);
 +  if (!ok) {
 +    log_warn(LD_DIR, "Invalid bandwidthburst %s", escaped(tok->args[1]));
 +    goto err;
 +  }
 +  router->bandwidthcapacity = (int)
 +    tor_parse_long(tok->args[2],10,0,INT_MAX,&ok,NULL);
 +  if (!ok) {
 +    log_warn(LD_DIR, "Invalid bandwidthcapacity %s", escaped(tok->args[1]));
 +    goto err;
 +  }
 +
 +  if ((tok = find_opt_by_keyword(tokens, A_PURPOSE))) {
 +    tor_assert(tok->n_args);
 +    router->purpose = router_purpose_from_string(tok->args[0]);
++    if (router->purpose == ROUTER_PURPOSE_UNKNOWN) {
++      goto err;
++    }
 +  } else {
 +    router->purpose = ROUTER_PURPOSE_GENERAL;
 +  }
 +  router->cache_info.send_unencrypted =
 +    (router->purpose == ROUTER_PURPOSE_GENERAL) ? 1 : 0;
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_UPTIME))) {
 +    tor_assert(tok->n_args >= 1);
 +    router->uptime = tor_parse_long(tok->args[0],10,0,LONG_MAX,&ok,NULL);
 +    if (!ok) {
 +      log_warn(LD_DIR, "Invalid uptime %s", escaped(tok->args[0]));
 +      goto err;
 +    }
 +  }
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_HIBERNATING))) {
 +    tor_assert(tok->n_args >= 1);
 +    router->is_hibernating
 +      = (tor_parse_long(tok->args[0],10,0,LONG_MAX,NULL,NULL) != 0);
 +  }
 +
 +  tok = find_by_keyword(tokens, K_PUBLISHED);
 +  tor_assert(tok->n_args == 1);
 +  if (parse_iso_time(tok->args[0], &router->cache_info.published_on) < 0)
 +    goto err;
 +
 +  tok = find_by_keyword(tokens, K_ONION_KEY);
 +  if (!crypto_pk_public_exponent_ok(tok->key)) {
 +    log_warn(LD_DIR,
 +             "Relay's onion key had invalid exponent.");
 +    goto err;
 +  }
 +  router_set_rsa_onion_pkey(tok->key, &router->onion_pkey,
 +                            &router->onion_pkey_len);
 +  crypto_pk_free(tok->key);
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) {
 +    curve25519_public_key_t k;
 +    tor_assert(tok->n_args >= 1);
 +    if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
 +      log_warn(LD_DIR, "Bogus ntor-onion-key in routerinfo");
 +      goto err;
 +    }
 +    router->onion_curve25519_pkey =
 +      tor_memdup(&k, sizeof(curve25519_public_key_t));
 +  }
 +
 +  tok = find_by_keyword(tokens, K_SIGNING_KEY);
 +  router->identity_pkey = tok->key;
 +  tok->key = NULL; /* Prevent free */
 +  if (crypto_pk_get_digest(router->identity_pkey,
 +                           router->cache_info.identity_digest)) {
 +    log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
 +  }
 +
 +  {
 +    directory_token_t *ed_sig_tok, *ed_cert_tok, *cc_tap_tok, *cc_ntor_tok,
 +      *master_key_tok;
 +    ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
 +    ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
 +    master_key_tok = find_opt_by_keyword(tokens, K_MASTER_KEY_ED25519);
 +    cc_tap_tok = find_opt_by_keyword(tokens, K_ONION_KEY_CROSSCERT);
 +    cc_ntor_tok = find_opt_by_keyword(tokens, K_NTOR_ONION_KEY_CROSSCERT);
 +    int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok +
 +      !!cc_tap_tok + !!cc_ntor_tok;
 +    if ((n_ed_toks != 0 && n_ed_toks != 4) ||
 +        (n_ed_toks == 4 && !router->onion_curve25519_pkey)) {
 +      log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
 +               "cross-certification support");
 +      goto err;
 +    }
 +    if (master_key_tok && !ed_sig_tok) {
 +      log_warn(LD_DIR, "Router descriptor has ed25519 master key but no "
 +               "certificate");
 +      goto err;
 +    }
 +    if (ed_sig_tok) {
 +      tor_assert(ed_cert_tok && cc_tap_tok && cc_ntor_tok);
 +      const int ed_cert_token_pos = smartlist_pos(tokens, ed_cert_tok);
 +      if (ed_cert_token_pos == -1 || router_token_pos == -1 ||
 +          (ed_cert_token_pos != router_token_pos + 1 &&
 +           ed_cert_token_pos != router_token_pos - 1)) {
 +        log_warn(LD_DIR, "Ed25519 certificate in wrong position");
 +        goto err;
 +      }
 +      if (ed_sig_tok != smartlist_get(tokens, smartlist_len(tokens)-2)) {
 +        log_warn(LD_DIR, "Ed25519 signature in wrong position");
 +        goto err;
 +      }
 +      if (strcmp(ed_cert_tok->object_type, "ED25519 CERT")) {
 +        log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
 +        goto err;
 +      }
 +      if (strcmp(cc_ntor_tok->object_type, "ED25519 CERT")) {
 +        log_warn(LD_DIR, "Wrong object type on ntor-onion-key-crosscert "
 +                 "in decriptor");
 +        goto err;
 +      }
 +      if (strcmp(cc_tap_tok->object_type, "CROSSCERT")) {
 +        log_warn(LD_DIR, "Wrong object type on onion-key-crosscert "
 +                 "in decriptor");
 +        goto err;
 +      }
 +      if (strcmp(cc_ntor_tok->args[0], "0") &&
 +          strcmp(cc_ntor_tok->args[0], "1")) {
 +        log_warn(LD_DIR, "Bad sign bit on ntor-onion-key-crosscert");
 +        goto err;
 +      }
 +      int ntor_cc_sign_bit = !strcmp(cc_ntor_tok->args[0], "1");
 +
 +      uint8_t d256[DIGEST256_LEN];
 +      const char *signed_start, *signed_end;
 +      tor_cert_t *cert = tor_cert_parse(
 +                       (const uint8_t*)ed_cert_tok->object_body,
 +                       ed_cert_tok->object_size);
 +      if (! cert) {
 +        log_warn(LD_DIR, "Couldn't parse ed25519 cert");
 +        goto err;
 +      }
 +      /* makes sure it gets freed. */
 +      router->cache_info.signing_key_cert = cert;
 +
 +      if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
 +          ! cert->signing_key_included) {
 +        log_warn(LD_DIR, "Invalid form for ed25519 cert");
 +        goto err;
 +      }
 +
 +      if (master_key_tok) {
 +        /* This token is optional, but if it's present, it must match
 +         * the signature in the signing cert, or supplant it. */
 +        tor_assert(master_key_tok->n_args >= 1);
 +        ed25519_public_key_t pkey;
 +        if (ed25519_public_from_base64(&pkey, master_key_tok->args[0])<0) {
 +          log_warn(LD_DIR, "Can't parse ed25519 master key");
 +          goto err;
 +        }
 +
 +        if (fast_memneq(&cert->signing_key.pubkey,
 +                        pkey.pubkey, ED25519_PUBKEY_LEN)) {
 +          log_warn(LD_DIR, "Ed25519 master key does not match "
 +                   "key in certificate");
 +          goto err;
 +        }
 +      }
 +      ntor_cc_cert = tor_cert_parse((const uint8_t*)cc_ntor_tok->object_body,
 +                                    cc_ntor_tok->object_size);
 +      if (!ntor_cc_cert) {
 +        log_warn(LD_DIR, "Couldn't parse ntor-onion-key-crosscert cert");
 +        goto err;
 +      }
 +      if (ntor_cc_cert->cert_type != CERT_TYPE_ONION_ID ||
 +          ! ed25519_pubkey_eq(&ntor_cc_cert->signed_key, &cert->signing_key)) {
 +        log_warn(LD_DIR, "Invalid contents for ntor-onion-key-crosscert cert");
 +        goto err;
 +      }
 +
 +      ed25519_public_key_t ntor_cc_pk;
 +      if (ed25519_public_key_from_curve25519_public_key(&ntor_cc_pk,
 +                                            router->onion_curve25519_pkey,
 +                                            ntor_cc_sign_bit)<0) {
 +        log_warn(LD_DIR, "Error converting onion key to ed25519");
 +        goto err;
 +      }
 +
 +      if (router_get_hash_impl_helper(s, end-s, "router ",
 +                                      "\nrouter-sig-ed25519",
 +                                      ' ', LOG_WARN,
 +                                      &signed_start, &signed_end) < 0) {
 +        log_warn(LD_DIR, "Can't find ed25519-signed portion of descriptor");
 +        goto err;
 +      }
 +      crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
 +      crypto_digest_add_bytes(d, ED_DESC_SIGNATURE_PREFIX,
 +        strlen(ED_DESC_SIGNATURE_PREFIX));
 +      crypto_digest_add_bytes(d, signed_start, signed_end-signed_start);
 +      crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
 +      crypto_digest_free(d);
 +
 +      ed25519_checkable_t check[3];
 +      int check_ok[3];
 +      time_t expires = TIME_MAX;
 +      if (tor_cert_get_checkable_sig(&check[0], cert, NULL, &expires) < 0) {
 +        log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
 +        goto err;
 +      }
 +      if (tor_cert_get_checkable_sig(&check[1],
 +                               ntor_cc_cert, &ntor_cc_pk, &expires) < 0) {
 +        log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
 +        goto err;
 +      }
 +
 +      if (ed25519_signature_from_base64(&check[2].signature,
 +                                        ed_sig_tok->args[0])<0) {
 +        log_warn(LD_DIR, "Couldn't decode ed25519 signature");
 +        goto err;
 +      }
 +      check[2].pubkey = &cert->signed_key;
 +      check[2].msg = d256;
 +      check[2].len = DIGEST256_LEN;
 +
 +      if (ed25519_checksig_batch(check_ok, check, 3) < 0) {
 +        log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
 +        goto err;
 +      }
 +
 +      rsa_pubkey = router_get_rsa_onion_pkey(router->onion_pkey,
 +                                             router->onion_pkey_len);
 +      if (check_tap_onion_key_crosscert(
 +                      (const uint8_t*)cc_tap_tok->object_body,
 +                      (int)cc_tap_tok->object_size,
 +                      rsa_pubkey,
 +                      &cert->signing_key,
 +                      (const uint8_t*)router->cache_info.identity_digest)<0) {
 +        log_warn(LD_DIR, "Incorrect TAP cross-verification");
 +        goto err;
 +      }
 +
 +      /* We check this before adding it to the routerlist. */
 +      router->cert_expiration_time = expires;
 +    }
 +  }
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_FINGERPRINT))) {
 +    /* If there's a fingerprint line, it must match the identity digest. */
 +    char d[DIGEST_LEN];
 +    tor_assert(tok->n_args == 1);
 +    tor_strstrip(tok->args[0], " ");
 +    if (base16_decode(d, DIGEST_LEN,
 +                      tok->args[0], strlen(tok->args[0])) != DIGEST_LEN) {
 +      log_warn(LD_DIR, "Couldn't decode router fingerprint %s",
 +               escaped(tok->args[0]));
 +      goto err;
 +    }
 +    if (tor_memneq(d,router->cache_info.identity_digest, DIGEST_LEN)) {
 +      log_warn(LD_DIR, "Fingerprint '%s' does not match identity digest.",
 +               tok->args[0]);
 +      goto err;
 +    }
 +  }
 +
 +  {
 +    const char *version = NULL, *protocols = NULL;
 +    if ((tok = find_opt_by_keyword(tokens, K_PLATFORM))) {
 +      router->platform = tor_strdup(tok->args[0]);
 +      version = tok->args[0];
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_PROTO))) {
 +      router->protocol_list = tor_strdup(tok->args[0]);
 +      protocols = tok->args[0];
 +    }
 +
 +    summarize_protover_flags(&router->pv, protocols, version);
 +  }
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_CONTACT))) {
 +    router->contact_info = tor_strdup(tok->args[0]);
 +  }
 +
 +  if (find_opt_by_keyword(tokens, K_REJECT6) ||
 +      find_opt_by_keyword(tokens, K_ACCEPT6)) {
 +    log_warn(LD_DIR, "Rejecting router with reject6/accept6 line: they crash "
 +             "older Tors.");
 +    goto err;
 +  }
 +  {
 +    smartlist_t *or_addresses = find_all_by_keyword(tokens, K_OR_ADDRESS);
 +    if (or_addresses) {
 +      find_single_ipv6_orport(or_addresses, &router->ipv6_addr,
 +                              &router->ipv6_orport);
 +      smartlist_free(or_addresses);
 +    }
 +  }
 +  exit_policy_tokens = find_all_exitpolicy(tokens);
 +  if (!smartlist_len(exit_policy_tokens)) {
 +    log_warn(LD_DIR, "No exit policy tokens in descriptor.");
 +    goto err;
 +  }
 +  SMARTLIST_FOREACH(exit_policy_tokens, directory_token_t *, t,
 +                    if (router_add_exit_policy(router,t)<0) {
 +                      log_warn(LD_DIR,"Error in exit policy");
 +                      goto err;
 +                    });
 +  policy_expand_private(&router->exit_policy);
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_IPV6_POLICY)) && tok->n_args) {
 +    router->ipv6_exit_policy = parse_short_policy(tok->args[0]);
 +    if (! router->ipv6_exit_policy) {
 +      log_warn(LD_DIR , "Error in ipv6-policy %s", escaped(tok->args[0]));
 +      goto err;
 +    }
 +  }
 +
 +  if (policy_is_reject_star(router->exit_policy, AF_INET, 1) &&
 +      (!router->ipv6_exit_policy ||
 +       short_policy_is_reject_star(router->ipv6_exit_policy)))
 +    router->policy_is_reject_star = 1;
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_FAMILY)) && tok->n_args) {
 +    int i;
 +    router->declared_family = smartlist_new();
 +    for (i=0;i<tok->n_args;++i) {
 +      if (!is_legal_nickname_or_hexdigest(tok->args[i])) {
 +        log_warn(LD_DIR, "Illegal nickname %s in family line",
 +                 escaped(tok->args[i]));
 +        goto err;
 +      }
 +      smartlist_add_strdup(router->declared_family, tok->args[i]);
 +    }
 +  }
 +
 +  if (find_opt_by_keyword(tokens, K_CACHES_EXTRA_INFO))
 +    router->caches_extra_info = 1;
 +
 +  if (find_opt_by_keyword(tokens, K_ALLOW_SINGLE_HOP_EXITS))
 +    router->allow_single_hop_exits = 1;
 +
 +  if ((tok = find_opt_by_keyword(tokens, K_EXTRA_INFO_DIGEST))) {
 +    tor_assert(tok->n_args >= 1);
 +    if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
 +      if (base16_decode(router->cache_info.extra_info_digest, DIGEST_LEN,
 +                        tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN) {
 +          log_warn(LD_DIR,"Invalid extra info digest");
 +      }
 +    } else {
 +      log_warn(LD_DIR, "Invalid extra info digest %s", escaped(tok->args[0]));
 +    }
 +
 +    if (tok->n_args >= 2) {
 +      if (digest256_from_base64(router->cache_info.extra_info_digest256,
 +                                tok->args[1]) < 0) {
 +        log_warn(LD_DIR, "Invalid extra info digest256 %s",
 +                 escaped(tok->args[1]));
 +      }
 +    }
 +  }
 +
 +  if (find_opt_by_keyword(tokens, K_HIDDEN_SERVICE_DIR)) {
 +    router->wants_to_be_hs_dir = 1;
 +  }
 +
 +  /* This router accepts tunnelled directory requests via begindir if it has
 +   * an open dirport or it included "tunnelled-dir-server". */
 +  if (find_opt_by_keyword(tokens, K_DIR_TUNNELLED) || router->dir_port > 0) {
 +    router->supports_tunnelled_dir_requests = 1;
 +  }
 +
 +  tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
 +
 +  if (!router->or_port) {
 +    log_warn(LD_DIR,"or_port unreadable or 0. Failing.");
 +    goto err;
 +  }
 +
 +  /* We've checked everything that's covered by the hash. */
 +  can_dl_again = 1;
 +  if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0,
 +                            "router descriptor") < 0)
 +    goto err;
 +
 +  if (!router->platform) {
 +    router->platform = tor_strdup("<unknown>");
 +  }
 +  goto done;
 +
 + err:
 +  dump_desc(s_dup, "router descriptor");
 +  routerinfo_free(router);
 +  router = NULL;
 + done:
 +  crypto_pk_free(rsa_pubkey);
 +  tor_cert_free(ntor_cc_cert);
 +  if (tokens) {
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
 +    smartlist_free(tokens);
 +  }
 +  smartlist_free(exit_policy_tokens);
 +  if (area) {
 +    DUMP_AREA(area, "routerinfo");
 +    memarea_drop_all(area);
 +  }
 +  if (can_dl_again_out)
 +    *can_dl_again_out = can_dl_again;
 +  return router;
 +}
 +
 +/** Parse a single extrainfo entry from the string <b>s</b>, ending at
 + * <b>end</b>.  (If <b>end</b> is NULL, parse up to the end of <b>s</b>.)  If
 + * <b>cache_copy</b> is true, make a copy of the extra-info document in the
 + * cache_info fields of the result.  If <b>routermap</b> is provided, use it
 + * as a map from router identity to routerinfo_t when looking up signing keys.
 + *
 + * If <b>can_dl_again_out</b> is provided, set *<b>can_dl_again_out</b> to 1
 + * if it's okay to try to download an extrainfo with this same digest again,
 + * and 0 if it isn't.  (It might not be okay to download it again if part of
 + * the part covered by the digest is invalid.)
 + */
 +extrainfo_t *
 +extrainfo_parse_entry_from_string(const char *s, const char *end,
 +                            int cache_copy, struct digest_ri_map_t *routermap,
 +                            int *can_dl_again_out)
 +{
 +  extrainfo_t *extrainfo = NULL;
 +  char digest[128];
 +  smartlist_t *tokens = NULL;
 +  directory_token_t *tok;
 +  crypto_pk_t *key = NULL;
 +  routerinfo_t *router = NULL;
 +  memarea_t *area = NULL;
 +  const char *s_dup = s;
 +  /* Do not set this to '1' until we have parsed everything that we intend to
 +   * parse that's covered by the hash. */
 +  int can_dl_again = 0;
 +
 +  if (BUG(s == NULL))
 +    return NULL;
 +
 +  if (!end) {
 +    end = s + strlen(s);
 +  }
 +
 +  /* point 'end' to a point immediately after the final newline. */
 +  while (end > s+2 && *(end-1) == '\n' && *(end-2) == '\n')
 +    --end;
 +
 +  if (router_get_extrainfo_hash(s, end-s, digest) < 0) {
 +    log_warn(LD_DIR, "Couldn't compute router hash.");
 +    goto err;
 +  }
 +  tokens = smartlist_new();
 +  area = memarea_new();
 +  if (tokenize_string(area,s,end,tokens,extrainfo_token_table,0)) {
 +    log_warn(LD_DIR, "Error tokenizing extra-info document.");
 +    goto err;
 +  }
 +
 +  if (smartlist_len(tokens) < 2) {
 +    log_warn(LD_DIR, "Impossibly short extra-info document.");
 +    goto err;
 +  }
 +
 +  /* XXXX Accept this in position 1 too, and ed identity in position 0. */
 +  tok = smartlist_get(tokens,0);
 +  if (tok->tp != K_EXTRA_INFO) {
 +    log_warn(LD_DIR,"Entry does not start with \"extra-info\"");
 +    goto err;
 +  }
 +
 +  extrainfo = tor_malloc_zero(sizeof(extrainfo_t));
 +  extrainfo->cache_info.is_extrainfo = 1;
 +  if (cache_copy)
 +    extrainfo->cache_info.signed_descriptor_body = tor_memdup_nulterm(s,end-s);
 +  extrainfo->cache_info.signed_descriptor_len = end-s;
 +  memcpy(extrainfo->cache_info.signed_descriptor_digest, digest, DIGEST_LEN);
 +  crypto_digest256((char*)extrainfo->digest256, s, end-s, DIGEST_SHA256);
 +
 +  tor_assert(tok->n_args >= 2);
 +  if (!is_legal_nickname(tok->args[0])) {
 +    log_warn(LD_DIR,"Bad nickname %s on \"extra-info\"",escaped(tok->args[0]));
 +    goto err;
 +  }
 +  strlcpy(extrainfo->nickname, tok->args[0], sizeof(extrainfo->nickname));
 +  if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 +      base16_decode(extrainfo->cache_info.identity_digest, DIGEST_LEN,
 +                    tok->args[1], HEX_DIGEST_LEN) != DIGEST_LEN) {
 +    log_warn(LD_DIR,"Invalid fingerprint %s on \"extra-info\"",
 +             escaped(tok->args[1]));
 +    goto err;
 +  }
 +
 +  tok = find_by_keyword(tokens, K_PUBLISHED);
 +  if (parse_iso_time(tok->args[0], &extrainfo->cache_info.published_on)) {
 +    log_warn(LD_DIR,"Invalid published time %s on \"extra-info\"",
 +             escaped(tok->args[0]));
 +    goto err;
 +  }
 +
 +  {
 +    directory_token_t *ed_sig_tok, *ed_cert_tok;
 +    ed_sig_tok = find_opt_by_keyword(tokens, K_ROUTER_SIG_ED25519);
 +    ed_cert_tok = find_opt_by_keyword(tokens, K_IDENTITY_ED25519);
 +    int n_ed_toks = !!ed_sig_tok + !!ed_cert_tok;
 +    if (n_ed_toks != 0 && n_ed_toks != 2) {
 +      log_warn(LD_DIR, "Router descriptor with only partial ed25519/"
 +               "cross-certification support");
 +      goto err;
 +    }
 +    if (ed_sig_tok) {
 +      tor_assert(ed_cert_tok);
 +      const int ed_cert_token_pos = smartlist_pos(tokens, ed_cert_tok);
 +      if (ed_cert_token_pos != 1) {
 +        /* Accept this in position 0 XXXX */
 +        log_warn(LD_DIR, "Ed25519 certificate in wrong position");
 +        goto err;
 +      }
 +      if (ed_sig_tok != smartlist_get(tokens, smartlist_len(tokens)-2)) {
 +        log_warn(LD_DIR, "Ed25519 signature in wrong position");
 +        goto err;
 +      }
 +      if (strcmp(ed_cert_tok->object_type, "ED25519 CERT")) {
 +        log_warn(LD_DIR, "Wrong object type on identity-ed25519 in decriptor");
 +        goto err;
 +      }
 +
 +      uint8_t d256[DIGEST256_LEN];
 +      const char *signed_start, *signed_end;
 +      tor_cert_t *cert = tor_cert_parse(
 +                       (const uint8_t*)ed_cert_tok->object_body,
 +                       ed_cert_tok->object_size);
 +      if (! cert) {
 +        log_warn(LD_DIR, "Couldn't parse ed25519 cert");
 +        goto err;
 +      }
 +      /* makes sure it gets freed. */
 +      extrainfo->cache_info.signing_key_cert = cert;
 +
 +      if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
 +          ! cert->signing_key_included) {
 +        log_warn(LD_DIR, "Invalid form for ed25519 cert");
 +        goto err;
 +      }
 +
 +      if (router_get_hash_impl_helper(s, end-s, "extra-info ",
 +                                      "\nrouter-sig-ed25519",
 +                                      ' ', LOG_WARN,
 +                                      &signed_start, &signed_end) < 0) {
 +        log_warn(LD_DIR, "Can't find ed25519-signed portion of extrainfo");
 +        goto err;
 +      }
 +      crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
 +      crypto_digest_add_bytes(d, ED_DESC_SIGNATURE_PREFIX,
 +        strlen(ED_DESC_SIGNATURE_PREFIX));
 +      crypto_digest_add_bytes(d, signed_start, signed_end-signed_start);
 +      crypto_digest_get_digest(d, (char*)d256, sizeof(d256));
 +      crypto_digest_free(d);
 +
 +      ed25519_checkable_t check[2];
 +      int check_ok[2];
 +      if (tor_cert_get_checkable_sig(&check[0], cert, NULL, NULL) < 0) {
 +        log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
 +        goto err;
 +      }
 +
 +      if (ed25519_signature_from_base64(&check[1].signature,
 +                                        ed_sig_tok->args[0])<0) {
 +        log_warn(LD_DIR, "Couldn't decode ed25519 signature");
 +        goto err;
 +      }
 +      check[1].pubkey = &cert->signed_key;
 +      check[1].msg = d256;
 +      check[1].len = DIGEST256_LEN;
 +
 +      if (ed25519_checksig_batch(check_ok, check, 2) < 0) {
 +        log_warn(LD_DIR, "Incorrect ed25519 signature(s)");
 +        goto err;
 +      }
 +      /* We don't check the certificate expiration time: checking that it
 +       * matches the cert in the router descriptor is adequate. */
 +    }
 +  }
 +
 +  /* We've checked everything that's covered by the hash. */
 +  can_dl_again = 1;
 +
 +  if (routermap &&
 +      (router = digestmap_get((digestmap_t*)routermap,
 +                              extrainfo->cache_info.identity_digest))) {
 +    key = router->identity_pkey;
 +  }
 +
 +  tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
 +  if (strcmp(tok->object_type, "SIGNATURE") ||
 +      tok->object_size < 128 || tok->object_size > 512) {
 +    log_warn(LD_DIR, "Bad object type or length on extra-info signature");
 +    goto err;
 +  }
 +
 +  if (key) {
 +    if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
 +                              "extra-info") < 0)
 +      goto err;
 +
 +    if (router)
 +      extrainfo->cache_info.send_unencrypted =
 +        router->cache_info.send_unencrypted;
 +  } else {
 +    extrainfo->pending_sig = tor_memdup(tok->object_body,
 +                                        tok->object_size);
 +    extrainfo->pending_sig_len = tok->object_size;
 +  }
 +
 +  goto done;
 + err:
 +  dump_desc(s_dup, "extra-info descriptor");
 +  extrainfo_free(extrainfo);
 +  extrainfo = NULL;
 + done:
 +  if (tokens) {
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
 +    smartlist_free(tokens);
 +  }
 +  if (area) {
 +    DUMP_AREA(area, "extrainfo");
 +    memarea_drop_all(area);
 +  }
 +  if (can_dl_again_out)
 +    *can_dl_again_out = can_dl_again;
 +  return extrainfo;
 +}
 +
 +/** Add an exit policy stored in the token <b>tok</b> to the router info in
 + * <b>router</b>.  Return 0 on success, -1 on failure. */
 +static int
 +router_add_exit_policy(routerinfo_t *router, directory_token_t *tok)
 +{
 +  addr_policy_t *newe;
 +  /* Use the standard interpretation of accept/reject *, an IPv4 wildcard. */
 +  newe = router_parse_addr_policy(tok, 0);
 +  if (!newe)
 +    return -1;
 +  if (! router->exit_policy)
 +    router->exit_policy = smartlist_new();
 +
 +  /* Ensure that in descriptors, accept/reject fields are followed by
 +   * IPv4 addresses, and accept6/reject6 fields are followed by
 +   * IPv6 addresses. Unlike torrcs, descriptor exit policies do not permit
 +   * accept/reject followed by IPv6. */
 +  if (((tok->tp == K_ACCEPT6 || tok->tp == K_REJECT6) &&
 +       tor_addr_family(&newe->addr) == AF_INET)
 +      ||
 +      ((tok->tp == K_ACCEPT || tok->tp == K_REJECT) &&
 +       tor_addr_family(&newe->addr) == AF_INET6)) {
 +    /* There's nothing the user can do about other relays' descriptors,
 +     * so we don't provide usage advice here. */
 +    log_warn(LD_DIR, "Mismatch between field type and address type in exit "
 +             "policy '%s'. Discarding entire router descriptor.",
 +             tok->n_args == 1 ? tok->args[0] : "");
 +    addr_policy_free(newe);
 +    return -1;
 +  }
 +
 +  smartlist_add(router->exit_policy, newe);
 +
 +  return 0;
 +}
 +
 +/** Return a newly allocated smartlist of all accept or reject tokens in
 + * <b>s</b>.
 + */
 +static smartlist_t *
 +find_all_exitpolicy(smartlist_t *s)
 +{
 +  smartlist_t *out = smartlist_new();
 +  SMARTLIST_FOREACH(s, directory_token_t *, t,
 +      if (t->tp == K_ACCEPT || t->tp == K_ACCEPT6 ||
 +          t->tp == K_REJECT || t->tp == K_REJECT6)
 +        smartlist_add(out,t));
 +  return out;
 +}
 +
 +/** Called on startup; right now we just handle scanning the unparseable
 + * descriptor dumps, but hang anything else we might need to do in the
 + * future here as well.
 + */
 +void
 +routerparse_init(void)
 +{
 +  /*
 +   * Check both if the sandbox is active and whether it's configured; no
 +   * point in loading all that if we won't be able to use it after the
 +   * sandbox becomes active.
 +   */
 +  if (!(sandbox_is_active() || get_options()->Sandbox)) {
 +    dump_desc_init();
 +  }
 +}
 +
 +/** Clean up all data structures used by routerparse.c at exit */
 +void
 +routerparse_free_all(void)
 +{
 +  dump_desc_fifo_cleanup();
 +}





More information about the tor-commits mailing list