[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [tor/release-0.2.2] Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2



commit a793f1f6f2bd7b774e3b001468df4ea72bdeffc4
Merge: 9bb947e c8f94ee
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Wed Jan 19 13:25:38 2011 -0500

    Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2

 changes/routerparse_maxima |    4 ++++
 src/or/routerparse.c       |   17 +++++++++++++++++
 2 files changed, 21 insertions(+), 0 deletions(-)

diff --combined src/or/routerparse.c
index 08f81d9,aa0687d..5ceb298
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@@ -10,21 -10,7 +10,21 @@@
   **/
  
  #include "or.h"
 +#include "config.h"
 +#include "circuitbuild.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "policies.h"
 +#include "rendcommon.h"
 +#include "router.h"
 +#include "routerlist.h"
  #include "memarea.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "rephist.h"
 +#include "routerparse.h"
 +#undef log
 +#include <math.h>
  
  /****************************************************************************/
  
@@@ -69,7 -55,6 +69,7 @@@ typedef enum 
    K_S,
    K_V,
    K_W,
 +  K_M,
    K_EVENTDNS,
    K_EXTRA_INFO,
    K_EXTRA_INFO_DIGEST,
@@@ -77,31 -62,6 +77,31 @@@
    K_HIDDEN_SERVICE_DIR,
    K_ALLOW_SINGLE_HOP_EXITS,
  
 +  K_DIRREQ_END,
 +  K_DIRREQ_V2_IPS,
 +  K_DIRREQ_V3_IPS,
 +  K_DIRREQ_V2_REQS,
 +  K_DIRREQ_V3_REQS,
 +  K_DIRREQ_V2_SHARE,
 +  K_DIRREQ_V3_SHARE,
 +  K_DIRREQ_V2_RESP,
 +  K_DIRREQ_V3_RESP,
 +  K_DIRREQ_V2_DIR,
 +  K_DIRREQ_V3_DIR,
 +  K_DIRREQ_V2_TUN,
 +  K_DIRREQ_V3_TUN,
 +  K_ENTRY_END,
 +  K_ENTRY_IPS,
 +  K_CELL_END,
 +  K_CELL_PROCESSED,
 +  K_CELL_QUEUED,
 +  K_CELL_TIME,
 +  K_CELL_CIRCS,
 +  K_EXIT_END,
 +  K_EXIT_WRITTEN,
 +  K_EXIT_READ,
 +  K_EXIT_OPENED,
 +
    K_DIR_KEY_CERTIFICATE_VERSION,
    K_DIR_IDENTITY_KEY,
    K_DIR_KEY_PUBLISHED,
@@@ -118,18 -78,13 +118,18 @@@
  
    K_KNOWN_FLAGS,
    K_PARAMS,
 +  K_BW_WEIGHTS,
    K_VOTE_DIGEST,
    K_CONSENSUS_DIGEST,
 +  K_ADDITIONAL_DIGEST,
 +  K_ADDITIONAL_SIGNATURE,
    K_CONSENSUS_METHODS,
    K_CONSENSUS_METHOD,
    K_LEGACY_DIR_KEY,
 +  K_DIRECTORY_FOOTER,
  
    A_PURPOSE,
 +  A_LAST_LISTED,
    _A_UNKNOWN,
  
    R_RENDEZVOUS_SERVICE_DESCRIPTOR,
@@@ -167,7 -122,7 +167,7 @@@
   * type.
   *
   * This structure is only allocated in memareas; do not allocate it on
 - * the heap, or token_free() won't work.
 + * the heap, or token_clear() won't work.
   */
  typedef struct directory_token_t {
    directory_keyword tp;        /**< Type of the token. */
@@@ -303,31 -258,6 +303,31 @@@ static token_rule_t extrainfo_token_tab
    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
@@@ -337,11 -267,10 +337,11 @@@
   * documents. */
  static token_rule_t rtrstatus_token_table[] = {
    T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
 -  T1( "r",                   K_R,                   GE(8),   NO_OBJ ),
 +  T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
    T1( "s",                   K_S,                   ARGS,    NO_OBJ ),
    T01("v",                   K_V,               CONCAT_ARGS, NO_OBJ ),
    T01("w",                   K_W,                   ARGS,    NO_OBJ ),
 +  T0N("m",                   K_M,               CONCAT_ARGS, NO_OBJ ),
    T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
    END_OF_TABLE
  };
@@@ -446,7 -375,7 +446,7 @@@ static token_rule_t client_keys_token_t
  
  /** List of tokens allowed in V3 networkstatus votes. */
  static token_rule_t networkstatus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("published",              K_PUBLISHED,        CONCAT_ARGS, NO_OBJ ),
@@@ -474,7 -403,7 +474,7 @@@
  
  /** List of tokens allowed in V3 networkstatus consensuses. */
  static token_rule_t networkstatus_consensus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
@@@ -501,29 -430,17 +501,29 @@@
  /** List of tokens allowable in the footer of v1/v2 directory/networkstatus
   * footers. */
  static token_rule_t networkstatus_vote_footer_token_table[] = {
 -  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T01("directory-footer",    K_DIRECTORY_FOOTER,    NO_ARGS,   NO_OBJ ),
 +  T01("bandwidth-weights",   K_BW_WEIGHTS,          ARGS,      NO_OBJ ),
 +  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),     NEED_OBJ ),
    END_OF_TABLE
  };
  
  /** List of tokens allowable in detached networkstatus signature documents. */
  static token_rule_t networkstatus_detached_signature_token_table[] = {
    T1_START("consensus-digest", K_CONSENSUS_DIGEST, GE(1),       NO_OBJ ),
 +  T("additional-digest",       K_ADDITIONAL_DIGEST,GE(3),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
    T1("fresh-until",            K_FRESH_UNTIL,      CONCAT_ARGS, NO_OBJ ),
    T1("valid-until",            K_VALID_UNTIL,      CONCAT_ARGS, NO_OBJ ),
 -  T1N("directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T("additional-signature",  K_ADDITIONAL_SIGNATURE, GE(4),   NEED_OBJ ),
 +  T1N("directory-signature", K_DIRECTORY_SIGNATURE,  GE(2),   NEED_OBJ ),
 +  END_OF_TABLE
 +};
 +
 +static token_rule_t microdesc_token_table[] = {
 +  T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     NEED_KEY_1024),
 +  T01("family",                K_FAMILY,           ARGS,        NO_OBJ ),
 +  T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
 +  A01("@last-listed",          A_LAST_LISTED,      CONCAT_ARGS, NO_OBJ ),
    END_OF_TABLE
  };
  
@@@ -536,13 -453,9 +536,13 @@@ static addr_policy_t *router_parse_addr
  
  static int router_get_hash_impl(const char *s, size_t s_len, char *digest,
                                  const char *start_str, const char *end_str,
 -                                char end_char);
 -
 -static void token_free(directory_token_t *tok);
 +                                char end_char,
 +                                digest_algorithm_t alg);
 +static int router_get_hashes_impl(const char *s, size_t s_len,
 +                                  digests_t *digests,
 +                                  const char *start_str, const char *end_str,
 +                                  char end_char);
 +static void token_clear(directory_token_t *tok);
  static smartlist_t *find_all_exitpolicy(smartlist_t *s);
  static directory_token_t *_find_by_keyword(smartlist_t *s,
                                             directory_keyword keyword,
@@@ -566,7 -479,6 +566,7 @@@ static directory_token_t *get_next_toke
  #define CST_CHECK_AUTHORITY   (1<<0)
  #define CST_NO_CHECK_OBJTYPE  (1<<1)
  static int check_signature_token(const char *digest,
 +                                 ssize_t digest_len,
                                   directory_token_t *tok,
                                   crypto_pk_env_t *pkey,
                                   int flags,
@@@ -587,34 -499,6 +587,34 @@@ static int tor_version_same_series(tor_
  #define DUMP_AREA(a,name) STMT_NIL
  #endif
  
 +/** Last time we dumped a descriptor to disk. */
 +static time_t last_desc_dumped = 0;
 +
 +/** For debugging purposes, dump unparseable descriptor *<b>desc</b> of
 + * type *<b>type</b> to file $DATADIR/unparseable-desc. Do not write more
 + * than one descriptor to disk per minute. If there is already such a
 + * file in the data directory, overwrite it. */
 +static void
 +dump_desc(const char *desc, const char *type)
 +{
 +  time_t now = time(NULL);
 +  tor_assert(desc);
 +  tor_assert(type);
 +  if (!last_desc_dumped || last_desc_dumped + 60 < now) {
 +    char *debugfile = get_datadir_fname("unparseable-desc");
 +    size_t filelen = 50 + strlen(type) + strlen(desc);
 +    char *content = tor_malloc_zero(filelen);
 +    tor_snprintf(content, filelen, "Unable to parse descriptor of type "
 +                 "%s:\n%s", type, desc);
 +    write_str_to_file(debugfile, content, 0);
 +    log_info(LD_DIR, "Unable to parse descriptor of type %s. See file "
 +             "unparseable-desc in data directory for details.", type);
 +    tor_free(content);
 +    tor_free(debugfile);
 +    last_desc_dumped = now;
 +  }
 +}
 +
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
   * <b>s</b>.  Return 0 on success, -1 on failure.
   */
@@@ -622,8 -506,7 +622,8 @@@ in
  router_get_dir_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "signed-directory","\ndirectory-signature",'\n');
 +                              "signed-directory","\ndirectory-signature",'\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
@@@ -633,8 -516,7 +633,8 @@@ in
  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');
 +                              "router ","\nrouter-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers
@@@ -644,8 -526,7 +644,8 @@@ in
  router_get_runningrouters_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "network-status","\ndirectory-signature", '\n');
 +                              "network-status","\ndirectory-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
@@@ -655,31 -536,18 +655,31 @@@ router_get_networkstatus_v2_hash(const 
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version","\ndirectory-signature",
 -                              '\n');
 +                              '\n',
 +                              DIGEST_SHA1);
 +}
 +
 +/** Set <b>digests</b> to all the digests of the consensus document in
 + * <b>s</b> */
 +int
 +router_get_networkstatus_v3_hashes(const char *s, digests_t *digests)
 +{
 +  return router_get_hashes_impl(s,strlen(s),digests,
 +                                "network-status-version",
 +                                "\ndirectory-signature",
 +                                ' ');
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
   * string in <b>s</b>.  Return 0 on success, -1 on failure. */
  int
 -router_get_networkstatus_v3_hash(const char *s, char *digest)
 +router_get_networkstatus_v3_hash(const char *s, char *digest,
 +                                 digest_algorithm_t alg)
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version",
                                "\ndirectory-signature",
 -                              ' ');
 +                              ' ', alg);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the extrainfo
@@@ -688,7 -556,7 +688,7 @@@ in
  router_get_extrainfo_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest, "extra-info",
 -                              "\nrouter-signature",'\n');
 +                              "\nrouter-signature",'\n', DIGEST_SHA1);
  }
  
  /** Helper: used to generate signatures for routers, directories and
@@@ -700,17 -568,16 +700,17 @@@
   */
  int
  router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest,
 -                               crypto_pk_env_t *private_key)
 +                               size_t digest_len, crypto_pk_env_t *private_key)
  {
    char *signature;
    size_t i, keysize;
 +  int siglen;
  
    keysize = crypto_pk_keysize(private_key);
    signature = tor_malloc(keysize);
 -  if (crypto_pk_private_sign(private_key, signature, keysize,
 -                             digest, DIGEST_LEN) < 0) {
 -
 +  siglen = crypto_pk_private_sign(private_key, signature, keysize,
 +                                  digest, digest_len);
 +  if (siglen < 0) {
      log_warn(LD_BUG,"Couldn't sign digest.");
      goto err;
    }
@@@ -718,7 -585,7 +718,7 @@@
      goto truncated;
  
    i = strlen(buf);
 -  if (base64_encode(buf+i, buf_len-i, signature, 128) < 0) {
 +  if (base64_encode(buf+i, buf_len-i, signature, siglen) < 0) {
      log_warn(LD_BUG,"couldn't base64-encode signature");
      goto err;
    }
@@@ -825,7 -692,7 +825,7 @@@ router_parse_directory(const char *str
    char digest[DIGEST_LEN];
    time_t published_on;
    int r;
 -  const char *end, *cp;
 +  const char *end, *cp, *str_dup = str;
    smartlist_t *tokens = NULL;
    crypto_pk_env_t *declared_key = NULL;
    memarea_t *area = memarea_new();
@@@ -861,11 -728,11 +861,11 @@@
    }
    declared_key = find_dir_signing_key(str, str+strlen(str));
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "directory")<0)
      goto err;
  
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
  
@@@ -898,12 -765,11 +898,12 @@@
    r = 0;
    goto done;
   err:
 +  dump_desc(str_dup, "v1 directory");
    r = -1;
   done:
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -925,7 -791,7 +925,7 @@@ router_parse_runningrouters(const char 
    int r = -1;
    crypto_pk_env_t *declared_key = NULL;
    smartlist_t *tokens = NULL;
 -  const char *eos = str + strlen(str);
 +  const char *eos = str + strlen(str), *str_dup = str;
    memarea_t *area = NULL;
  
    if (router_get_runningrouters_hash(str, digest)) {
@@@ -954,7 -820,7 +954,7 @@@
    }
    declared_key = find_dir_signing_key(str, eos);
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "running-routers")
        < 0)
      goto err;
@@@ -966,10 -832,9 +966,10 @@@
  
    r = 0;
   err:
 +  dump_desc(str_dup, "v1 running-routers");
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1019,7 -884,7 +1019,7 @@@ find_dir_signing_key(const char *str, c
    }
  
   done:
 -  if (tok) token_free(tok);
 +  if (tok) token_clear(tok);
    if (area) {
      DUMP_AREA(area, "dir-signing-key token");
      memarea_drop_all(area);
@@@ -1055,7 -920,6 +1055,7 @@@ dir_signing_key_is_trusted(crypto_pk_en
   */
  static int
  check_signature_token(const char *digest,
 +                      ssize_t digest_len,
                        directory_token_t *tok,
                        crypto_pk_env_t *pkey,
                        int flags,
@@@ -1088,14 -952,14 +1088,14 @@@
    signed_digest = tor_malloc(keysize);
    if (crypto_pk_public_checksig(pkey, signed_digest, keysize,
                                  tok->object_body, tok->object_size)
 -      != DIGEST_LEN) {
 +      < DIGEST_LEN) {
      log_warn(LD_DIR, "Error reading %s: invalid signature.", doctype);
      tor_free(signed_digest);
      return -1;
    }
  //  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
  //            hex_str(signed_digest,4));
 -  if (memcmp(digest, signed_digest, DIGEST_LEN)) {
 +  if (memcmp(digest, signed_digest, digest_len)) {
      log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
      tor_free(signed_digest);
      return -1;
@@@ -1281,7 -1145,7 +1281,7 @@@ router_parse_entry_from_string(const ch
    smartlist_t *tokens = NULL, *exit_policy_tokens = NULL;
    directory_token_t *tok;
    struct in_addr in;
 -  const char *start_of_annotations, *cp;
 +  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;
@@@ -1559,7 -1423,7 +1559,7 @@@
      verified_digests = digestmap_new();
    digestmap_set(verified_digests, signed_digest, (void*)(uintptr_t)1);
  #endif
 -  if (check_signature_token(digest, tok, router->identity_pkey, 0,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0,
                              "router descriptor") < 0)
      goto err;
  
@@@ -1577,15 -1441,16 +1577,15 @@@
    goto done;
  
   err:
 +  dump_desc(s_dup, "router descriptor");
    routerinfo_free(router);
    router = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
 -  if (exit_policy_tokens) {
 -    smartlist_free(exit_policy_tokens);
 -  }
 +  smartlist_free(exit_policy_tokens);
    if (area) {
      DUMP_AREA(area, "routerinfo");
      memarea_drop_all(area);
@@@ -1610,7 -1475,6 +1610,7 @@@ extrainfo_parse_entry_from_string(cons
    crypto_pk_env_t *key = NULL;
    routerinfo_t *router = NULL;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    if (!end) {
      end = s + strlen(s);
@@@ -1685,8 -1549,7 +1685,8 @@@
  
    if (key) {
      note_crypto_pk_op(VERIFY_RTR);
 -    if (check_signature_token(digest, tok, key, 0, "extra-info") < 0)
 +    if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
 +                              "extra-info") < 0)
        goto err;
  
      if (router)
@@@ -1700,12 -1563,12 +1700,12 @@@
  
    goto done;
   err:
 -  if (extrainfo)
 -    extrainfo_free(extrainfo);
 +  dump_desc(s_dup, "extra-info descriptor");
 +  extrainfo_free(extrainfo);
    extrainfo = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1720,6 -1583,10 +1720,10 @@@
  authority_cert_t *
  authority_cert_parse_from_string(const char *s, const char **end_of_string)
  {
+   /** Reject any certificate at least this big; it is probably an overflow, an
+    * attack, a bug, or some other nonsense. */
+ #define MAX_CERT_SIZE (128*1024)
+ 
    authority_cert_t *cert = NULL, *old_cert;
    smartlist_t *tokens = NULL;
    char digest[DIGEST_LEN];
@@@ -1729,7 -1596,6 +1733,7 @@@
    size_t len;
    int found;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    s = eat_whitespace(s);
    eos = strstr(s, "\ndir-key-certification");
@@@ -1747,6 -1613,12 +1751,12 @@@
    ++eos;
    len = eos - s;
  
+   if (len > MAX_CERT_SIZE) {
+     log_warn(LD_DIR, "Certificate is far too big (at %lu bytes long); "
+              "rejecting", (unsigned long)len);
+     return NULL;
+   }
+ 
    tokens = smartlist_create();
    area = memarea_new();
    if (tokenize_string(area,s, eos, tokens, dir_key_certificate_table, 0) < 0) {
@@@ -1754,7 -1626,7 +1764,7 @@@
      goto err;
    }
    if (router_get_hash_impl(s, strlen(s), digest, "dir-key-certificate-version",
 -                           "\ndir-key-certification", '\n') < 0)
 +                           "\ndir-key-certification", '\n', DIGEST_SHA1) < 0)
      goto err;
    tok = smartlist_get(tokens, 0);
    if (tok->tp != K_DIR_KEY_CERTIFICATE_VERSION || strcmp(tok->args[0], "3")) {
@@@ -1847,7 -1719,7 +1857,7 @@@
      }
    }
    if (!found) {
 -    if (check_signature_token(digest, tok, cert->identity_key, 0,
 +    if (check_signature_token(digest, DIGEST_LEN, tok, cert->identity_key, 0,
                                "key certificate")) {
        goto err;
      }
@@@ -1856,7 -1728,6 +1866,7 @@@
        /* XXXX Once all authorities generate cross-certified certificates,
         * make this field mandatory. */
        if (check_signature_token(cert->cache_info.identity_digest,
 +                                DIGEST_LEN,
                                  tok,
                                  cert->signing_key,
                                  CST_NO_CHECK_OBJTYPE,
@@@ -1876,7 -1747,7 +1886,7 @@@
    if (end_of_string) {
      *end_of_string = eat_whitespace(eos);
    }
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1884,9 -1755,8 +1894,9 @@@
    }
    return cert;
   err:
 +  dump_desc(s_dup, "authority cert");
    authority_cert_free(cert);
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1897,28 -1767,23 +1907,28 @@@
  
  /** Helper: given a string <b>s</b>, return the start of the next router-status
   * object (starting with "r " at the start of a line).  If none is found,
 - * return the start of the next directory signature.  If none is found, return
 - * the end of the string. */
 + * return the start of the directory footer, or the next directory signature.
 + * If none is found, return the end of the string. */
  static INLINE const char *
  find_start_of_next_routerstatus(const char *s)
  {
 -  const char *eos = strstr(s, "\nr ");
 -  if (eos) {
 -    const char *eos2 = tor_memstr(s, eos-s, "\ndirectory-signature");
 -    if (eos2 && eos2 < eos)
 -      return eos2;
 -    else
 -      return eos+1;
 -  } else {
 -    if ((eos = strstr(s, "\ndirectory-signature")))
 -      return eos+1;
 -    return s + strlen(s);
 -  }
 +  const char *eos, *footer, *sig;
 +  if ((eos = strstr(s, "\nr ")))
 +    ++eos;
 +  else
 +    eos = s + strlen(s);
 +
 +  footer = tor_memstr(s, eos-s, "\ndirectory-footer");
 +  sig = tor_memstr(s, eos-s, "\ndirectory-signature");
 +
 +  if (footer && sig)
 +    return MIN(footer, sig) + 1;
 +  else if (footer)
 +    return footer+1;
 +  else if (sig)
 +    return sig+1;
 +  else
 +    return eos;
  }
  
  /** Given a string at *<b>s</b>, containing a routerstatus object, and an
@@@ -1932,29 -1797,22 +1942,29 @@@
   * If <b>consensus_method</b> is nonzero, this routerstatus is part of a
   * consensus, and we should parse it according to the method used to
   * make that consensus.
 + *
 + * Parse according to the syntax used by the consensus flavor <b>flav</b>.
   **/
  static routerstatus_t *
  routerstatus_parse_entry_from_string(memarea_t *area,
                                       const char **s, smartlist_t *tokens,
                                       networkstatus_t *vote,
                                       vote_routerstatus_t *vote_rs,
 -                                     int consensus_method)
 +                                     int consensus_method,
 +                                     consensus_flavor_t flav)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = *s;
    routerstatus_t *rs = NULL;
    directory_token_t *tok;
    char timebuf[ISO_TIME_LEN+1];
    struct in_addr in;
 +  int offset = 0;
    tor_assert(tokens);
    tor_assert(bool_eq(vote, vote_rs));
  
 +  if (!consensus_method)
 +    flav = FLAV_NS;
 +
    eos = find_start_of_next_routerstatus(*s);
  
    if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) {
@@@ -1966,15 -1824,7 +1976,15 @@@
      goto err;
    }
    tok = find_by_keyword(tokens, K_R);
 -  tor_assert(tok->n_args >= 8);
 +  tor_assert(tok->n_args >= 7);
 +  if (flav == FLAV_NS) {
 +    if (tok->n_args < 8) {
 +      log_warn(LD_DIR, "Too few arguments to r");
 +      goto err;
 +    }
 +  } else {
 +    offset = -1;
 +  }
    if (vote_rs) {
      rs = &vote_rs->status;
    } else {
@@@ -1995,34 -1845,29 +2005,34 @@@
      goto err;
    }
  
 -  if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 -    log_warn(LD_DIR, "Error decoding descriptor digest %s",
 -             escaped(tok->args[2]));
 -    goto err;
 +  if (flav == FLAV_NS) {
 +    if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 +      log_warn(LD_DIR, "Error decoding descriptor digest %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
    }
  
    if (tor_snprintf(timebuf, sizeof(timebuf), "%s %s",
 -                   tok->args[3], tok->args[4]) < 0 ||
 +                   tok->args[3+offset], tok->args[4+offset]) < 0 ||
        parse_iso_time(timebuf, &rs->published_on)<0) {
 -    log_warn(LD_DIR, "Error parsing time '%s %s'",
 -             tok->args[3], tok->args[4]);
 +    log_warn(LD_DIR, "Error parsing time '%s %s' [%d %d]",
 +             tok->args[3+offset], tok->args[4+offset],
 +             offset, (int)flav);
      goto err;
    }
  
 -  if (tor_inet_aton(tok->args[5], &in) == 0) {
 +  if (tor_inet_aton(tok->args[5+offset], &in) == 0) {
      log_warn(LD_DIR, "Error parsing router address in network-status %s",
 -             escaped(tok->args[5]));
 +             escaped(tok->args[5+offset]));
      goto err;
    }
    rs->addr = ntohl(in.s_addr);
  
 -  rs->or_port =(uint16_t) tor_parse_long(tok->args[6],10,0,65535,NULL,NULL);
 -  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7],10,0,65535,NULL,NULL);
 +  rs->or_port = (uint16_t) tor_parse_long(tok->args[6+offset],
 +                                         10,0,65535,NULL,NULL);
 +  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset],
 +                                           10,0,65535,NULL,NULL);
  
    tok = find_opt_by_keyword(tokens, K_S);
    if (tok && vote) {
@@@ -2108,17 -1953,6 +2118,17 @@@
            goto err;
          }
          rs->has_bandwidth = 1;
 +      } else if (!strcmpstart(tok->args[i], "Measured=")) {
 +        int ok;
 +        rs->measured_bw =
 +            (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1,
 +                                      10, 0, UINT32_MAX, &ok, NULL);
 +        if (!ok) {
 +          log_warn(LD_DIR, "Invalid Measured Bandwidth %s",
 +                   escaped(tok->args[i]));
 +          goto err;
 +        }
 +        rs->has_measured_bw = 1;
        }
      }
    }
@@@ -2140,29 -1974,16 +2150,29 @@@
      rs->has_exitsummary = 1;
    }
  
 +  if (vote_rs) {
 +    SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, t) {
 +      if (t->tp == K_M && t->n_args) {
 +        vote_microdesc_hash_t *line =
 +          tor_malloc(sizeof(vote_microdesc_hash_t));
 +        line->next = vote_rs->microdesc;
 +        line->microdesc_hash_line = tor_strdup(t->args[0]);
 +        vote_rs->microdesc = line;
 +      }
 +    } SMARTLIST_FOREACH_END(t);
 +  }
 +
    if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME))
      rs->is_named = 0;
  
    goto done;
   err:
 +  dump_desc(s_dup, "routerstatus entry");
    if (rs && !vote_rs)
      routerstatus_free(rs);
    rs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    if (area) {
      DUMP_AREA(area, "routerstatus entry");
@@@ -2174,8 -1995,8 +2184,8 @@@
  }
  
  /** Helper to sort a smartlist of pointers to routerstatus_t */
 -static int
 -_compare_routerstatus_entries(const void **_a, const void **_b)
 +int
 +compare_routerstatus_entries(const void **_a, const void **_b)
  {
    const routerstatus_t *a = *_a, *b = *_b;
    return memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN);
@@@ -2199,7 -2020,7 +2209,7 @@@ _free_duplicate_routerstatus_entry(voi
  networkstatus_v2_t *
  networkstatus_v2_parse_from_string(const char *s)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = s;
    smartlist_t *tokens = smartlist_create();
    smartlist_t *footer_tokens = smartlist_create();
    networkstatus_v2_t *ns = NULL;
@@@ -2313,17 -2134,17 +2323,17 @@@
  
    ns->entries = smartlist_create();
    s = eos;
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
    while (!strcmpstart(s, "r ")) {
      routerstatus_t *rs;
      if ((rs = routerstatus_parse_entry_from_string(area, &s, tokens,
 -                                                   NULL, NULL, 0)))
 +                                                   NULL, NULL, 0, 0)))
        smartlist_add(ns->entries, rs);
    }
 -  smartlist_sort(ns->entries, _compare_routerstatus_entries);
 -  smartlist_uniq(ns->entries, _compare_routerstatus_entries,
 +  smartlist_sort(ns->entries, compare_routerstatus_entries);
 +  smartlist_uniq(ns->entries, compare_routerstatus_entries,
                   _free_duplicate_routerstatus_entry);
  
    if (tokenize_string(area,s, NULL, footer_tokens, dir_footer_token_table,0)) {
@@@ -2342,19 -2163,19 +2352,19 @@@
    }
  
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(ns_digest, tok, ns->signing_key, 0,
 +  if (check_signature_token(ns_digest, DIGEST_LEN, tok, ns->signing_key, 0,
                              "network-status") < 0)
      goto err;
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_v2_free(ns);
 +  dump_desc(s_dup, "v2 networkstatus");
 +  networkstatus_v2_free(ns);
    ns = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(footer_tokens);
    if (area) {
      DUMP_AREA(area, "v2 networkstatus");
@@@ -2363,395 -2184,6 +2373,395 @@@
    return ns;
  }
  
 +/** Verify the bandwidth weights of a network status document */
 +int
 +networkstatus_verify_bw_weights(networkstatus_t *ns)
 +{
 +  int64_t weight_scale;
 +  int64_t G=0, M=0, E=0, D=0, T=0;
 +  double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
 +  double Gtotal=0, Mtotal=0, Etotal=0;
 +  const char *casename = NULL;
 +  int valid = 1;
 +
 +  weight_scale = circuit_build_times_get_bw_scale(ns);
 +  Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
 +  Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
 +  Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
 +  Wmg = networkstatus_get_bw_weight(ns, "Wmg", -1);
 +  Wmm = networkstatus_get_bw_weight(ns, "Wmm", -1);
 +  Wme = networkstatus_get_bw_weight(ns, "Wme", -1);
 +  Wmd = networkstatus_get_bw_weight(ns, "Wmd", -1);
 +  Weg = networkstatus_get_bw_weight(ns, "Weg", -1);
 +  Wem = networkstatus_get_bw_weight(ns, "Wem", -1);
 +  Wee = networkstatus_get_bw_weight(ns, "Wee", -1);
 +  Wed = networkstatus_get_bw_weight(ns, "Wed", -1);
 +
 +  if (Wgg<0 || Wgm<0 || Wgd<0 || Wmg<0 || Wmm<0 || Wme<0 || Wmd<0 || Weg<0
 +          || Wem<0 || Wee<0 || Wed<0) {
 +    log_warn(LD_BUG, "No bandwidth weights produced in consensus!");
 +    return 0;
 +  }
 +
 +  // First, sanity check basic summing properties that hold for all cases
 +  // We use > 1 as the check for these because they are computed as integers.
 +  // Sometimes there are rounding errors.
 +  if (fabs(Wmm - weight_scale) > 1) {
 +    log_warn(LD_BUG, "Wmm=%lf != "I64_FORMAT,
 +             Wmm, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wem - Wee) > 1) {
 +    log_warn(LD_BUG, "Wem=%lf != Wee=%lf", Wem, Wee);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgm - Wgg) > 1) {
 +    log_warn(LD_BUG, "Wgm=%lf != Wgg=%lf", Wgm, Wgg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Weg - Wed) > 1) {
 +    log_warn(LD_BUG, "Wed=%lf != Weg=%lf", Wed, Weg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgg=%lf != "I64_FORMAT" - Wmg=%lf", Wgg,
 +             I64_PRINTF_ARG(weight_scale), Wmg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wee=%lf != "I64_FORMAT" - Wme=%lf", Wee,
 +             I64_PRINTF_ARG(weight_scale), Wme);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgd=%lf + Wmd=%lf + Wed=%lf != "I64_FORMAT,
 +             Wgd, Wmd, Wed, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  Wgg /= weight_scale;
 +  Wgm /= weight_scale;
 +  Wgd /= weight_scale;
 +
 +  Wmg /= weight_scale;
 +  Wmm /= weight_scale;
 +  Wme /= weight_scale;
 +  Wmd /= weight_scale;
 +
 +  Weg /= weight_scale;
 +  Wem /= weight_scale;
 +  Wee /= weight_scale;
 +  Wed /= weight_scale;
 +
 +  // Then, gather G, M, E, D, T to determine case
 +  SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) {
 +    if (rs->has_bandwidth) {
 +      T += rs->bandwidth;
 +      if (rs->is_exit && rs->is_possible_guard) {
 +        D += rs->bandwidth;
 +        Gtotal += Wgd*rs->bandwidth;
 +        Mtotal += Wmd*rs->bandwidth;
 +        Etotal += Wed*rs->bandwidth;
 +      } else if (rs->is_exit) {
 +        E += rs->bandwidth;
 +        Mtotal += Wme*rs->bandwidth;
 +        Etotal += Wee*rs->bandwidth;
 +      } else if (rs->is_possible_guard) {
 +        G += rs->bandwidth;
 +        Gtotal += Wgg*rs->bandwidth;
 +        Mtotal += Wmg*rs->bandwidth;
 +      } else {
 +        M += rs->bandwidth;
 +        Mtotal += Wmm*rs->bandwidth;
 +      }
 +    } else {
 +      log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
 +          rs->nickname);
 +    }
 +  } SMARTLIST_FOREACH_END(rs);
 +
 +  // Finally, check equality conditions depending upon case 1, 2 or 3
 +  // Full equality cases: 1, 3b
 +  // Partial equality cases: 2b (E=G), 3a (M=E)
 +  // Fully unknown: 2a
 +  if (3*E >= T && 3*G >= T) {
 +    // Case 1: Neither are scarce
 +    casename = "Case 1";
 +    if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Mtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Mtotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +  } else if (3*E < T && 3*G < T) {
 +    int64_t R = MIN(E, G);
 +    int64_t S = MAX(E, G);
 +    /*
 +     * Case 2: Both Guards and Exits are scarce
 +     * Balance D between E and G, depending upon
 +     * D capacity and scarcity. Devote no extra
 +     * bandwidth to middle nodes.
 +     */
 +    if (R+D < S) { // Subcase a
 +      double Rtotal, Stotal;
 +      if (E < G) {
 +        Rtotal = Etotal;
 +        Stotal = Gtotal;
 +      } else {
 +        Rtotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      casename = "Case 2a";
 +      // Rtotal < Stotal
 +      if (Rtotal > Stotal) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Rtotal %lf > Stotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal, Stotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Rtotal < T/3
 +      if (3*Rtotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Rtotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Mtotal > T/3
 +      if (3*Mtotal < T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Mtotal %lf < T "
 +                   I64_FORMAT". "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    } else { // Subcase b: R+D > S
 +      casename = "Case 2b";
 +
 +      /* Check the rare-M redirect case. */
 +      if (D != 0 && 3*M < T) {
 +        casename = "Case 2b (balanced)";
 +        if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    }
 +  } else { // if (E < T/3 || G < T/3) {
 +    int64_t S = MIN(E, G);
 +    int64_t NS = MAX(E, G);
 +    if (3*(S+D) < T) { // Subcase a:
 +      double Stotal;
 +      double NStotal;
 +      if (G < E) {
 +        casename = "Case 3a (G scarce)";
 +        Stotal = Gtotal;
 +        NStotal = Etotal;
 +      } else { // if (G >= E) {
 +        casename = "Case 3a (E scarce)";
 +        NStotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (NS >= M) {
 +        if (fabs(NStotal-Mtotal) > 0.01*MAX(NStotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: NStotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, NStotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        // if NS < M, NStotal > T/3 because only one of G or E is scarce
 +        if (3*NStotal < T) {
 +          log_warn(LD_DIR,
 +                     "Bw Weight Failure for %s: 3*NStotal %lf < T "
 +                     I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT
 +                     " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT". "
 +                     "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                     casename, NStotal*3, I64_PRINTF_ARG(T),
 +                     I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                     I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                     Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    } else { // Subcase b: S+D >= T/3
 +      casename = "Case 3b";
 +      if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Mtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Mtotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    }
 +  }
 +
 +  if (valid)
 +    log_notice(LD_DIR, "Bandwidth-weight %s is verified and valid.",
 +               casename);
 +
 +  return valid;
 +}
 +
  /** Parse a v3 networkstatus vote, opinion, or consensus (depending on
   * ns_type), from <b>s</b>, and return the result.  Return NULL on failure. */
  networkstatus_t *
@@@ -2762,21 -2194,19 +2772,21 @@@ networkstatus_parse_vote_from_string(co
    smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
    networkstatus_voter_info_t *voter = NULL;
    networkstatus_t *ns = NULL;
 -  char ns_digest[DIGEST_LEN];
 -  const char *cert, *end_of_header, *end_of_footer;
 +  digests_t ns_digests;
 +  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
    directory_token_t *tok;
    int ok;
    struct in_addr in;
    int i, inorder, n_signatures = 0;
    memarea_t *area = NULL, *rs_area = NULL;
 +  consensus_flavor_t flav = FLAV_NS;
 +
    tor_assert(s);
  
    if (eos_out)
      *eos_out = NULL;
  
 -  if (router_get_networkstatus_v3_hash(s, ns_digest)) {
 +  if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
      log_warn(LD_DIR, "Unable to compute digest of network-status");
      goto err;
    }
@@@ -2792,23 -2222,7 +2802,23 @@@
    }
  
    ns = tor_malloc_zero(sizeof(networkstatus_t));
 -  memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN);
 +  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
 +
 +  tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION);
 +  tor_assert(tok);
 +  if (tok->n_args > 1) {
 +    int flavor = networkstatus_parse_flavor_name(tok->args[1]);
 +    if (flavor < 0) {
 +      log_warn(LD_DIR, "Can't parse document with unknown flavor %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
 +    ns->flavor = flav = flavor;
 +  }
 +  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
 +    log_warn(LD_DIR, "Flavor found on non-consenus networkstatus.");
 +    goto err;
 +  }
  
    if (ns_type != NS_TYPE_CONSENSUS) {
      const char *end_of_cert = NULL;
@@@ -2962,9 -2376,8 +2972,9 @@@
        if (voter)
          smartlist_add(ns->voters, voter);
        voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 +      voter->sigs = smartlist_create();
        if (ns->type != NS_TYPE_CONSENSUS)
 -        memcpy(voter->vote_digest, ns_digest, DIGEST_LEN);
 +        memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN);
  
        voter->nickname = tor_strdup(tok->args[0]);
        if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
@@@ -3056,7 -2469,7 +3066,7 @@@
      if (ns->type != NS_TYPE_CONSENSUS) {
        vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
        if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
 -                                               rs, 0))
 +                                               rs, 0, 0))
          smartlist_add(ns->routerstatus_list, rs);
        else {
          tor_free(rs->version);
@@@ -3066,8 -2479,7 +3076,8 @@@
        routerstatus_t *rs;
        if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
                                                       NULL, NULL,
 -                                                     ns->consensus_method)))
 +                                                     ns->consensus_method,
 +                                                     flav)))
          smartlist_add(ns->routerstatus_list, rs);
      }
    }
@@@ -3100,73 -2512,14 +3110,73 @@@
      goto err;
    }
  
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, _tok,
    {
 +    int found_sig = 0;
 +    SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
 +      tok = _tok;
 +      if (tok->tp == K_DIRECTORY_SIGNATURE)
 +        found_sig = 1;
 +      else if (found_sig) {
 +        log_warn(LD_DIR, "Extraneous token after first directory-signature");
 +        goto err;
 +      }
 +    } SMARTLIST_FOREACH_END(_tok);
 +  }
 +
 +  if ((tok = find_opt_by_keyword(footer_tokens, K_DIRECTORY_FOOTER))) {
 +    if (tok != smartlist_get(footer_tokens, 0)) {
 +      log_warn(LD_DIR, "Misplaced directory-footer token");
 +      goto err;
 +    }
 +  }
 +
 +  tok = find_opt_by_keyword(footer_tokens, K_BW_WEIGHTS);
 +  if (tok) {
 +    ns->weight_params = smartlist_create();
 +    for (i = 0; i < tok->n_args; ++i) {
 +      int ok=0;
 +      char *eq = strchr(tok->args[i], '=');
 +      if (!eq) {
 +        log_warn(LD_DIR, "Bad element '%s' in weight params",
 +                 escaped(tok->args[i]));
 +        goto err;
 +      }
 +      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
 +      if (!ok) {
 +        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
 +        goto err;
 +      }
 +      smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
 +    }
 +  }
 +
 +  SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
      char declared_identity[DIGEST_LEN];
      networkstatus_voter_info_t *v;
 +    document_signature_t *sig;
 +    const char *id_hexdigest = NULL;
 +    const char *sk_hexdigest = NULL;
 +    digest_algorithm_t alg = DIGEST_SHA1;
      tok = _tok;
      if (tok->tp != K_DIRECTORY_SIGNATURE)
        continue;
      tor_assert(tok->n_args >= 2);
 +    if (tok->n_args == 2) {
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else {
 +      const char *algname = tok->args[0];
 +      int a;
 +      id_hexdigest = tok->args[1];
 +      sk_hexdigest = tok->args[2];
 +      a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unknown digest algorithm %s; skipping",
 +                 escaped(algname));
 +        continue;
 +      }
 +      alg = a;
 +    }
  
      if (!tok->object_type ||
          strcmp(tok->object_type, "SIGNATURE") ||
@@@ -3175,11 -2528,11 +3185,11 @@@
        goto err;
      }
  
 -    if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
          base16_decode(declared_identity, sizeof(declared_identity),
 -                      tok->args[0], HEX_DIGEST_LEN) < 0) {
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -               "network-status vote.", escaped(tok->args[0]));
 +               "network-status vote.", escaped(id_hexdigest));
        goto err;
      }
      if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
@@@ -3187,15 -2540,11 +3197,15 @@@
                 "any declared directory source.");
        goto err;
      }
 -    if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -        base16_decode(v->signing_key_digest, sizeof(v->signing_key_digest),
 -                      tok->args[1], HEX_DIGEST_LEN) < 0) {
 -      log_warn(LD_DIR, "Error decoding declared digest %s in "
 -               "network-status vote.", escaped(tok->args[1]));
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN);
 +    sig->alg = alg;
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      tor_free(sig);
        goto err;
      }
  
@@@ -3204,49 -2553,35 +3214,49 @@@
                   DIGEST_LEN)) {
          log_warn(LD_DIR, "Digest mismatch between declared and actual on "
                   "network-status vote.");
 +        tor_free(sig);
          goto err;
        }
      }
  
 +    if (voter_get_sig_by_algorithm(v, sig->alg)) {
 +      /* We already parsed a vote with this algorithm from this voter. Use the
 +         first one. */
 +      log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 +             "that contains two votes from the same voter with the same "
 +             "algorithm. Ignoring the second vote.");
 +      tor_free(sig);
 +      continue;
 +    }
 +
      if (ns->type != NS_TYPE_CONSENSUS) {
 -      if (check_signature_token(ns_digest, tok, ns->cert->signing_key, 0,
 -                                "network-status vote"))
 +      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
 +                                tok, ns->cert->signing_key, 0,
 +                                "network-status vote")) {
 +        tor_free(sig);
          goto err;
 -      v->good_signature = 1;
 +      }
 +      sig->good_signature = 1;
      } else {
 -      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING)
 +      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) {
 +        tor_free(sig);
          goto err;
 -      /* We already parsed a vote from this voter. Use the first one. */
 -      if (v->signature) {
 -        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 -                   "that contains two votes from the same voter. Ignoring "
 -                   "the second vote.");
 -        continue;
        }
 -
 -      v->signature = tor_memdup(tok->object_body, tok->object_size);
 -      v->signature_len = (int) tok->object_size;
 +      sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +      sig->signature_len = (int) tok->object_size;
      }
 +    smartlist_add(v->sigs, sig);
 +
      ++n_signatures;
 -  });
 +  } SMARTLIST_FOREACH_END(_tok);
  
    if (! n_signatures) {
      log_warn(LD_DIR, "No signatures on networkstatus vote.");
      goto err;
 +  } else if (ns->type == NS_TYPE_VOTE && n_signatures != 1) {
 +    log_warn(LD_DIR, "Received more than one signature on a "
 +             "network-status vote.");
 +    goto err;
    }
  
    if (eos_out)
@@@ -3254,31 -2589,27 +3264,31 @@@
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_vote_free(ns);
 +  dump_desc(s_dup, "v3 networkstatus");
 +  networkstatus_vote_free(ns);
    ns = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (voter) {
 +    if (voter->sigs) {
 +      SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +                        document_signature_free(sig));
 +      smartlist_free(voter->sigs);
 +    }
      tor_free(voter->nickname);
      tor_free(voter->address);
      tor_free(voter->contact);
      tor_free(voter);
    }
    if (rs_tokens) {
 -    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(rs_tokens);
    }
    if (footer_tokens) {
 -    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(footer_tokens);
    }
    if (area) {
@@@ -3291,35 -2622,6 +3301,35 @@@
    return ns;
  }
  
 +/** Return the digests_t that holds the digests of the
 + * <b>flavor_name</b>-flavored networkstatus according to the detached
 + * signatures document <b>sigs</b>, allocating a new digests_t as neeeded. */
 +static digests_t *
 +detached_get_digests(ns_detached_signatures_t *sigs, const char *flavor_name)
 +{
 +  digests_t *d = strmap_get(sigs->digests, flavor_name);
 +  if (!d) {
 +    d = tor_malloc_zero(sizeof(digests_t));
 +    strmap_set(sigs->digests, flavor_name, d);
 +  }
 +  return d;
 +}
 +
 +/** Return the list of signatures of the <b>flavor_name</b>-flavored
 + * networkstatus according to the detached signatures document <b>sigs</b>,
 + * allocating a new digests_t as neeeded. */
 +static smartlist_t *
 +detached_get_signatures(ns_detached_signatures_t *sigs,
 +                        const char *flavor_name)
 +{
 +  smartlist_t *sl = strmap_get(sigs->signatures, flavor_name);
 +  if (!sl) {
 +    sl = smartlist_create();
 +    strmap_set(sigs->signatures, flavor_name, sl);
 +  }
 +  return sl;
 +}
 +
  /** Parse a detached v3 networkstatus signature document between <b>s</b> and
   * <b>eos</b> and return the result.  Return -1 on failure. */
  ns_detached_signatures_t *
@@@ -3329,13 -2631,10 +3339,13 @@@ networkstatus_parse_detached_signatures
     * networkstatus_parse_vote_from_string(). */
    directory_token_t *tok;
    memarea_t *area = NULL;
 +  digests_t *digests;
  
    smartlist_t *tokens = smartlist_create();
    ns_detached_signatures_t *sigs =
      tor_malloc_zero(sizeof(ns_detached_signatures_t));
 +  sigs->digests = strmap_new();
 +  sigs->signatures = strmap_new();
  
    if (!eos)
      eos = s + strlen(s);
@@@ -3347,57 -2646,18 +3357,57 @@@
      goto err;
    }
  
 -  tok = find_by_keyword(tokens, K_CONSENSUS_DIGEST);
 -  if (strlen(tok->args[0]) != HEX_DIGEST_LEN) {
 -    log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 -  if (base16_decode(sigs->networkstatus_digest, DIGEST_LEN,
 -                    tok->args[0], strlen(tok->args[0])) < 0) {
 -    log_warn(LD_DIR, "Bad encoding on on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 +  /* Grab all the digest-like tokens. */
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *algname;
 +    digest_algorithm_t alg;
 +    const char *flavor;
 +    const char *hexdigest;
 +    size_t expected_length;
 +
 +    tok = _tok;
 +
 +    if (tok->tp == K_CONSENSUS_DIGEST) {
 +      algname = "sha1";
 +      alg = DIGEST_SHA1;
 +      flavor = "ns";
 +      hexdigest = tok->args[0];
 +    } else if (tok->tp == K_ADDITIONAL_DIGEST) {
 +      int a = crypto_digest_algorithm_parse_name(tok->args[1]);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", tok->args[0]);
 +        continue;
 +      }
 +      alg = (digest_algorithm_t) a;
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      hexdigest = tok->args[2];
 +    } else {
 +      continue;
 +    }
 +
 +    expected_length =
 +      (alg == DIGEST_SHA1) ? HEX_DIGEST_LEN : HEX_DIGEST256_LEN;
 +
 +    if (strlen(hexdigest) != expected_length) {
 +      log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +    digests = detached_get_digests(sigs, flavor);
 +    tor_assert(digests);
 +    if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
 +      log_warn(LD_DIR, "Multiple digests for %s with %s on detached "
 +               "signatures document", flavor, algname);
 +      continue;
 +    }
 +    if (base16_decode(digests->d[alg], DIGEST256_LEN,
 +                      hexdigest, strlen(hexdigest)) < 0) {
 +      log_warn(LD_DIR, "Bad encoding on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +  } SMARTLIST_FOREACH_END(_tok);
  
    tok = find_by_keyword(tokens, K_VALID_AFTER);
    if (parse_iso_time(tok->args[0], &sigs->valid_after)) {
@@@ -3417,102 -2677,57 +3427,102 @@@
      goto err;
    }
  
 -  sigs->signatures = smartlist_create();
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, _tok,
 -    {
 -      char id_digest[DIGEST_LEN];
 -      char sk_digest[DIGEST_LEN];
 -      networkstatus_voter_info_t *voter;
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *id_hexdigest;
 +    const char *sk_hexdigest;
 +    const char *algname;
 +    const char *flavor;
 +    digest_algorithm_t alg;
 +
 +    char id_digest[DIGEST_LEN];
 +    char sk_digest[DIGEST_LEN];
 +    smartlist_t *siglist;
 +    document_signature_t *sig;
 +    int is_duplicate;
  
 -      tok = _tok;
 -      if (tok->tp != K_DIRECTORY_SIGNATURE)
 -        continue;
 +    tok = _tok;
 +    if (tok->tp == K_DIRECTORY_SIGNATURE) {
        tor_assert(tok->n_args >= 2);
 +      flavor = "ns";
 +      algname = "sha1";
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else if (tok->tp == K_ADDITIONAL_SIGNATURE) {
 +      tor_assert(tok->n_args >= 4);
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      id_hexdigest = tok->args[2];
 +      sk_hexdigest = tok->args[3];
 +    } else {
 +      continue;
 +    }
  
 -      if (!tok->object_type ||
 -          strcmp(tok->object_type, "SIGNATURE") ||
 -          tok->object_size < 128 || tok->object_size > 512) {
 -        log_warn(LD_DIR, "Bad object type or length on directory-signature");
 -        goto err;
 +    {
 +      int a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", algname);
 +        continue;
        }
 +      alg = (digest_algorithm_t) a;
 +    }
  
 -      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 -          base16_decode(id_digest, sizeof(id_digest),
 -                        tok->args[0], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -                 "network-status vote.", escaped(tok->args[0]));
 -        goto err;
 -      }
 -      if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -          base16_decode(sk_digest, sizeof(sk_digest),
 -                        tok->args[1], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared digest %s in "
 -                 "network-status vote.", escaped(tok->args[1]));
 -        goto err;
 -      }
 +    if (!tok->object_type ||
 +        strcmp(tok->object_type, "SIGNATURE") ||
 +        tok->object_size < 128 || tok->object_size > 512) {
 +      log_warn(LD_DIR, "Bad object type or length on directory-signature");
 +      goto err;
 +    }
  
 -      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 -      memcpy(voter->identity_digest, id_digest, DIGEST_LEN);
 -      memcpy(voter->signing_key_digest, sk_digest, DIGEST_LEN);
 -      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING)
 -        goto err;
 -      voter->signature = tor_memdup(tok->object_body, tok->object_size);
 -      voter->signature_len = (int) tok->object_size;
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(id_digest, sizeof(id_digest),
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared identity %s in "
 +               "network-status vote.", escaped(id_hexdigest));
 +      goto err;
 +    }
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sk_digest, sizeof(sk_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      goto err;
 +    }
  
 -      smartlist_add(sigs->signatures, voter);
 +    siglist = detached_get_signatures(sigs, flavor);
 +    is_duplicate = 0;
 +    SMARTLIST_FOREACH(siglist, document_signature_t *, s, {
 +      if (s->alg == alg &&
 +          !memcmp(id_digest, s->identity_digest, DIGEST_LEN) &&
 +          !memcmp(sk_digest, s->signing_key_digest, DIGEST_LEN)) {
 +        is_duplicate = 1;
 +      }
      });
 +    if (is_duplicate) {
 +      log_warn(LD_DIR, "Two signatures with identical keys and algorithm "
 +               "found.");
 +      continue;
 +    }
 +
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    sig->alg = alg;
 +    memcpy(sig->identity_digest, id_digest, DIGEST_LEN);
 +    memcpy(sig->signing_key_digest, sk_digest, DIGEST_LEN);
 +    if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) {
 +      tor_free(sig);
 +      goto err;
 +    }
 +    sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +    sig->signature_len = (int) tok->object_size;
 +
 +    smartlist_add(siglist, sig);
 +  } SMARTLIST_FOREACH_END(_tok);
  
    goto done;
   err:
    ns_detached_signatures_free(sigs);
    sigs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "detached signatures");
@@@ -3568,7 -2783,7 +3578,7 @@@ router_parse_addr_policy_item_from_stri
   err:
    r = NULL;
   done:
 -  token_free(tok);
 +  token_clear(tok);
    if (area) {
      DUMP_AREA(area, "policy item");
      memarea_drop_all(area);
@@@ -3691,8 -2906,9 +3701,8 @@@ assert_addr_policy_ok(smartlist_t *lst
  
  /** Free all resources allocated for <b>tok</b> */
  static void
 -token_free(directory_token_t *tok)
 +token_clear(directory_token_t *tok)
  {
 -  tor_assert(tok);
    if (tok->key)
      crypto_free_pk_env(tok->key);
  }
@@@ -3704,7 -2920,7 +3714,7 @@@
  
  #define RET_ERR(msg)                                               \
    STMT_BEGIN                                                       \
 -    if (tok) token_free(tok);                                      \
 +    if (tok) token_clear(tok);                                      \
      tok = ALLOC_ZERO(sizeof(directory_token_t));                   \
      tok->tp = _ERR;                                                \
      tok->error = STRDUP(msg);                                      \
@@@ -3818,6 -3034,9 +3828,9 @@@ get_next_token(memarea_t *area
    /** Reject any object at least this big; it is probably an overflow, an
     * attack, a bug, or some other nonsense. */
  #define MAX_UNPARSED_OBJECT_SIZE (128*1024)
+   /** Reject any line at least this big; it is probably an overflow, an
+    * attack, a bug, or some other nonsense. */
+ #define MAX_LINE_LENGTH (128*1024)
  
    const char *next, *eol, *obstart;
    size_t obname_len;
@@@ -3837,6 -3056,10 +3850,10 @@@
    eol = memchr(*s, '\n', eos-*s);
    if (!eol)
      eol = eos;
+   if (eol - *s > MAX_LINE_LENGTH) {
+     RET_ERR("Line far too long");
+   }
+ 
    next = find_whitespace_eos(*s, eol);
  
    if (!strcmp_len(*s, "opt", next-*s)) {
@@@ -3993,7 -3216,7 +4010,7 @@@ tokenize_string(memarea_t *area
      tok = get_next_token(area, s, end, table);
      if (tok->tp == _ERR) {
        log_warn(LD_DIR, "parse error: %s", tok->error);
 -      token_free(tok);
 +      token_clear(tok);
        return -1;
      }
      ++counts[tok->tp];
@@@ -4107,11 -3330,17 +4124,11 @@@ find_all_exitpolicy(smartlist_t *s
    return out;
  }
  
 -/** Compute the SHA-1 digest of the substring of <b>s</b> taken from the first
 - * occurrence of <b>start_str</b> through the first instance of c after the
 - * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 - * <b>digest</b>; return 0 on success.
 - *
 - * If no such substring exists, return -1.
 - */
  static int
 -router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +router_get_hash_impl_helper(const char *s, size_t s_len,
                              const char *start_str,
 -                            const char *end_str, char end_c)
 +                            const char *end_str, char end_c,
 +                            const char **start_out, const char **end_out)
  {
    const char *start, *end;
    start = tor_memstr(s, s_len, start_str);
@@@ -4138,214 -3367,14 +4155,214 @@@
    }
    ++end;
  
 -  if (crypto_digest(digest, start, end-start)) {
 -    log_warn(LD_BUG,"couldn't compute digest");
 +  *start_out = start;
 +  *end_out = end;
 +  return 0;
 +}
 +
 +/** Compute the digest of the substring of <b>s</b> taken from the first
 + * occurrence of <b>start_str</b> through the first instance of c after the
 + * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 + * <b>digest</b>; return 0 on success.
 + *
 + * If no such substring exists, return -1.
 + */
 +static int
 +router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +                     const char *start_str,
 +                     const char *end_str, char end_c,
 +                     digest_algorithm_t alg)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
      return -1;
 +
 +  if (alg == DIGEST_SHA1) {
 +    if (crypto_digest(digest, start, end-start)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
 +  } else {
 +    if (crypto_digest256(digest, start, end-start, alg)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
    }
  
    return 0;
  }
  
 +/** As router_get_hash_impl, but compute all hashes. */
 +static int
 +router_get_hashes_impl(const char *s, size_t s_len, digests_t *digests,
 +                       const char *start_str,
 +                       const char *end_str, char end_c)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
 +    return -1;
 +
 +  if (crypto_digest_all(digests, start, end-start)) {
 +    log_warn(LD_BUG,"couldn't compute digests");
 +    return -1;
 +  }
 +
 +  return 0;
 +}
 +
 +/** Assuming that s starts with a microdesc, return the start of the
 + * *NEXT* one.  Return NULL on "not found." */
 +static const char *
 +find_start_of_next_microdesc(const char *s, const char *eos)
 +{
 +  int started_with_annotations;
 +  s = eat_whitespace_eos(s, eos);
 +  if (!s)
 +    return NULL;
 +
 +#define CHECK_LENGTH() STMT_BEGIN \
 +    if (s+32 > eos)               \
 +      return NULL;                \
 +  STMT_END
 +
 +#define NEXT_LINE() STMT_BEGIN            \
 +    s = memchr(s, '\n', eos-s);           \
 +    if (!s || s+1 >= eos)                 \
 +      return NULL;                        \
 +    s++;                                  \
 +  STMT_END
 +
 +  CHECK_LENGTH();
 +
 +  started_with_annotations = (*s == '@');
 +
 +  if (started_with_annotations) {
 +    /* Start by advancing to the first non-annotation line. */
 +    while (*s == '@')
 +      NEXT_LINE();
 +  }
 +  CHECK_LENGTH();
 +
 +  /* Now we should be pointed at an onion-key line.  If we are, then skip
 +   * it. */
 +  if (!strcmpstart(s, "onion-key"))
 +    NEXT_LINE();
 +
 +  /* Okay, now we're pointed at the first line of the microdescriptor which is
 +     not an annotation or onion-key.  The next line that _is_ an annotation or
 +     onion-key is the start of the next microdescriptor. */
 +  while (s+32 < eos) {
 +    if (*s == '@' || !strcmpstart(s, "onion-key"))
 +      return s;
 +    NEXT_LINE();
 +  }
 +  return NULL;
 +
 +#undef CHECK_LENGTH
 +#undef NEXT_LINE
 +}
 +
 +/** Parse as many microdescriptors as are found from the string starting at
 + * <b>s</b> and ending at <b>eos</b>.  If allow_annotations is set, read any
 + * annotations we recognize and ignore ones we don't.  If <b>copy_body</b> is
 + * true, then strdup the bodies of the microdescriptors.  Return all newly
 + * parsed microdescriptors in a newly allocated smartlist_t. */
 +smartlist_t *
 +microdescs_parse_from_string(const char *s, const char *eos,
 +                             int allow_annotations, int copy_body)
 +{
 +  smartlist_t *tokens;
 +  smartlist_t *result;
 +  microdesc_t *md = NULL;
 +  memarea_t *area;
 +  const char *start = s;
 +  const char *start_of_next_microdesc;
 +  int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0;
 +
 +  directory_token_t *tok;
 +
 +  if (!eos)
 +    eos = s + strlen(s);
 +
 +  s = eat_whitespace_eos(s, eos);
 +  area = memarea_new();
 +  result = smartlist_create();
 +  tokens = smartlist_create();
 +
 +  while (s < eos) {
 +    start_of_next_microdesc = find_start_of_next_microdesc(s, eos);
 +    if (!start_of_next_microdesc)
 +      start_of_next_microdesc = eos;
 +
 +    if (tokenize_string(area, s, start_of_next_microdesc, tokens,
 +                        microdesc_token_table, flags)) {
 +      log_warn(LD_DIR, "Unparseable microdescriptor");
 +      goto next;
 +    }
 +
 +    md = tor_malloc_zero(sizeof(microdesc_t));
 +    {
 +      const char *cp = tor_memstr(s, start_of_next_microdesc-s,
 +                                  "onion-key");
 +      tor_assert(cp);
 +
 +      md->bodylen = start_of_next_microdesc - cp;
 +      if (copy_body)
 +        md->body = tor_strndup(cp, md->bodylen);
 +      else
 +        md->body = (char*)cp;
 +      md->off = cp - start;
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) {
 +      if (parse_iso_time(tok->args[0], &md->last_listed)) {
 +        log_warn(LD_DIR, "Bad last-listed time in microdescriptor");
 +        goto next;
 +      }
 +    }
 +
 +    tok = find_by_keyword(tokens, K_ONION_KEY);
 +    md->onion_pkey = tok->key;
 +    tok->key = NULL;
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) {
 +      int i;
 +      md->family = smartlist_create();
 +      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 next;
 +        }
 +        smartlist_add(md->family, tor_strdup(tok->args[i]));
 +      }
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_P))) {
 +      md->exitsummary = tor_strdup(tok->args[0]);
 +    }
 +
 +    crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256);
 +
 +    smartlist_add(result, md);
 +
 +    md = NULL;
 +  next:
 +    microdesc_free(md);
 +
 +    memarea_clear(area);
 +    smartlist_clear(tokens);
 +    s = start_of_next_microdesc;
 +  }
 +
 +  memarea_drop_all(area);
 +  smartlist_free(tokens);
 +
 +  return result;
 +}
 +
  /** Parse the Tor version of the platform string <b>platform</b>,
   * and compare it to the version in <b>cutoff</b>. Return 1 if
   * the router is at least as new as the cutoff, else return 0.
@@@ -4370,7 -3399,7 +4387,7 @@@ tor_version_as_new_as(const char *platf
    if (!*start) return 0;
    s = (char *)find_whitespace(start); /* also finds '\0', which is fine */
    s2 = (char*)eat_whitespace(s);
 -  if (!strcmpstart(s2, "(r"))
 +  if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-"))
      s = (char*)find_whitespace(s2);
  
    if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */
@@@ -4466,23 -3495,6 +4483,23 @@@ tor_version_parse(const char *s, tor_ve
    if (!strcmpstart(cp, "(r")) {
      cp += 2;
      out->svn_revision = (int) strtol(cp,&eos,10);
 +  } else if (!strcmpstart(cp, "(git-")) {
 +    char *close_paren = strchr(cp, ')');
 +    int hexlen;
 +    char digest[DIGEST_LEN];
 +    if (! close_paren)
 +      return -1;
 +    cp += 5;
 +    if (close_paren-cp > HEX_DIGEST_LEN)
 +      return -1;
 +    hexlen = (int)(close_paren-cp);
 +    memset(digest, 0, sizeof(digest));
 +    if ( hexlen == 0 || (hexlen % 2) == 1)
 +      return -1;
 +    if (base16_decode(digest, hexlen/2, cp, hexlen))
 +      return -1;
 +    memcpy(out->git_tag, digest, hexlen/2);
 +    out->git_tag_len = hexlen/2;
    }
  
    return 0;
@@@ -4508,14 -3520,8 +4525,14 @@@ tor_version_compare(tor_version_t *a, t
      return i;
    else if ((i = strcmp(a->status_tag, b->status_tag)))
      return i;
 +  else if ((i = a->svn_revision - b->svn_revision))
 +    return i;
 +  else if ((i = a->git_tag_len - b->git_tag_len))
 +    return i;
 +  else if (a->git_tag_len)
 +    return memcmp(a->git_tag, b->git_tag, a->git_tag_len);
    else
 -    return a->svn_revision - b->svn_revision;
 +    return 0;
  }
  
  /** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
@@@ -4604,7 -3610,7 +4621,7 @@@ rend_parse_v2_service_descriptor(rend_s
    /* Compute descriptor hash for later validation. */
    if (router_get_hash_impl(desc, strlen(desc), desc_hash,
                             "rendezvous-service-descriptor ",
 -                           "\nsignature", '\n') < 0) {
 +                           "\nsignature", '\n', DIGEST_SHA1) < 0) {
      log_warn(LD_REND, "Couldn't compute descriptor hash.");
      goto err;
    }
@@@ -4723,7 -3729,7 +4740,7 @@@
    /* Parse and verify signature. */
    tok = find_by_keyword(tokens, R_SIGNATURE);
    note_crypto_pk_op(VERIFY_RTR);
 -  if (check_signature_token(desc_hash, tok, result->pk, 0,
 +  if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
                              "v2 rendezvous service descriptor") < 0)
      goto err;
    /* Verify that descriptor ID belongs to public key and secret ID part. */
@@@ -4737,11 -3743,12 +4754,11 @@@
    }
    goto done;
   err:
 -  if (result)
 -    rend_service_descriptor_free(result);
 +  rend_service_descriptor_free(result);
    result = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area)
@@@ -4899,7 -3906,7 +4916,7 @@@ rend_parse_introduction_points(rend_ser
        eos = eos+1;
      tor_assert(eos <= intro_points_encoded+intro_points_encoded_size);
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -4972,7 -3979,7 +4989,7 @@@
  
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);
@@@ -5011,7 -4018,7 +5028,7 @@@ rend_parse_client_keys(strmap_t *parsed
      else
        eos = eos + 1;
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -5083,7 -4090,7 +5100,7 @@@
    result = -1;
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);