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

[or-cvs] [tor/maint-0.2.2] Always nul-terminate the result passed to evdns_server_add_ptr_reply



commit a16902b9d4b0a912eb0a252bb945cbeaaa40dacb
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Mon Jan 10 16:18:32 2011 -0500

    Always nul-terminate the result passed to evdns_server_add_ptr_reply
    
    In dnsserv_resolved(), we carefully made a nul-terminated copy of the
    answer in a PTR RESOLVED cell... then never used that nul-terminated
    copy.  Ouch.
    
    Surprisingly this one isn't as huge a security problem as it could be.
    The only place where the input to dnsserv_resolved wasn't necessarily
    nul-terminated was when it was called indirectly from relay.c with the
    contents of a relay cell's payload.  If the end of the payload was
    filled with junk, eventdns.c would take the strdup() of the name [This
    part is bad; we might crash there if the cell is in a bad part of the
    stack or the heap] and get a name of at least length
    495[*]. eventdns.c then rejects any name of length over 255, so the
    bogus data would be neither transmitted nor altered.
    
      [*] If the name was less than 495 bytes long, the client wouldn't
         actually be reading off the end of the cell.
    
    Nonetheless this is a reasonably annoying bug.  Better fix it.
    
    Found while looking at bug 2332, reported by doorss.  Bugfix on
    0.2.0.1-alpha.
---
 changes/bug2332  |    4 ++++
 src/or/dnsserv.c |    2 +-
 2 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/changes/bug2332 b/changes/bug2332
new file mode 100644
index 0000000..5f73ddd
--- /dev/null
+++ b/changes/bug2332
@@ -0,0 +1,4 @@
+  o Minor bugfixes
+    - Fix a bug with handling misformed replies to reverse DNS lookup
+      requests in DNSPort.  Bugfix on Tor 0.2.0.1-alpha.  Related to a bug
+      reported by doorss.
diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c
index 579080b..57c4493 100644
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@ -275,7 +275,7 @@ dnsserv_resolved(edge_connection_t *conn,
     char *ans = tor_strndup(answer, answer_len);
     evdns_server_request_add_ptr_reply(req, NULL,
                                        name,
-                                       (char*)answer, ttl);
+                                       ans, ttl);
     tor_free(ans);
   } else if (answer_type == RESOLVED_TYPE_ERROR) {
     err = DNS_ERR_NOTEXIST;

From 115782bdbe42e4b3d5cb386d2939a883bc381d12 Mon Sep 17 00:00:00 2001
Patch-Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Subject: [tor/maint-0.2.2] Fix a heap overflow found by debuger, and make it harder to make that mistake again

commit 115782bdbe42e4b3d5cb386d2939a883bc381d12
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Thu Jan 13 14:36:41 2011 -0500

    Fix a heap overflow found by debuger, and make it harder to make that mistake again
    
    Our public key functions assumed that they were always writing into a
    large enough buffer.  In one case, they weren't.
    
    (Incorporates fixes from sebastian)
---
 changes/tolen_asserts  |    9 ++++++++
 src/common/crypto.c    |   53 ++++++++++++++++++++++++++++++++++++++----------
 src/common/crypto.h    |   12 ++++++----
 src/or/config.c        |    3 +-
 src/or/networkstatus.c |    1 +
 src/or/onion.c         |    2 +
 src/or/rendclient.c    |    1 +
 src/or/rendcommon.c    |    4 ++-
 src/or/rendservice.c   |    6 +++-
 src/or/routerlist.c    |    3 +-
 src/or/routerparse.c   |   16 +++++++++-----
 src/or/test.c          |   37 +++++++++++++++++++-------------
 12 files changed, 105 insertions(+), 42 deletions(-)

diff --git a/changes/tolen_asserts b/changes/tolen_asserts
new file mode 100644
index 0000000..90cdb2d
--- /dev/null
+++ b/changes/tolen_asserts
@@ -0,0 +1,9 @@
+  o Major bugfixes (security)
+    - Fix a heap overflow bug where an adversary could cause heap
+      corruption.  Since the contents of the corruption would need to be
+      the output of an RSA decryption, we do not think this is easy to
+      turn in to a remote code execution attack, but everybody should
+      upgrade anyway.  Found by debuger.  Bugfix on 0.1.2.10-rc.
+  o Defensive programming
+    - Introduce output size checks on all of our decryption functions.
+
diff --git a/src/common/crypto.c b/src/common/crypto.c
index 3343980..7cb849a 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -717,9 +717,12 @@ crypto_pk_copy_full(crypto_pk_env_t *env)
  * in <b>env</b>, using the padding method <b>padding</b>.  On success,
  * write the result to <b>to</b>, and return the number of bytes
  * written.  On failure, return -1.
+ *
+ * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+ * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
+crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                          const char *from, size_t fromlen, int padding)
 {
   int r;
@@ -727,6 +730,7 @@ crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
   tor_assert(from);
   tor_assert(to);
   tor_assert(fromlen<INT_MAX);
+  tor_assert(tolen >= crypto_pk_keysize(env));
 
   r = RSA_public_encrypt((int)fromlen,
                          (unsigned char*)from, (unsigned char*)to,
@@ -742,9 +746,13 @@ crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
  * in <b>env</b>, using the padding method <b>padding</b>.  On success,
  * write the result to <b>to</b>, and return the number of bytes
  * written.  On failure, return -1.
+ *
+ * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+ * at least the length of the modulus of <b>env</b>.
  */
 int
 crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
+                          size_t tolen,
                           const char *from, size_t fromlen,
                           int padding, int warnOnFailure)
 {
@@ -754,6 +762,7 @@ crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
   tor_assert(to);
   tor_assert(env->key);
   tor_assert(fromlen<INT_MAX);
+  tor_assert(tolen >= crypto_pk_keysize(env));
   if (!env->key->p)
     /* Not a private key */
     return -1;
@@ -774,9 +783,13 @@ crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
  * public key in <b>env</b>, using PKCS1 padding.  On success, write the
  * signed data to <b>to</b>, and return the number of bytes written.
  * On failure, return -1.
+ *
+ * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+ * at least the length of the modulus of <b>env</b>.
  */
 int
 crypto_pk_public_checksig(crypto_pk_env_t *env, char *to,
+                          size_t tolen,
                           const char *from, size_t fromlen)
 {
   int r;
@@ -784,6 +797,7 @@ crypto_pk_public_checksig(crypto_pk_env_t *env, char *to,
   tor_assert(from);
   tor_assert(to);
   tor_assert(fromlen < INT_MAX);
+  tor_assert(tolen >= crypto_pk_keysize(env));
   r = RSA_public_decrypt((int)fromlen,
                          (unsigned char*)from, (unsigned char*)to,
                          env->key, RSA_PKCS1_PADDING);
@@ -806,6 +820,7 @@ crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data,
 {
   char digest[DIGEST_LEN];
   char *buf;
+  size_t buflen;
   int r;
 
   tor_assert(env);
@@ -818,8 +833,9 @@ crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data,
     log_warn(LD_BUG, "couldn't compute digest");
     return -1;
   }
-  buf = tor_malloc(crypto_pk_keysize(env)+1);
-  r = crypto_pk_public_checksig(env,buf,sig,siglen);
+  buflen = crypto_pk_keysize(env)+1;
+  buf = tor_malloc(buflen);
+  r = crypto_pk_public_checksig(env,buf,buflen,sig,siglen);
   if (r != DIGEST_LEN) {
     log_warn(LD_CRYPTO, "Invalid signature");
     tor_free(buf);
@@ -839,9 +855,12 @@ crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data,
  * <b>env</b>, using PKCS1 padding.  On success, write the signature to
  * <b>to</b>, and return the number of bytes written.  On failure, return
  * -1.
+ *
+ * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+ * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
+crypto_pk_private_sign(crypto_pk_env_t *env, char *to, size_t tolen,
                        const char *from, size_t fromlen)
 {
   int r;
@@ -849,6 +868,7 @@ crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
   tor_assert(from);
   tor_assert(to);
   tor_assert(fromlen < INT_MAX);
+  tor_assert(tolen >= crypto_pk_keysize(env));
   if (!env->key->p)
     /* Not a private key */
     return -1;
@@ -867,16 +887,19 @@ crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
  * <b>from</b>; sign the data with the private key in <b>env</b>, and
  * store it in <b>to</b>.  Return the number of bytes written on
  * success, and -1 on failure.
+ *
+ * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+ * at least the length of the modulus of <b>env</b>.
  */
 int
-crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to,
+crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen)
 {
   int r;
   char digest[DIGEST_LEN];
   if (crypto_digest(digest,from,fromlen)<0)
     return -1;
-  r = crypto_pk_private_sign(env,to,digest,DIGEST_LEN);
+  r = crypto_pk_private_sign(env,to,tolen,digest,DIGEST_LEN);
   memset(digest, 0, sizeof(digest));
   return r;
 }
@@ -900,7 +923,7 @@ crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to,
  */
 int
 crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env,
-                                char *to,
+                                char *to, size_t tolen,
                                 const char *from,
                                 size_t fromlen,
                                 int padding, int force)
@@ -923,8 +946,13 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env,
 
   if (!force && fromlen+overhead <= pkeylen) {
     /* It all fits in a single encrypt. */
-    return crypto_pk_public_encrypt(env,to,from,fromlen,padding);
+    return crypto_pk_public_encrypt(env,to,
+                                    tolen,
+                                    from,fromlen,padding);
   }
+  tor_assert(tolen >= fromlen + overhead + CIPHER_KEY_LEN);
+  tor_assert(tolen >= pkeylen);
+
   cipher = crypto_new_cipher_env();
   if (!cipher) return -1;
   if (crypto_cipher_generate_key(cipher)<0)
@@ -946,7 +974,7 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env,
   /* Length of symmetrically encrypted data. */
   symlen = fromlen-(pkeylen-overhead-CIPHER_KEY_LEN);
 
-  outlen = crypto_pk_public_encrypt(env,to,buf,pkeylen-overhead,padding);
+  outlen = crypto_pk_public_encrypt(env,to,tolen,buf,pkeylen-overhead,padding);
   if (outlen!=(int)pkeylen) {
     goto err;
   }
@@ -972,6 +1000,7 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env,
 int
 crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env,
                                  char *to,
+                                 size_t tolen,
                                  const char *from,
                                  size_t fromlen,
                                  int padding, int warnOnFailure)
@@ -985,11 +1014,12 @@ crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env,
   pkeylen = crypto_pk_keysize(env);
 
   if (fromlen <= pkeylen) {
-    return crypto_pk_private_decrypt(env,to,from,fromlen,padding,
+    return crypto_pk_private_decrypt(env,to,tolen,from,fromlen,padding,
                                      warnOnFailure);
   }
+
   buf = tor_malloc(pkeylen+1);
-  outlen = crypto_pk_private_decrypt(env,buf,from,pkeylen,padding,
+  outlen = crypto_pk_private_decrypt(env,buf,pkeylen+1,from,pkeylen,padding,
                                      warnOnFailure);
   if (outlen<0) {
     log_fn(warnOnFailure?LOG_WARN:LOG_DEBUG, LD_CRYPTO,
@@ -1007,6 +1037,7 @@ crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env,
   }
   memcpy(to,buf+CIPHER_KEY_LEN,outlen-CIPHER_KEY_LEN);
   outlen -= CIPHER_KEY_LEN;
+  tor_assert(tolen - outlen >= fromlen - pkeylen);
   r = crypto_cipher_decrypt(cipher, to+outlen, from+pkeylen, fromlen-pkeylen);
   if (r<0)
     goto err;
diff --git a/src/common/crypto.h b/src/common/crypto.h
index 4fb06be..9cfb414 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -93,23 +93,25 @@ crypto_pk_env_t *crypto_pk_dup_key(crypto_pk_env_t *orig);
 crypto_pk_env_t *crypto_pk_copy_full(crypto_pk_env_t *orig);
 int crypto_pk_key_is_private(const crypto_pk_env_t *key);
 
-int crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
+int crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                              const char *from, size_t fromlen, int padding);
-int crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
+int crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen,
                               int padding, int warnOnFailure);
-int crypto_pk_public_checksig(crypto_pk_env_t *env, char *to,
+int crypto_pk_public_checksig(crypto_pk_env_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen);
 int crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data,
                                size_t datalen, const char *sig, size_t siglen);
-int crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
+int crypto_pk_private_sign(crypto_pk_env_t *env, char *to, size_t tolen,
                            const char *from, size_t fromlen);
-int crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to,
+int crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to, size_t tolen,
                                   const char *from, size_t fromlen);
 int crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env, char *to,
+                                    size_t tolen,
                                     const char *from, size_t fromlen,
                                     int padding, int force);
 int crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env, char *to,
+                                     size_t tolen,
                                      const char *from, size_t fromlen,
                                      int padding, int warnOnFailure);
 
diff --git a/src/or/config.c b/src/or/config.c
index 45f6114..f8cfd29 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -5321,7 +5321,8 @@ or_state_save(time_t now)
   tor_free(state);
   fname = get_datadir_fname("state");
   if (write_str_to_file(fname, contents, 0)<0) {
-    log_warn(LD_FS, "Unable to write state to file \"%s\"", fname);
+    log_warn(LD_FS, "Unable to write state to file \"%s\"; "
+             "will try again later", fname);
     tor_free(fname);
     tor_free(contents);
     return -1;
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 53e8a63..7106294 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -362,6 +362,7 @@ networkstatus_check_voter_signature(networkstatus_t *consensus,
   signed_digest = tor_malloc(signed_digest_len);
   if (crypto_pk_public_checksig(cert->signing_key,
                                 signed_digest,
+                                signed_digest_len,
                                 voter->signature,
                                 voter->signature_len) != DIGEST_LEN ||
       memcmp(signed_digest, consensus->networkstatus_digest, DIGEST_LEN)) {
diff --git a/src/or/onion.c b/src/or/onion.c
index 45c75b0..bf72b4c 100644
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@ -188,6 +188,7 @@ onion_skin_create(crypto_pk_env_t *dest_router_key,
 
   /* set meeting point, meeting cookie, etc here. Leave zero for now. */
   if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out,
+                                      ONIONSKIN_CHALLENGE_LEN,
                                       challenge, DH_KEY_LEN,
                                       PK_PKCS1_OAEP_PADDING, 1)<0)
     goto err;
@@ -230,6 +231,7 @@ onion_skin_server_handshake(const char *onion_skin, /*ONIONSKIN_CHALLENGE_LEN*/
       break;
     note_crypto_pk_op(DEC_ONIONSKIN);
     len = crypto_pk_private_hybrid_decrypt(k, challenge,
+                                           ONIONSKIN_CHALLENGE_LEN,
                                            onion_skin, ONIONSKIN_CHALLENGE_LEN,
                                            PK_PKCS1_OAEP_PADDING,0);
     if (len>0)
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index edd24d8..ab18d35 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -193,6 +193,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
   /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
    * to avoid buffer overflows? */
   r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
+                                      sizeof(payload)-DIGEST_LEN,
                                       tmp,
                                       (int)(dh_offset+DH_KEY_LEN),
                                       PK_PKCS1_OAEP_PADDING, 0);
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index 1d96f3d..d6f5443 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -700,7 +700,9 @@ rend_encode_service_descriptor(rend_service_descriptor_t *desc,
     cp += ipoint_len+1;
   }
   note_crypto_pk_op(REND_SERVER);
-  r = crypto_pk_private_sign_digest(key, cp, *str_out, cp-*str_out);
+  r = crypto_pk_private_sign_digest(key,
+                                    cp, buflen - (cp - *str_out),
+                                    *str_out, cp-*str_out);
   if (r<0) {
     tor_free(*str_out);
     return -1;
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index 9035ea4..07f01ae 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -979,7 +979,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   /* Next N bytes is encrypted with service key */
   note_crypto_pk_op(REND_SERVER);
   r = crypto_pk_private_hybrid_decrypt(
-       intro_key,buf,(char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
+       intro_key,buf,sizeof(buf),
+       (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
        PK_PKCS1_OAEP_PADDING,1);
   if (r<0) {
     log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell.");
@@ -1424,7 +1425,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
     goto err;
   len += 20;
   note_crypto_pk_op(REND_SERVER);
-  r = crypto_pk_private_sign_digest(intro_key, buf+len, buf, len);
+  r = crypto_pk_private_sign_digest(intro_key, buf+len, sizeof(buf)-len,
+                                    buf, len);
   if (r<0) {
     log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
     reason = END_CIRC_REASON_INTERNAL;
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index c6c84a8..7c8e36e 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -4676,7 +4676,8 @@ routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei,
 
   if (ei->pending_sig) {
     char signed_digest[128];
-    if (crypto_pk_public_checksig(ri->identity_pkey, signed_digest,
+    if (crypto_pk_public_checksig(ri->identity_pkey,
+                       signed_digest, sizeof(signed_digest),
                        ei->pending_sig, ei->pending_sig_len) != DIGEST_LEN ||
         memcmp(signed_digest, ei->cache_info.signed_descriptor_digest,
                DIGEST_LEN)) {
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index fc30c62..9ad84ed 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -571,10 +571,12 @@ router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest,
                                crypto_pk_env_t *private_key)
 {
   char *signature;
-  size_t i;
+  size_t i, keysize;
 
-  signature = tor_malloc(crypto_pk_keysize(private_key));
-  if (crypto_pk_private_sign(private_key, signature, digest, DIGEST_LEN) < 0) {
+  keysize = crypto_pk_keysize(private_key);
+  signature = tor_malloc(keysize);
+  if (crypto_pk_private_sign(private_key, signature, keysize,
+                             digest, DIGEST_LEN) < 0) {
 
     log_warn(LD_BUG,"Couldn't sign digest.");
     goto err;
@@ -924,6 +926,7 @@ check_signature_token(const char *digest,
                       const char *doctype)
 {
   char *signed_digest;
+  size_t keysize;
   const int check_authority = (flags & CST_CHECK_AUTHORITY);
   const int check_objtype = ! (flags & CST_NO_CHECK_OBJTYPE);
 
@@ -945,9 +948,10 @@ check_signature_token(const char *digest,
     }
   }
 
-  signed_digest = tor_malloc(tok->object_size);
-  if (crypto_pk_public_checksig(pkey, signed_digest, tok->object_body,
-                                tok->object_size)
+  keysize = crypto_pk_keysize(pkey);
+  signed_digest = tor_malloc(keysize);
+  if (crypto_pk_public_checksig(pkey, signed_digest, keysize,
+                                tok->object_body, tok->object_size)
       != DIGEST_LEN) {
     log_warn(LD_DIR, "Error reading %s: invalid signature.", doctype);
     tor_free(signed_digest);
diff --git a/src/or/test.c b/src/or/test.c
index 66fa560..103866b 100644
--- a/src/or/test.c
+++ b/src/or/test.c
@@ -701,25 +701,27 @@ test_crypto_pk(void)
   test_eq(128, crypto_pk_keysize(pk1));
   test_eq(128, crypto_pk_keysize(pk2));
 
-  test_eq(128, crypto_pk_public_encrypt(pk2, data1, "Hello whirled.", 15,
+  test_eq(128, crypto_pk_public_encrypt(pk2, data1, sizeof(data1),
+                                        "Hello whirled.", 15,
                                         PK_PKCS1_OAEP_PADDING));
-  test_eq(128, crypto_pk_public_encrypt(pk1, data2, "Hello whirled.", 15,
+  test_eq(128, crypto_pk_public_encrypt(pk1, data2, sizeof(data2),
+                                        "Hello whirled.", 15,
                                         PK_PKCS1_OAEP_PADDING));
   /* oaep padding should make encryption not match */
   test_memneq(data1, data2, 128);
-  test_eq(15, crypto_pk_private_decrypt(pk1, data3, data1, 128,
+  test_eq(15, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data1, 128,
                                         PK_PKCS1_OAEP_PADDING,1));
   test_streq(data3, "Hello whirled.");
   memset(data3, 0, 1024);
-  test_eq(15, crypto_pk_private_decrypt(pk1, data3, data2, 128,
+  test_eq(15, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data2, 128,
                                         PK_PKCS1_OAEP_PADDING,1));
   test_streq(data3, "Hello whirled.");
   /* Can't decrypt with public key. */
-  test_eq(-1, crypto_pk_private_decrypt(pk2, data3, data2, 128,
+  test_eq(-1, crypto_pk_private_decrypt(pk2, data3, sizeof(data3), data2, 128,
                                         PK_PKCS1_OAEP_PADDING,1));
   /* Try again with bad padding */
   memcpy(data2+1, "XYZZY", 5);  /* This has fails ~ once-in-2^40 */
-  test_eq(-1, crypto_pk_private_decrypt(pk1, data3, data2, 128,
+  test_eq(-1, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data2, 128,
                                         PK_PKCS1_OAEP_PADDING,1));
 
   /* File operations: save and load private key */
@@ -734,19 +736,22 @@ test_crypto_pk(void)
                                                    get_fname("xyzzy")) < 0);
   test_assert(! crypto_pk_read_private_key_from_filename(pk2,
                                                          get_fname("pkey1")));
-  test_eq(15, crypto_pk_private_decrypt(pk2, data3, data1, 128,
+  test_eq(15, crypto_pk_private_decrypt(pk2, data3, sizeof(data3), data1, 128,
                                         PK_PKCS1_OAEP_PADDING,1));
 
   /* Now try signing. */
   strlcpy(data1, "Ossifrage", 1024);
-  test_eq(128, crypto_pk_private_sign(pk1, data2, data1, 10));
-  test_eq(10, crypto_pk_public_checksig(pk1, data3, data2, 128));
+  test_eq(128, crypto_pk_private_sign(pk1, data2, sizeof(data2), data1, 10));
+  test_eq(10, crypto_pk_public_checksig(pk1, data3, sizeof(data3), data2, 128));
   test_streq(data3, "Ossifrage");
   /* Try signing digests. */
-  test_eq(128, crypto_pk_private_sign_digest(pk1, data2, data1, 10));
-  test_eq(20, crypto_pk_public_checksig(pk1, data3, data2, 128));
-  test_eq(0, crypto_pk_public_checksig_digest(pk1, data1, 10, data2, 128));
-  test_eq(-1, crypto_pk_public_checksig_digest(pk1, data1, 11, data2, 128));
+  test_eq(128, crypto_pk_private_sign_digest(pk1, data2, sizeof(data2),
+                                             data1, 10));
+  test_eq(20, crypto_pk_public_checksig(pk1, data3, sizeof(data1), data2, 128));
+  test_eq(0, crypto_pk_public_checksig_digest(pk1, data1,
+                                              10, data2, 128));
+  test_eq(-1, crypto_pk_public_checksig_digest(pk1, data1,
+                                               11, data2, 128));
   /*XXXX test failed signing*/
 
   /* Try encoding */
@@ -767,9 +772,11 @@ test_crypto_pk(void)
         continue;
       p = (i==0)?PK_NO_PADDING:
         (i==1)?PK_PKCS1_PADDING:PK_PKCS1_OAEP_PADDING;
-      len = crypto_pk_public_hybrid_encrypt(pk1,data2,data1,j,p,0);
+      len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2),
+                                            data1,j,p,0);
       test_assert(len>=0);
-      len = crypto_pk_private_hybrid_decrypt(pk1,data3,data2,len,p,1);
+      len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3),
+                                             data2,len,p,1);
       test_eq(len,j);
       test_memeq(data1,data3,j);
     }

From 50b06a2b76190170e9f80739f022696755b54b99 Mon Sep 17 00:00:00 2001
Patch-Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Subject: [tor/maint-0.2.2] make the description of tolen_asserts more dire

commit 50b06a2b76190170e9f80739f022696755b54b99
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Sat Jan 15 10:54:58 2011 -0500

    make the description of tolen_asserts more dire
    
    We have a CVE # for this bug.
---
 changes/tolen_asserts |    7 +++----
 1 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/changes/tolen_asserts b/changes/tolen_asserts
index 90cdb2d..a9834ab 100644
--- a/changes/tolen_asserts
+++ b/changes/tolen_asserts
@@ -1,9 +1,8 @@
   o Major bugfixes (security)
     - Fix a heap overflow bug where an adversary could cause heap
-      corruption.  Since the contents of the corruption would need to be
-      the output of an RSA decryption, we do not think this is easy to
-      turn in to a remote code execution attack, but everybody should
-      upgrade anyway.  Found by debuger.  Bugfix on 0.1.2.10-rc.
+      corruption.  This bug potentially allows remote code execution
+      attacks.  Found by debuger.  Fixes CVE-2011-0427.  Bugfix on
+      0.1.2.10-rc.
   o Defensive programming
     - Introduce output size checks on all of our decryption functions.
 

From ed87738ede789fb9eccfd2e5a34bd8c484dfe44e Mon Sep 17 00:00:00 2001
Patch-Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Subject: [tor/maint-0.2.2] Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2

commit ed87738ede789fb9eccfd2e5a34bd8c484dfe44e
Merge: b27f5cc 50b06a2
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Sat Jan 15 12:02:55 2011 -0500

    Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2
    
    Conflicts:
    	src/or/config.c
    	src/or/networkstatus.c
    	src/or/rendcommon.c
    	src/or/routerparse.c
    	src/or/test.c

 changes/bug2332        |    4 +++
 changes/tolen_asserts  |    8 +++++++
 src/common/crypto.c    |   53 ++++++++++++++++++++++++++++++++++++++----------
 src/common/crypto.h    |   12 ++++++----
 src/or/config.c        |    4 +-
 src/or/dnsserv.c       |    2 +-
 src/or/networkstatus.c |    1 +
 src/or/onion.c         |    2 +
 src/or/rendclient.c    |    1 +
 src/or/rendservice.c   |    6 +++-
 src/or/routerlist.c    |    3 +-
 src/or/routerparse.c   |   18 ++++++++++------
 src/test/test_crypto.c |   32 +++++++++++++++++-----------
 13 files changed, 104 insertions(+), 42 deletions(-)

diff --combined src/common/crypto.c
index e47fa56,7cb849a..15b5818
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@@ -27,7 -27,6 +27,7 @@@
  #include <openssl/rsa.h>
  #include <openssl/pem.h>
  #include <openssl/evp.h>
 +#include <openssl/engine.h>
  #include <openssl/rand.h>
  #include <openssl/opensslv.h>
  #include <openssl/bn.h>
@@@ -50,9 -49,9 +50,9 @@@
  
  #define CRYPTO_PRIVATE
  #include "crypto.h"
 -#include "log.h"
 +#include "../common/torlog.h"
  #include "aes.h"
 -#include "util.h"
 +#include "../common/util.h"
  #include "container.h"
  #include "compat.h"
  
@@@ -62,33 -61,6 +62,33 @@@
  
  #include <openssl/engine.h>
  
 +#ifdef ANDROID
 +/* Android's OpenSSL seems to have removed all of its Engine support. */
 +#define DISABLE_ENGINES
 +#endif
 +
 +#if OPENSSL_VERSION_NUMBER < 0x00908000l
 +/* On OpenSSL versions before 0.9.8, there is no working SHA256
 + * implementation, so we use Tom St Denis's nice speedy one, slightly adapted
 + * to our needs */
 +#define SHA256_CTX sha256_state
 +#define SHA256_Init sha256_init
 +#define SHA256_Update sha256_process
 +#define LTC_ARGCHK(x) tor_assert(x)
 +#include "sha256.c"
 +#define SHA256_Final(a,b) sha256_done(b,a)
 +
 +static unsigned char *
 +SHA256(const unsigned char *m, size_t len, unsigned char *d)
 +{
 +  SHA256_CTX ctx;
 +  SHA256_Init(&ctx);
 +  SHA256_Update(&ctx, m, len);
 +  SHA256_Final(d, &ctx);
 +  return d;
 +}
 +#endif
 +
  /** Macro: is k a valid RSA public or private key? */
  #define PUBLIC_KEY_OK(k) ((k) && (k)->key && (k)->key->n)
  /** Macro: is k a valid RSA private key? */
@@@ -122,7 -94,7 +122,7 @@@ struct crypto_dh_env_t 
  };
  
  static int setup_openssl_threading(void);
 -static int tor_check_dh_key(BIGNUM *bn);
 +static int tor_check_dh_key(int severity, BIGNUM *bn);
  
  /** Return the number of bytes added by padding method <b>padding</b>.
   */
@@@ -179,7 -151,6 +179,7 @@@ crypto_log_errors(int severity, const c
    }
  }
  
 +#ifndef DISABLE_ENGINES
  /** Log any OpenSSL engines we're using at NOTICE. */
  static void
  log_engine(const char *fn, ENGINE *e)
@@@ -194,82 -165,37 +194,82 @@@
      log(LOG_INFO, LD_CRYPTO, "Using default implementation for %s", fn);
    }
  }
 +#endif
 +
 +#ifndef DISABLE_ENGINES
 +/** Try to load an engine in a shared library via fully qualified path.
 + */
 +static ENGINE *
 +try_load_engine(const char *path, const char *engine)
 +{
 +  ENGINE *e = ENGINE_by_id("dynamic");
 +  if (e) {
 +    if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
 +      ENGINE_free(e);
 +      e = NULL;
 +    }
 +  }
 +  return e;
 +}
 +#endif
  
  /** Initialize the crypto library.  Return 0 on success, -1 on failure.
   */
  int
 -crypto_global_init(int useAccel)
 +crypto_global_init(int useAccel, const char *accelName, const char *accelDir)
  {
    if (!_crypto_global_initialized) {
      ERR_load_crypto_strings();
      OpenSSL_add_all_algorithms();
      _crypto_global_initialized = 1;
      setup_openssl_threading();
 -    /* XXX the below is a bug, since we can't know if we're supposed
 -     * to be using hardware acceleration or not. we should arrange
 -     * for this function to be called before init_keys. But make it
 -     * not complain loudly, at least until we make acceleration work. */
 -    if (useAccel < 0) {
 -      log_info(LD_CRYPTO, "Initializing OpenSSL via tor_tls_init().");
 -    }
      if (useAccel > 0) {
 +#ifdef DISABLE_ENGINES
 +      (void)accelName;
 +      (void)accelDir;
 +      log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled.");
 +#else
 +      ENGINE *e = NULL;
 +
        log_info(LD_CRYPTO, "Initializing OpenSSL engine support.");
        ENGINE_load_builtin_engines();
 -      if (!ENGINE_register_all_complete())
 -        return -1;
 -
 -      /* XXXX make sure this isn't leaking. */
 +      ENGINE_register_all_complete();
 +
 +      if (accelName) {
 +        if (accelDir) {
 +          log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\""
 +                   " via path \"%s\".", accelName, accelDir);
 +          e = try_load_engine(accelName, accelDir);
 +        } else {
 +          log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\""
 +                   " acceleration support.", accelName);
 +          e = ENGINE_by_id(accelName);
 +        }
 +        if (!e) {
 +          log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        } else {
 +          log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        }
 +      }
 +      if (e) {
 +        log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine,"
 +                 " setting default ciphers.");
 +        ENGINE_set_default(e, ENGINE_METHOD_ALL);
 +      }
        log_engine("RSA", ENGINE_get_default_RSA());
        log_engine("DH", ENGINE_get_default_DH());
        log_engine("RAND", ENGINE_get_default_RAND());
        log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1));
        log_engine("3DES", ENGINE_get_cipher_engine(NID_des_ede3_ecb));
        log_engine("AES", ENGINE_get_cipher_engine(NID_aes_128_ecb));
 +#endif
 +    } else {
 +      log_info(LD_CRYPTO, "NOT using OpenSSL engine support.");
      }
      return crypto_seed_rng(1);
    }
@@@ -291,11 -217,7 +291,11 @@@ crypto_global_cleanup(void
    EVP_cleanup();
    ERR_remove_state(0);
    ERR_free_strings();
 +
 +#ifndef DISABLE_ENGINES
    ENGINE_cleanup();
 +#endif
 +
    CONF_modules_unload(1);
    CRYPTO_cleanup_all_ex_data();
  #ifdef TOR_IS_MULTITHREADED
@@@ -337,8 -259,7 +337,8 @@@ _crypto_new_pk_env_evp_pkey(EVP_PKEY *p
    return _crypto_new_pk_env_rsa(rsa);
  }
  
 -/** Helper, used by tor-checkkey.c.  Return the RSA from a crypto_pk_env_t. */
 +/** Helper, used by tor-checkkey.c and tor-gencert.c.  Return the RSA from a
 + * crypto_pk_env_t. */
  RSA *
  _crypto_pk_env_get_rsa(crypto_pk_env_t *env)
  {
@@@ -400,12 -321,10 +400,12 @@@ crypto_new_pk_env(void
  void
  crypto_free_pk_env(crypto_pk_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    if (--env->refs > 0)
      return;
 +  tor_assert(env->refs == 0);
  
    if (env->key)
      RSA_free(env->key);
@@@ -428,7 -347,10 +428,7 @@@ crypto_create_init_cipher(const char *k
      return NULL;
    }
  
 -  if (crypto_cipher_set_key(crypto, key)) {
 -    crypto_log_errors(LOG_WARN, "setting symmetric key");
 -    goto error;
 -  }
 +  crypto_cipher_set_key(crypto, key);
  
    if (encrypt_mode)
      r = crypto_cipher_encrypt_init_cipher(crypto);
@@@ -462,8 -384,7 +462,8 @@@ crypto_new_cipher_env(void
  void
  crypto_free_cipher_env(crypto_cipher_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    tor_assert(env->cipher);
    aes_free_cipher(env->cipher);
@@@ -473,11 -394,11 +473,11 @@@
  
  /* public key crypto */
  
 -/** Generate a new public/private keypair in <b>env</b>.  Return 0 on
 - * success, -1 on failure.
 +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>.
 + * Return 0 on success, -1 on failure.
   */
  int
 -crypto_pk_generate_key(crypto_pk_env_t *env)
 +crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits)
  {
    tor_assert(env);
  
@@@ -485,7 -406,7 +485,7 @@@
      RSA_free(env->key);
  #if OPENSSL_VERSION_NUMBER < 0x00908000l
    /* In OpenSSL 0.9.7, RSA_generate_key is all we have. */
 -  env->key = RSA_generate_key(PK_BYTES*8,65537, NULL, NULL);
 +  env->key = RSA_generate_key(bits, 65537, NULL, NULL);
  #else
    /* In OpenSSL 0.9.8, RSA_generate_key is deprecated. */
    {
@@@ -498,7 -419,7 +498,7 @@@
      r = RSA_new();
      if (!r)
        goto done;
 -    if (RSA_generate_key_ex(r, PK_BYTES*8, e, NULL) == -1)
 +    if (RSA_generate_key_ex(r, bits, e, NULL) == -1)
        goto done;
  
      env->key = r;
@@@ -780,25 -701,14 +780,25 @@@ crypto_pk_env_t 
  crypto_pk_copy_full(crypto_pk_env_t *env)
  {
    RSA *new_key;
 +  int privatekey = 0;
    tor_assert(env);
    tor_assert(env->key);
  
    if (PRIVATE_KEY_OK(env)) {
      new_key = RSAPrivateKey_dup(env->key);
 +    privatekey = 1;
    } else {
      new_key = RSAPublicKey_dup(env->key);
    }
 +  if (!new_key) {
 +    log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.",
 +            privatekey?"private":"public");
 +    crypto_log_errors(LOG_ERR,
 +                      privatekey ? "Duplicating a private key" :
 +                      "Duplicating a public key");
 +    tor_fragile_assert();
 +    return NULL;
 +  }
  
    return _crypto_new_pk_env_rsa(new_key);
  }
@@@ -807,9 -717,12 +807,12 @@@
   * in <b>env</b>, using the padding method <b>padding</b>.  On success,
   * write the result to <b>to</b>, and return the number of bytes
   * written.  On failure, return -1.
+  *
+  * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+  * at least the length of the modulus of <b>env</b>.
   */
  int
- crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
+ crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                           const char *from, size_t fromlen, int padding)
  {
    int r;
@@@ -817,6 -730,7 +820,7 @@@
    tor_assert(from);
    tor_assert(to);
    tor_assert(fromlen<INT_MAX);
+   tor_assert(tolen >= crypto_pk_keysize(env));
  
    r = RSA_public_encrypt((int)fromlen,
                           (unsigned char*)from, (unsigned char*)to,
@@@ -832,9 -746,13 +836,13 @@@
   * in <b>env</b>, using the padding method <b>padding</b>.  On success,
   * write the result to <b>to</b>, and return the number of bytes
   * written.  On failure, return -1.
+  *
+  * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+  * at least the length of the modulus of <b>env</b>.
   */
  int
  crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
+                           size_t tolen,
                            const char *from, size_t fromlen,
                            int padding, int warnOnFailure)
  {
@@@ -844,6 -762,7 +852,7 @@@
    tor_assert(to);
    tor_assert(env->key);
    tor_assert(fromlen<INT_MAX);
+   tor_assert(tolen >= crypto_pk_keysize(env));
    if (!env->key->p)
      /* Not a private key */
      return -1;
@@@ -864,9 -783,13 +873,13 @@@
   * public key in <b>env</b>, using PKCS1 padding.  On success, write the
   * signed data to <b>to</b>, and return the number of bytes written.
   * On failure, return -1.
+  *
+  * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+  * at least the length of the modulus of <b>env</b>.
   */
  int
  crypto_pk_public_checksig(crypto_pk_env_t *env, char *to,
+                           size_t tolen,
                            const char *from, size_t fromlen)
  {
    int r;
@@@ -874,6 -797,7 +887,7 @@@
    tor_assert(from);
    tor_assert(to);
    tor_assert(fromlen < INT_MAX);
+   tor_assert(tolen >= crypto_pk_keysize(env));
    r = RSA_public_decrypt((int)fromlen,
                           (unsigned char*)from, (unsigned char*)to,
                           env->key, RSA_PKCS1_PADDING);
@@@ -896,6 -820,7 +910,7 @@@ crypto_pk_public_checksig_digest(crypto
  {
    char digest[DIGEST_LEN];
    char *buf;
+   size_t buflen;
    int r;
  
    tor_assert(env);
@@@ -908,8 -833,9 +923,9 @@@
      log_warn(LD_BUG, "couldn't compute digest");
      return -1;
    }
-   buf = tor_malloc(crypto_pk_keysize(env)+1);
-   r = crypto_pk_public_checksig(env,buf,sig,siglen);
+   buflen = crypto_pk_keysize(env)+1;
+   buf = tor_malloc(buflen);
+   r = crypto_pk_public_checksig(env,buf,buflen,sig,siglen);
    if (r != DIGEST_LEN) {
      log_warn(LD_CRYPTO, "Invalid signature");
      tor_free(buf);
@@@ -929,9 -855,12 +945,12 @@@
   * <b>env</b>, using PKCS1 padding.  On success, write the signature to
   * <b>to</b>, and return the number of bytes written.  On failure, return
   * -1.
+  *
+  * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+  * at least the length of the modulus of <b>env</b>.
   */
  int
- crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
+ crypto_pk_private_sign(crypto_pk_env_t *env, char *to, size_t tolen,
                         const char *from, size_t fromlen)
  {
    int r;
@@@ -939,6 -868,7 +958,7 @@@
    tor_assert(from);
    tor_assert(to);
    tor_assert(fromlen < INT_MAX);
+   tor_assert(tolen >= crypto_pk_keysize(env));
    if (!env->key->p)
      /* Not a private key */
      return -1;
@@@ -957,16 -887,19 +977,19 @@@
   * <b>from</b>; sign the data with the private key in <b>env</b>, and
   * store it in <b>to</b>.  Return the number of bytes written on
   * success, and -1 on failure.
+  *
+  * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
+  * at least the length of the modulus of <b>env</b>.
   */
  int
- crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to,
+ crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to, size_t tolen,
                                const char *from, size_t fromlen)
  {
    int r;
    char digest[DIGEST_LEN];
    if (crypto_digest(digest,from,fromlen)<0)
      return -1;
-   r = crypto_pk_private_sign(env,to,digest,DIGEST_LEN);
+   r = crypto_pk_private_sign(env,to,tolen,digest,DIGEST_LEN);
    memset(digest, 0, sizeof(digest));
    return r;
  }
@@@ -990,7 -923,7 +1013,7 @@@
   */
  int
  crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env,
-                                 char *to,
+                                 char *to, size_t tolen,
                                  const char *from,
                                  size_t fromlen,
                                  int padding, int force)
@@@ -1013,8 -946,13 +1036,13 @@@
  
    if (!force && fromlen+overhead <= pkeylen) {
      /* It all fits in a single encrypt. */
-     return crypto_pk_public_encrypt(env,to,from,fromlen,padding);
+     return crypto_pk_public_encrypt(env,to,
+                                     tolen,
+                                     from,fromlen,padding);
    }
+   tor_assert(tolen >= fromlen + overhead + CIPHER_KEY_LEN);
+   tor_assert(tolen >= pkeylen);
+ 
    cipher = crypto_new_cipher_env();
    if (!cipher) return -1;
    if (crypto_cipher_generate_key(cipher)<0)
@@@ -1036,7 -974,7 +1064,7 @@@
    /* Length of symmetrically encrypted data. */
    symlen = fromlen-(pkeylen-overhead-CIPHER_KEY_LEN);
  
-   outlen = crypto_pk_public_encrypt(env,to,buf,pkeylen-overhead,padding);
+   outlen = crypto_pk_public_encrypt(env,to,tolen,buf,pkeylen-overhead,padding);
    if (outlen!=(int)pkeylen) {
      goto err;
    }
@@@ -1062,6 -1000,7 +1090,7 @@@
  int
  crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env,
                                   char *to,
+                                  size_t tolen,
                                   const char *from,
                                   size_t fromlen,
                                   int padding, int warnOnFailure)
@@@ -1075,11 -1014,12 +1104,12 @@@
    pkeylen = crypto_pk_keysize(env);
  
    if (fromlen <= pkeylen) {
-     return crypto_pk_private_decrypt(env,to,from,fromlen,padding,
+     return crypto_pk_private_decrypt(env,to,tolen,from,fromlen,padding,
                                       warnOnFailure);
    }
+ 
    buf = tor_malloc(pkeylen+1);
-   outlen = crypto_pk_private_decrypt(env,buf,from,pkeylen,padding,
+   outlen = crypto_pk_private_decrypt(env,buf,pkeylen+1,from,pkeylen,padding,
                                       warnOnFailure);
    if (outlen<0) {
      log_fn(warnOnFailure?LOG_WARN:LOG_DEBUG, LD_CRYPTO,
@@@ -1097,6 -1037,7 +1127,7 @@@
    }
    memcpy(to,buf+CIPHER_KEY_LEN,outlen-CIPHER_KEY_LEN);
    outlen -= CIPHER_KEY_LEN;
+   tor_assert(tolen - outlen >= fromlen - pkeylen);
    r = crypto_cipher_decrypt(cipher, to+outlen, from+pkeylen, fromlen-pkeylen);
    if (r<0)
      goto err;
@@@ -1268,14 -1209,19 +1299,14 @@@ crypto_cipher_generate_key(crypto_ciphe
  
  /** Set the symmetric key for the cipher in <b>env</b> to the first
   * CIPHER_KEY_LEN bytes of <b>key</b>. Does not initialize the cipher.
 - * Return 0 on success, -1 on failure.
   */
 -int
 +void
  crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key)
  {
    tor_assert(env);
    tor_assert(key);
  
 -  if (!env->key)
 -    return -1;
 -
    memcpy(env->key, key, CIPHER_KEY_LEN);
 -  return 0;
  }
  
  /** Generate an initialization vector for our AES-CTR cipher; store it
@@@ -1452,69 -1398,9 +1483,69 @@@ crypto_digest(char *digest, const char 
    return (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
  }
  
 +int
 +crypto_digest256(char *digest, const char *m, size_t len,
 +                 digest_algorithm_t algorithm)
 +{
 +  tor_assert(m);
 +  tor_assert(digest);
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  return (SHA256((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
 +}
 +
 +/** Set the digests_t in <b>ds_out</b> to contain every digest on the
 + * <b>len</b> bytes in <b>m</b> that we know how to compute.  Return 0 on
 + * success, -1 on failure. */
 +int
 +crypto_digest_all(digests_t *ds_out, const char *m, size_t len)
 +{
 +  digest_algorithm_t i;
 +  tor_assert(ds_out);
 +  memset(ds_out, 0, sizeof(*ds_out));
 +  if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0)
 +    return -1;
 +  for (i = DIGEST_SHA256; i < N_DIGEST_ALGORITHMS; ++i) {
 +    if (crypto_digest256(ds_out->d[i], m, len, i) < 0)
 +      return -1;
 +  }
 +  return 0;
 +}
 +
 +/** Return the name of an algorithm, as used in directory documents. */
 +const char *
 +crypto_digest_algorithm_get_name(digest_algorithm_t alg)
 +{
 +  switch (alg) {
 +    case DIGEST_SHA1:
 +      return "sha1";
 +    case DIGEST_SHA256:
 +      return "sha256";
 +    default:
 +      tor_fragile_assert();
 +      return "??unknown_digest??";
 +  }
 +}
 +
 +/** Given the name of a digest algorithm, return its integer value, or -1 if
 + * the name is not recognized. */
 +int
 +crypto_digest_algorithm_parse_name(const char *name)
 +{
 +  if (!strcmp(name, "sha1"))
 +    return DIGEST_SHA1;
 +  else if (!strcmp(name, "sha256"))
 +    return DIGEST_SHA256;
 +  else
 +    return -1;
 +}
 +
  /** Intermediate information about the digest of a stream of data. */
  struct crypto_digest_env_t {
 -  SHA_CTX d;
 +  union {
 +    SHA_CTX sha1;
 +    SHA256_CTX sha2;
 +  } d;
 +  digest_algorithm_t algorithm : 8;
  };
  
  /** Allocate and return a new digest object.
@@@ -1524,19 -1410,7 +1555,19 @@@ crypto_new_digest_env(void
  {
    crypto_digest_env_t *r;
    r = tor_malloc(sizeof(crypto_digest_env_t));
 -  SHA1_Init(&r->d);
 +  SHA1_Init(&r->d.sha1);
 +  r->algorithm = DIGEST_SHA1;
 +  return r;
 +}
 +
 +crypto_digest_env_t *
 +crypto_new_digest256_env(digest_algorithm_t algorithm)
 +{
 +  crypto_digest_env_t *r;
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  r = tor_malloc(sizeof(crypto_digest_env_t));
 +  SHA256_Init(&r->d.sha2);
 +  r->algorithm = algorithm;
    return r;
  }
  
@@@ -1545,8 -1419,6 +1576,8 @@@
  void
  crypto_free_digest_env(crypto_digest_env_t *digest)
  {
 +  if (!digest)
 +    return;
    memset(digest, 0, sizeof(crypto_digest_env_t));
    tor_free(digest);
  }
@@@ -1559,51 -1431,30 +1590,51 @@@ crypto_digest_add_bytes(crypto_digest_e
  {
    tor_assert(digest);
    tor_assert(data);
 -  /* Using the SHA1_*() calls directly means we don't support doing
 -   * SHA1 in hardware. But so far the delay of getting the question
 +  /* Using the SHA*_*() calls directly means we don't support doing
 +   * SHA in hardware. But so far the delay of getting the question
     * to the hardware, and hearing the answer, is likely higher than
     * just doing it ourselves. Hashes are fast.
     */
 -  SHA1_Update(&digest->d, (void*)data, len);
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      SHA1_Update(&digest->d.sha1, (void*)data, len);
 +      break;
 +    case DIGEST_SHA256:
 +      SHA256_Update(&digest->d.sha2, (void*)data, len);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
  }
  
  /** Compute the hash of the data that has been passed to the digest
   * object; write the first out_len bytes of the result to <b>out</b>.
 - * <b>out_len</b> must be \<= DIGEST_LEN.
 + * <b>out_len</b> must be \<= DIGEST256_LEN.
   */
  void
  crypto_digest_get_digest(crypto_digest_env_t *digest,
                           char *out, size_t out_len)
  {
 -  unsigned char r[DIGEST_LEN];
 -  SHA_CTX tmpctx;
 +  unsigned char r[DIGEST256_LEN];
 +  crypto_digest_env_t tmpenv;
    tor_assert(digest);
    tor_assert(out);
 -  tor_assert(out_len <= DIGEST_LEN);
 -  /* memcpy into a temporary ctx, since SHA1_Final clears the context */
 -  memcpy(&tmpctx, &digest->d, sizeof(SHA_CTX));
 -  SHA1_Final(r, &tmpctx);
 +  /* memcpy into a temporary ctx, since SHA*_Final clears the context */
 +  memcpy(&tmpenv, digest, sizeof(crypto_digest_env_t));
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      tor_assert(out_len <= DIGEST_LEN);
 +      SHA1_Final(r, &tmpenv.d.sha1);
 +      break;
 +    case DIGEST_SHA256:
 +      tor_assert(out_len <= DIGEST256_LEN);
 +      SHA256_Final(r, &tmpenv.d.sha2);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
    memcpy(out, r, out_len);
    memset(r, 0, sizeof(r));
  }
@@@ -1739,7 -1590,7 +1770,7 @@@ crypto_dh_generate_public(crypto_dh_env
      crypto_log_errors(LOG_WARN, "generating DH key");
      return -1;
    }
 -  if (tor_check_dh_key(dh->dh->pub_key)<0) {
 +  if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) {
      log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid.  I guess once-in-"
               "the-universe chances really do happen.  Trying again.");
      /* Free and clear the keys, so OpenSSL will actually try again. */
@@@ -1786,7 -1637,7 +1817,7 @@@ crypto_dh_get_public(crypto_dh_env_t *d
   * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips.
   */
  static int
 -tor_check_dh_key(BIGNUM *bn)
 +tor_check_dh_key(int severity, BIGNUM *bn)
  {
    BIGNUM *x;
    char *s;
@@@ -1797,13 -1648,13 +1828,13 @@@
      init_dh_param();
    BN_set_word(x, 1);
    if (BN_cmp(bn,x)<=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at least 2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at least 2.");
      goto err;
    }
    BN_copy(x,dh_param_p);
    BN_sub_word(x, 1);
    if (BN_cmp(bn,x)>=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at most p-2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at most p-2.");
      goto err;
    }
    BN_free(x);
@@@ -1811,7 -1662,7 +1842,7 @@@
   err:
    BN_free(x);
    s = BN_bn2hex(bn);
 -  log_warn(LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
 +  log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
    OPENSSL_free(s);
    return -1;
  }
@@@ -1829,7 -1680,7 +1860,7 @@@
   * where || is concatenation.)
   */
  ssize_t
 -crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                           const char *pubkey, size_t pubkey_len,
                           char *secret_out, size_t secret_bytes_out)
  {
@@@ -1844,9 -1695,9 +1875,9 @@@
    if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey,
                                (int)pubkey_len, NULL)))
      goto error;
 -  if (tor_check_dh_key(pubkey_bn)<0) {
 +  if (tor_check_dh_key(severity, pubkey_bn)<0) {
      /* Check for invalid public keys. */
 -    log_warn(LD_CRYPTO,"Rejected invalid g^x");
 +    log_fn(severity, LD_CRYPTO,"Rejected invalid g^x");
      goto error;
    }
    secret_tmp = tor_malloc(crypto_dh_get_bytes(dh));
@@@ -1918,8 -1769,7 +1949,8 @@@ crypto_expand_key_material(const char *
  void
  crypto_dh_free(crypto_dh_env_t *dh)
  {
 -  tor_assert(dh);
 +  if (!dh)
 +    return;
    tor_assert(dh->dh);
    DH_free(dh->dh);
    tor_free(dh);
@@@ -1944,14 -1794,6 +1975,14 @@@
      OPENSSL_VERSION_NUMBER <= 0x00907fffl) ||   \
     (OPENSSL_VERSION_NUMBER >= 0x0090803fl))
  
 +static void
 +seed_weak_rng(void)
 +{
 +  unsigned seed;
 +  crypto_rand((void*)&seed, sizeof(seed));
 +  tor_init_weak_random(seed);
 +}
 +
  /** Seed OpenSSL's random number generator with bytes from the operating
   * system.  <b>startup</b> should be true iff we have just started Tor and
   * have not yet allocated a bunch of fds.  Return 0 on success, -1 on failure.
@@@ -1959,15 -1801,14 +1990,15 @@@
  int
  crypto_seed_rng(int startup)
  {
 -  char buf[ADD_ENTROPY];
    int rand_poll_status = 0;
  
    /* local variables */
  #ifdef MS_WINDOWS
 +  unsigned char buf[ADD_ENTROPY];
    static int provider_set = 0;
    static HCRYPTPROV provider;
  #else
 +  char buf[ADD_ENTROPY];
    static const char *filenames[] = {
      "/dev/srandom", "/dev/urandom", "/dev/random", NULL
    };
@@@ -2003,7 -1844,6 +2034,7 @@@
    }
    RAND_seed(buf, sizeof(buf));
    memset(buf, 0, sizeof(buf));
 +  seed_weak_rng();
    return 0;
  #else
    for (i = 0; filenames[i]; ++i) {
@@@ -2020,7 -1860,6 +2051,7 @@@
      }
      RAND_seed(buf, (int)sizeof(buf));
      memset(buf, 0, sizeof(buf));
 +    seed_weak_rng();
      return 0;
    }
  
@@@ -2088,26 -1927,6 +2119,26 @@@ crypto_rand_uint64(uint64_t max
    }
  }
  
 +/** Return a pseudorandom double d, chosen uniformly from the range
 + * 0.0 <= d < 1.0.
 + */
 +double
 +crypto_rand_double(void)
 +{
 +  /* We just use an unsigned int here; we don't really care about getting
 +   * more than 32 bits of resolution */
 +  unsigned int uint;
 +  crypto_rand((char*)&uint, sizeof(uint));
 +#if SIZEOF_INT == 4
 +#define UINT_MAX_AS_DOUBLE 4294967296.0
 +#elif SIZEOF_INT == 8
 +#define UINT_MAX_AS_DOUBLE 1.8446744073709552e+19
 +#else
 +#error SIZEOF_INT is neither 4 nor 8
 +#endif
 +  return ((double)uint) / UINT_MAX_AS_DOUBLE;
 +}
 +
  /** Generate and return a new random hostname starting with <b>prefix</b>,
   * ending with <b>suffix</b>, and containing no less than
   * <b>min_rand_len</b> and no more than <b>max_rand_len</b> random base32
@@@ -2368,54 -2187,15 +2399,54 @@@ digest_from_base64(char *digest, const 
  #endif
  }
  
 +/** Base-64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the
 + * trailing = and newline characters, and store the nul-terminated result in
 + * the first BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>.  */
 +int
 +digest256_to_base64(char *d64, const char *digest)
 +{
 +  char buf[256];
 +  base64_encode(buf, sizeof(buf), digest, DIGEST256_LEN);
 +  buf[BASE64_DIGEST256_LEN] = '\0';
 +  memcpy(d64, buf, BASE64_DIGEST256_LEN+1);
 +  return 0;
 +}
 +
 +/** Given a base-64 encoded, nul-terminated digest in <b>d64</b> (without
 + * trailing newline or = characters), decode it and store the result in the
 + * first DIGEST256_LEN bytes at <b>digest</b>. */
 +int
 +digest256_from_base64(char *digest, const char *d64)
 +{
 +#ifdef USE_OPENSSL_BASE64
 +  char buf_in[BASE64_DIGEST256_LEN+3];
 +  char buf[256];
 +  if (strlen(d64) != BASE64_DIGEST256_LEN)
 +    return -1;
 +  memcpy(buf_in, d64, BASE64_DIGEST256_LEN);
 +  memcpy(buf_in+BASE64_DIGEST256_LEN, "=\n\0", 3);
 +  if (base64_decode(buf, sizeof(buf), buf_in, strlen(buf_in)) != DIGEST256_LEN)
 +    return -1;
 +  memcpy(digest, buf, DIGEST256_LEN);
 +  return 0;
 +#else
 +  if (base64_decode(digest, DIGEST256_LEN, d64, strlen(d64)) == DIGEST256_LEN)
 +    return 0;
 +  else
 +    return -1;
 +#endif
 +}
 +
  /** Implements base32 encoding as in rfc3548.  Limitation: Requires
   * that srclen*8 is a multiple of 5.
   */
  void
  base32_encode(char *dest, size_t destlen, const char *src, size_t srclen)
  {
 -  unsigned int i, bit, v, u;
 -  size_t nbits = srclen * 8;
 +  unsigned int i, v, u;
 +  size_t nbits = srclen * 8, bit;
  
 +  tor_assert(srclen < SIZE_T_CEILING/8);
    tor_assert((nbits%5) == 0); /* We need an even multiple of 5 bits. */
    tor_assert((nbits/5)+1 <= destlen); /* We need enough space. */
    tor_assert(destlen < SIZE_T_CEILING);
@@@ -2439,12 -2219,11 +2470,12 @@@ base32_decode(char *dest, size_t destle
  {
    /* XXXX we might want to rewrite this along the lines of base64_decode, if
     * it ever shows up in the profile. */
 -  unsigned int i, j, bit;
 -  size_t nbits;
 +  unsigned int i;
 +  size_t nbits, j, bit;
    char *tmp;
    nbits = srclen * 5;
  
 +  tor_assert(srclen < SIZE_T_CEILING / 5);
    tor_assert((nbits%8) == 0); /* We need an even multiple of 8 bits. */
    tor_assert((nbits/8) <= destlen); /* We need enough space. */
    tor_assert(destlen < SIZE_T_CEILING);
diff --combined src/common/crypto.h
index 0801728,9cfb414..29ba36c
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@@ -18,9 -18,6 +18,9 @@@
  
  /** Length of the output of our message digest. */
  #define DIGEST_LEN 20
 +/** Length of the output of our second (improved) message digests.  (For now
 + * this is just sha256, but any it can be any other 256-byte digest). */
 +#define DIGEST256_LEN 32
  /** Length of our symmetric cipher's keys. */
  #define CIPHER_KEY_LEN 16
  /** Length of our symmetric cipher's IV. */
@@@ -30,12 -27,9 +30,12 @@@
  /** Length of our DH keys. */
  #define DH_BYTES (1024/8)
  
 -/** Length of a message digest when encoded in base64 with trailing = signs
 - * removed. */
 +/** Length of a sha1 message digest when encoded in base64 with trailing =
 + * signs removed. */
  #define BASE64_DIGEST_LEN 27
 +/** Length of a sha256 message digest when encoded in base64 with trailing =
 + * signs removed. */
 +#define BASE64_DIGEST256_LEN 43
  
  /** Constants used to indicate no padding for public-key encryption */
  #define PK_NO_PADDING         60000
@@@ -54,26 -48,6 +54,26 @@@
  #define FINGERPRINT_LEN 49
  /** Length of hex encoding of SHA1 digest, not including final NUL. */
  #define HEX_DIGEST_LEN 40
 +/** Length of hex encoding of SHA256 digest, not including final NUL. */
 +#define HEX_DIGEST256_LEN 64
 +
 +typedef enum {
 +  DIGEST_SHA1 = 0,
 +  DIGEST_SHA256 = 1,
 +} digest_algorithm_t;
 +#define  N_DIGEST_ALGORITHMS (DIGEST_SHA256+1)
 +
 +/** A set of all the digests we know how to compute, taken on a single
 + * string.  Any digests that are shorter than 256 bits are right-padded
 + * with 0 bits.
 + *
 + * Note that this representation wastes 12 bytes for the SHA1 case, so
 + * don't use it for anything where we need to allocate a whole bunch at
 + * once.
 + **/
 +typedef struct {
 +  char d[N_DIGEST_ALGORITHMS][DIGEST256_LEN];
 +} digests_t;
  
  typedef struct crypto_pk_env_t crypto_pk_env_t;
  typedef struct crypto_cipher_env_t crypto_cipher_env_t;
@@@ -81,9 -55,7 +81,9 @@@ typedef struct crypto_digest_env_t cryp
  typedef struct crypto_dh_env_t crypto_dh_env_t;
  
  /* global state */
 -int crypto_global_init(int hardwareAccel);
 +int crypto_global_init(int hardwareAccel,
 +                       const char *accelName,
 +                       const char *accelPath);
  void crypto_thread_cleanup(void);
  int crypto_global_cleanup(void);
  
@@@ -99,9 -71,7 +99,9 @@@ crypto_cipher_env_t *crypto_new_cipher_
  void crypto_free_cipher_env(crypto_cipher_env_t *env);
  
  /* public key crypto */
 -int crypto_pk_generate_key(crypto_pk_env_t *env);
 +int crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits);
 +#define crypto_pk_generate_key(env)                     \
 +  crypto_pk_generate_key_with_bits((env), (PK_BYTES*8))
  
  int crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env,
                                               const char *keyfile);
@@@ -123,23 -93,25 +123,25 @@@ crypto_pk_env_t *crypto_pk_dup_key(cryp
  crypto_pk_env_t *crypto_pk_copy_full(crypto_pk_env_t *orig);
  int crypto_pk_key_is_private(const crypto_pk_env_t *key);
  
- int crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to,
+ int crypto_pk_public_encrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                               const char *from, size_t fromlen, int padding);
- int crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to,
+ int crypto_pk_private_decrypt(crypto_pk_env_t *env, char *to, size_t tolen,
                                const char *from, size_t fromlen,
                                int padding, int warnOnFailure);
- int crypto_pk_public_checksig(crypto_pk_env_t *env, char *to,
+ int crypto_pk_public_checksig(crypto_pk_env_t *env, char *to, size_t tolen,
                                const char *from, size_t fromlen);
  int crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data,
                                 size_t datalen, const char *sig, size_t siglen);
- int crypto_pk_private_sign(crypto_pk_env_t *env, char *to,
+ int crypto_pk_private_sign(crypto_pk_env_t *env, char *to, size_t tolen,
                             const char *from, size_t fromlen);
- int crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to,
+ int crypto_pk_private_sign_digest(crypto_pk_env_t *env, char *to, size_t tolen,
                                    const char *from, size_t fromlen);
  int crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env, char *to,
+                                     size_t tolen,
                                      const char *from, size_t fromlen,
                                      int padding, int force);
  int crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env, char *to,
+                                      size_t tolen,
                                       const char *from, size_t fromlen,
                                       int padding, int warnOnFailure);
  
@@@ -151,7 -123,7 +153,7 @@@ int crypto_pk_check_fingerprint_syntax(
  
  /* symmetric crypto */
  int crypto_cipher_generate_key(crypto_cipher_env_t *env);
 -int crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
 +void crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
  void crypto_cipher_generate_iv(char *iv_out);
  int crypto_cipher_set_iv(crypto_cipher_env_t *env, const char *iv);
  const char *crypto_cipher_get_key(crypto_cipher_env_t *env);
@@@ -171,15 -143,9 +173,15 @@@ int crypto_cipher_decrypt_with_iv(crypt
                                    char *to, size_t tolen,
                                    const char *from, size_t fromlen);
  
 -/* SHA-1 */
 +/* SHA-1 and other digests. */
  int crypto_digest(char *digest, const char *m, size_t len);
 +int crypto_digest256(char *digest, const char *m, size_t len,
 +                     digest_algorithm_t algorithm);
 +int crypto_digest_all(digests_t *ds_out, const char *m, size_t len);
 +const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg);
 +int crypto_digest_algorithm_parse_name(const char *name);
  crypto_digest_env_t *crypto_new_digest_env(void);
 +crypto_digest_env_t *crypto_new_digest256_env(digest_algorithm_t algorithm);
  void crypto_free_digest_env(crypto_digest_env_t *digest);
  void crypto_digest_add_bytes(crypto_digest_env_t *digest, const char *data,
                               size_t len);
@@@ -198,7 -164,7 +200,7 @@@ int crypto_dh_get_bytes(crypto_dh_env_
  int crypto_dh_generate_public(crypto_dh_env_t *dh);
  int crypto_dh_get_public(crypto_dh_env_t *dh, char *pubkey_out,
                           size_t pubkey_out_len);
 -ssize_t crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +ssize_t crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                               const char *pubkey, size_t pubkey_len,
                               char *secret_out, size_t secret_out_len);
  void crypto_dh_free(crypto_dh_env_t *dh);
@@@ -210,7 -176,6 +212,7 @@@ int crypto_seed_rng(int startup)
  int crypto_rand(char *to, size_t n);
  int crypto_rand_int(unsigned int max);
  uint64_t crypto_rand_uint64(uint64_t max);
 +double crypto_rand_double(void);
  
  char *crypto_random_hostname(int min_rand_len, int max_rand_len,
                               const char *prefix, const char *suffix);
@@@ -228,8 -193,6 +230,8 @@@ int base32_decode(char *dest, size_t de
  
  int digest_to_base64(char *d64, const char *digest);
  int digest_from_base64(char *digest, const char *d64);
 +int digest256_to_base64(char *d64, const char *digest);
 +int digest256_from_base64(char *digest, const char *d64);
  
  /** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the
   * 9th describes how much iteration to do. */
diff --combined src/or/config.c
index a27fd22,f8cfd29..b124db1
--- a/src/or/config.c
+++ b/src/or/config.c
@@@ -12,28 -12,6 +12,28 @@@
  #define CONFIG_PRIVATE
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "cpuworker.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "dns.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  #ifdef MS_WINDOWS
  #include <shlobj.h>
  #endif
@@@ -83,12 -61,11 +83,12 @@@ static config_abbrev_t _option_abbrevs[
    PLURAL(LongLivedPort),
    PLURAL(HiddenServiceNode),
    PLURAL(HiddenServiceExcludeNode),
 -  PLURAL(NumCpu),
 +  PLURAL(NumCPU),
    PLURAL(RendNode),
    PLURAL(RendExcludeNode),
    PLURAL(StrictEntryNode),
    PLURAL(StrictExitNode),
 +  PLURAL(StrictNode),
    { "l", "Log", 1, 0},
    { "AllowUnverifiedNodes", "AllowInvalidNodes", 0, 0},
    { "AutomapHostSuffixes", "AutomapHostsSuffixes", 0, 0},
@@@ -106,12 -83,10 +106,12 @@@
    { "NumEntryNodes", "NumEntryGuards", 0, 0},
    { "ResolvConf", "ServerDNSResolvConfFile", 0, 1},
    { "SearchDomains", "ServerDNSSearchDomains", 0, 1},
 -  { "ServerDNSAllowBrokenResolvConf", "ServerDNSAllowBrokenConfig", 0, 0 },
 +  { "ServerDNSAllowBrokenResolvConf", "ServerDNSAllowBrokenConfig", 0, 0},
    { "PreferTunnelledDirConns", "PreferTunneledDirConns", 0, 0},
    { "BridgeAuthoritativeDirectory", "BridgeAuthoritativeDir", 0, 0},
    { "HashedControlPassword", "__HashedControlSessionPassword", 1, 0},
 +  { "StrictEntryNodes", "StrictNodes", 0, 1},
 +  { "StrictExitNodes", "StrictNodes", 0, 1},
    { NULL, NULL, 0, 0},
  };
  
@@@ -159,7 -134,6 +159,7 @@@ static config_var_t _option_vars[] = 
    V(AccountingMax,               MEMUNIT,  "0 bytes"),
    V(AccountingStart,             STRING,   NULL),
    V(Address,                     STRING,   NULL),
 +  V(AllowDotExit,                BOOL,     "0"),
    V(AllowInvalidNodes,           CSV,      "middle,rendezvous"),
    V(AllowNonRFC953Hostnames,     BOOL,     "0"),
    V(AllowSingleHopCircuits,      BOOL,     "0"),
@@@ -188,15 -162,10 +188,15 @@@
    V(BridgePassword,              STRING,   NULL),
    V(BridgeRecordUsageByCountry,  BOOL,     "1"),
    V(BridgeRelay,                 BOOL,     "0"),
 -  V(CircuitBuildTimeout,         INTERVAL, "1 minute"),
 +  V(CellStatistics,              BOOL,     "0"),
 +  V(LearnCircuitBuildTimeout,    BOOL,     "1"),
 +  V(CircuitBuildTimeout,         INTERVAL, "0"),
    V(CircuitIdleTimeout,          INTERVAL, "1 hour"),
 +  V(CircuitStreamTimeout,        INTERVAL, "0"),
 +  V(CircuitPriorityHalflife,     DOUBLE,  "-100.0"), /*negative:'Use default'*/
    V(ClientDNSRejectInternalAddresses, BOOL,"1"),
    V(ClientOnly,                  BOOL,     "0"),
 +  V(ConsensusParams,             STRING,   NULL),
    V(ConnLimit,                   UINT,     "1000"),
    V(ConstrainedSockets,          BOOL,     "0"),
    V(ConstrainedSockSize,         MEMUNIT,  "8192"),
@@@ -217,19 -186,18 +217,19 @@@
    V(DirPort,                     UINT,     "0"),
    V(DirPortFrontPage,            FILENAME, NULL),
    OBSOLETE("DirPostPeriod"),
 -#ifdef ENABLE_GEOIP_STATS
 -  V(DirRecordUsageByCountry,     BOOL,     "0"),
 -  V(DirRecordUsageGranularity,   UINT,     "4"),
 -  V(DirRecordUsageRetainIPs,     INTERVAL, "14 days"),
 -  V(DirRecordUsageSaveInterval,  INTERVAL, "6 hours"),
 -#endif
 +  OBSOLETE("DirRecordUsageByCountry"),
 +  OBSOLETE("DirRecordUsageGranularity"),
 +  OBSOLETE("DirRecordUsageRetainIPs"),
 +  OBSOLETE("DirRecordUsageSaveInterval"),
 +  V(DirReqStatistics,            BOOL,     "0"),
    VAR("DirServer",               LINELIST, DirServers, NULL),
 +  V(DisableAllSwap,              BOOL,     "0"),
    V(DNSPort,                     UINT,     "0"),
    V(DNSListenAddress,            LINELIST, NULL),
    V(DownloadExtraInfo,           BOOL,     "0"),
    V(EnforceDistinctSubnets,      BOOL,     "1"),
    V(EntryNodes,                  ROUTERSET,   NULL),
 +  V(EntryStatistics,             BOOL,     "0"),
    V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
    V(ExcludeNodes,                ROUTERSET, NULL),
    V(ExcludeExitNodes,            ROUTERSET, NULL),
@@@ -237,20 -205,12 +237,20 @@@
    V(ExitNodes,                   ROUTERSET, NULL),
    V(ExitPolicy,                  LINELIST, NULL),
    V(ExitPolicyRejectPrivate,     BOOL,     "1"),
 +  V(ExitPortStatistics,          BOOL,     "0"),
 +  V(ExtraInfoStatistics,         BOOL,     "0"),
 +
 +#if defined (WINCE)
 +  V(FallbackNetworkstatusFile,   FILENAME, "fallback-consensus"),
 +#else
    V(FallbackNetworkstatusFile,   FILENAME,
      SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "fallback-consensus"),
 +#endif
    V(FascistFirewall,             BOOL,     "0"),
    V(FirewallPorts,               CSV,      ""),
    V(FastFirstHopPK,              BOOL,     "1"),
    V(FetchDirInfoEarly,           BOOL,     "0"),
 +  V(FetchDirInfoExtraEarly,      BOOL,     "0"),
    V(FetchServerDescriptors,      BOOL,     "1"),
    V(FetchHidServDescriptors,     BOOL,     "1"),
    V(FetchUselessDescriptors,     BOOL,     "0"),
@@@ -262,8 -222,6 +262,8 @@@
  #endif
    OBSOLETE("Group"),
    V(HardwareAccel,               BOOL,     "0"),
 +  V(AccelName,                   STRING,   NULL),
 +  V(AccelDir,                    FILENAME, NULL),
    V(HashedControlPassword,       LINELIST, NULL),
    V(HidServDirectoryV2,          BOOL,     "1"),
    VAR("HiddenServiceDir",    LINELIST_S, RendConfigLines,    NULL),
@@@ -275,15 -233,11 +275,15 @@@
    VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL),
    V(HidServAuth,                 LINELIST, NULL),
    V(HSAuthoritativeDir,          BOOL,     "0"),
 -  V(HSAuthorityRecordStats,      BOOL,     "0"),
 -  V(HttpProxy,                   STRING,   NULL),
 -  V(HttpProxyAuthenticator,      STRING,   NULL),
 -  V(HttpsProxy,                  STRING,   NULL),
 -  V(HttpsProxyAuthenticator,     STRING,   NULL),
 +  OBSOLETE("HSAuthorityRecordStats"),
 +  V(HTTPProxy,                   STRING,   NULL),
 +  V(HTTPProxyAuthenticator,      STRING,   NULL),
 +  V(HTTPSProxy,                  STRING,   NULL),
 +  V(HTTPSProxyAuthenticator,     STRING,   NULL),
 +  V(Socks4Proxy,                 STRING,   NULL),
 +  V(Socks5Proxy,                 STRING,   NULL),
 +  V(Socks5ProxyUsername,         STRING,   NULL),
 +  V(Socks5ProxyPassword,         STRING,   NULL),
    OBSOLETE("IgnoreVersion"),
    V(KeepalivePeriod,             INTERVAL, "5 minutes"),
    VAR("Log",                     LINELIST, Logs,             NULL),
@@@ -300,20 -254,17 +300,20 @@@
    V(MyFamily,                    STRING,   NULL),
    V(NewCircuitPeriod,            INTERVAL, "30 seconds"),
    VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"),
 -  V(NatdListenAddress,           LINELIST, NULL),
 -  V(NatdPort,                    UINT,     "0"),
 +  V(NATDListenAddress,           LINELIST, NULL),
 +  V(NATDPort,                    UINT,     "0"),
    V(Nickname,                    STRING,   NULL),
 -  V(NoPublish,                   BOOL,     "0"),
 +  V(WarnUnsafeSocks,              BOOL,     "1"),
 +  OBSOLETE("NoPublish"),
    VAR("NodeFamily",              LINELIST, NodeFamilies,         NULL),
 -  V(NumCpus,                     UINT,     "1"),
 +  V(NumCPUs,                     UINT,     "1"),
    V(NumEntryGuards,              UINT,     "3"),
    V(ORListenAddress,             LINELIST, NULL),
    V(ORPort,                      UINT,     "0"),
    V(OutboundBindAddress,         STRING,   NULL),
    OBSOLETE("PathlenCoinWeight"),
 +  V(PerConnBWBurst,              MEMUNIT,  "0"),
 +  V(PerConnBWRate,               MEMUNIT,  "0"),
    V(PidFile,                     STRING,   NULL),
    V(TestingTorNetwork,           BOOL,     "0"),
    V(PreferTunneledDirConns,      BOOL,     "1"),
@@@ -327,7 -278,6 +327,7 @@@
    V(RecommendedClientVersions,   LINELIST, NULL),
    V(RecommendedServerVersions,   LINELIST, NULL),
    OBSOLETE("RedirectExit"),
 +  V(RefuseUnknownExits,          STRING,   "auto"),
    V(RejectPlaintextPorts,        CSV,      ""),
    V(RelayBandwidthBurst,         MEMUNIT,  "0"),
    V(RelayBandwidthRate,          MEMUNIT,  "0"),
@@@ -337,9 -287,8 +337,9 @@@
    V(RephistTrackTime,            INTERVAL, "24 hours"),
    OBSOLETE("RouterFile"),
    V(RunAsDaemon,                 BOOL,     "0"),
 -  V(RunTesting,                  BOOL,     "0"),
 -  V(SafeLogging,                 BOOL,     "1"),
 +//  V(RunTesting,                  BOOL,     "0"),
 +  OBSOLETE("RunTesting"), // currently unused
 +  V(SafeLogging,                 STRING,   "1"),
    V(SafeSocks,                   BOOL,     "0"),
    V(ServerDNSAllowBrokenConfig,  BOOL,     "1"),
    V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"),
@@@ -355,7 -304,8 +355,7 @@@
    V(SocksPort,                   UINT,     "9050"),
    V(SocksTimeout,                INTERVAL, "2 minutes"),
    OBSOLETE("StatusFetchPeriod"),
 -  V(StrictEntryNodes,            BOOL,     "0"),
 -  V(StrictExitNodes,             BOOL,     "0"),
 +  V(StrictNodes,                 BOOL,     "0"),
    OBSOLETE("SysLog"),
    V(TestSocks,                   BOOL,     "0"),
    OBSOLETE("TestVia"),
@@@ -380,7 -330,6 +380,7 @@@
    V(V3AuthDistDelay,             INTERVAL, "5 minutes"),
    V(V3AuthNIntervalsValid,       UINT,     "3"),
    V(V3AuthUseLegacyKey,          BOOL,     "0"),
 +  V(V3BandwidthsFile,            FILENAME, NULL),
    VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
    V(VirtualAddrNetwork,          STRING,   "127.192.0.0/10"),
    V(WarnPlaintextPorts,          CSV,      "23,109,110,143"),
@@@ -391,7 -340,6 +391,7 @@@
    VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword,
        NULL),
    V(MinUptimeHidServDirectoryV2, INTERVAL, "24 hours"),
 +
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  
@@@ -414,7 -362,6 +414,7 @@@ static config_var_t testing_tor_network
    V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"),
    V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"),
    V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"),
 +  V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"),
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  #undef VAR
@@@ -430,9 -377,6 +430,9 @@@ static config_var_t _state_vars[] = 
    V(AccountingExpectedUsage,          MEMUNIT,  NULL),
    V(AccountingIntervalStart,          ISOTIME,  NULL),
    V(AccountingSecondsActive,          INTERVAL, NULL),
 +  V(AccountingSecondsToReachSoftLimit,INTERVAL, NULL),
 +  V(AccountingSoftLimitHitAt,         ISOTIME,  NULL),
 +  V(AccountingBytesAtSoftLimit,       MEMUNIT,  NULL),
  
    VAR("EntryGuard",              LINELIST_S,  EntryGuards,             NULL),
    VAR("EntryGuardDownSince",     LINELIST_S,  EntryGuards,             NULL),
@@@ -446,23 -390,12 +446,23 @@@
    V(BWHistoryWriteEnds,               ISOTIME,  NULL),
    V(BWHistoryWriteInterval,           UINT,     "900"),
    V(BWHistoryWriteValues,             CSV,      ""),
 +  V(BWHistoryDirReadEnds,             ISOTIME,  NULL),
 +  V(BWHistoryDirReadInterval,         UINT,     "900"),
 +  V(BWHistoryDirReadValues,           CSV,      ""),
 +  V(BWHistoryDirWriteEnds,            ISOTIME,  NULL),
 +  V(BWHistoryDirWriteInterval,        UINT,     "900"),
 +  V(BWHistoryDirWriteValues,          CSV,      ""),
  
    V(TorVersion,                       STRING,   NULL),
  
    V(LastRotatedOnionKey,              ISOTIME,  NULL),
    V(LastWritten,                      ISOTIME,  NULL),
  
 +  V(TotalBuildTimes,                  UINT,     NULL),
 +  V(CircuitBuildAbandonedCount,       UINT,     "0"),
 +  VAR("CircuitBuildTimeBin",          LINELIST_S, BuildtimeHistogram, NULL),
 +  VAR("BuildtimeHistogram",           LINELIST_V, BuildtimeHistogram, NULL),
 +
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  
@@@ -477,6 -410,213 +477,6 @@@ typedef struct config_var_description_
    const char *description;
  } config_var_description_t;
  
 -/** Descriptions of the configuration options, to be displayed by online
 - * option browsers */
 -/* XXXX022 did anybody want this? at all? If not, kill it.*/
 -static config_var_description_t options_description[] = {
 -  /* ==== general options */
 -  { "AvoidDiskWrites", "If non-zero, try to write to disk less frequently than"
 -    " we would otherwise." },
 -  { "BandwidthRate", "A token bucket limits the average incoming bandwidth on "
 -    "this node to the specified number of bytes per second." },
 -  { "BandwidthBurst", "Limit the maximum token buffer size (also known as "
 -    "burst) to the given number of bytes." },
 -  { "ConnLimit", "Minimum number of simultaneous sockets we must have." },
 -  { "ConstrainedSockets", "Shrink tx and rx buffers for sockets to avoid "
 -    "system limits on vservers and related environments.  See man page for "
 -    "more information regarding this option." },
 -  { "ConstrainedSockSize", "Limit socket buffers to this size when "
 -    "ConstrainedSockets is enabled." },
 -  /*  ControlListenAddress */
 -  { "ControlPort", "If set, Tor will accept connections from the same machine "
 -    "(localhost only) on this port, and allow those connections to control "
 -    "the Tor process using the Tor Control Protocol (described in "
 -    "control-spec.txt).", },
 -  { "CookieAuthentication", "If this option is set to 1, don't allow any "
 -    "connections to the control port except when the connecting process "
 -    "can read a file that Tor creates in its data directory." },
 -  { "DataDirectory", "Store working data, state, keys, and caches here." },
 -  { "DirServer", "Tor only trusts directories signed with one of these "
 -    "servers' keys.  Used to override the standard list of directory "
 -    "authorities." },
 -  /* { "FastFirstHopPK", "" }, */
 -  /* FetchServerDescriptors, FetchHidServDescriptors,
 -   * FetchUselessDescriptors */
 -  { "HardwareAccel", "If set, Tor tries to use hardware crypto accelerators "
 -    "when it can." },
 -  /* HashedControlPassword */
 -  { "HTTPProxy", "Force Tor to make all HTTP directory requests through this "
 -    "host:port (or host:80 if port is not set)." },
 -  { "HTTPProxyAuthenticator", "A username:password pair to be used with "
 -    "HTTPProxy." },
 -  { "HTTPSProxy", "Force Tor to make all TLS (SSL) connections through this "
 -    "host:port (or host:80 if port is not set)." },
 -  { "HTTPSProxyAuthenticator", "A username:password pair to be used with "
 -    "HTTPSProxy." },
 -  { "KeepalivePeriod", "Send a padding cell every N seconds to keep firewalls "
 -    "from closing our connections while Tor is not in use." },
 -  { "Log", "Where to send logging messages.  Format is "
 -    "minSeverity[-maxSeverity] (stderr|stdout|syslog|file FILENAME)." },
 -  { "OutboundBindAddress", "Make all outbound connections originate from the "
 -    "provided IP address (only useful for multiple network interfaces)." },
 -  { "PIDFile", "On startup, write our PID to this file. On clean shutdown, "
 -    "remove the file." },
 -  { "PreferTunneledDirConns", "If non-zero, avoid directory servers that "
 -    "don't support tunneled connections." },
 -  /* PreferTunneledDirConns */
 -  /* ProtocolWarnings */
 -  /* RephistTrackTime */
 -  { "RunAsDaemon", "If set, Tor forks and daemonizes to the background when "
 -    "started.  Unix only." },
 -  { "SafeLogging", "If set to 0, Tor logs potentially sensitive strings "
 -    "rather than replacing them with the string [scrubbed]." },
 -  { "TunnelDirConns", "If non-zero, when a directory server we contact "
 -    "supports it, we will build a one-hop circuit and make an encrypted "
 -    "connection via its ORPort." },
 -  { "User", "On startup, setuid to this user." },
 -
 -  /* ==== client options */
 -  { "AllowInvalidNodes", "Where on our circuits should Tor allow servers "
 -    "that the directory authorities haven't called \"valid\"?" },
 -  { "AllowNonRFC953Hostnames", "If set to 1, we don't automatically reject "
 -    "hostnames for having invalid characters." },
 -  /*  CircuitBuildTimeout, CircuitIdleTimeout */
 -  { "ClientOnly", "If set to 1, Tor will under no circumstances run as a "
 -    "server, even if ORPort is enabled." },
 -  { "EntryNodes", "A list of preferred entry nodes to use for the first hop "
 -    "in circuits, when possible." },
 -  /* { "EnforceDistinctSubnets" , "" }, */
 -  { "ExitNodes", "A list of preferred nodes to use for the last hop in "
 -    "circuits, when possible." },
 -  { "ExcludeNodes", "A list of nodes never to use when building a circuit." },
 -  { "FascistFirewall", "If set, Tor will only create outgoing connections to "
 -    "servers running on the ports listed in FirewallPorts." },
 -  { "FirewallPorts", "A list of ports that we can connect to.  Only used "
 -    "when FascistFirewall is set." },
 -  { "LongLivedPorts", "A list of ports for services that tend to require "
 -    "high-uptime connections." },
 -  { "MapAddress", "Force Tor to treat all requests for one address as if "
 -    "they were for another." },
 -  { "NewCircuitPeriod", "Force Tor to consider whether to build a new circuit "
 -    "every NUM seconds." },
 -  { "MaxCircuitDirtiness", "Do not attach new streams to a circuit that has "
 -    "been used more than this many seconds ago." },
 -  /* NatdPort, NatdListenAddress */
 -  { "NodeFamily", "A list of servers that constitute a 'family' and should "
 -    "never be used in the same circuit." },
 -  { "NumEntryGuards", "How many entry guards should we keep at a time?" },
 -  /* PathlenCoinWeight */
 -  { "ReachableAddresses", "Addresses we can connect to, as IP/bits:port-port. "
 -    "By default, we assume all addresses are reachable." },
 -  /* reachablediraddresses, reachableoraddresses. */
 -  /* SafeSOCKS */
 -  { "SOCKSPort", "The port where we listen for SOCKS connections from "
 -    "applications." },
 -  { "SOCKSListenAddress", "Bind to this address to listen to connections from "
 -    "SOCKS-speaking applications." },
 -  { "SOCKSPolicy", "Set an entry policy to limit which addresses can connect "
 -    "to the SOCKSPort." },
 -  /* SocksTimeout */
 -  { "StrictExitNodes", "If set, Tor will fail to operate when none of the "
 -    "configured ExitNodes can be used." },
 -  { "StrictEntryNodes", "If set, Tor will fail to operate when none of the "
 -    "configured EntryNodes can be used." },
 -  /* TestSocks */
 -  { "TrackHostsExit", "Hosts and domains which should, if possible, be "
 -    "accessed from the same exit node each time we connect to them." },
 -  { "TrackHostsExitExpire", "Time after which we forget which exit we were "
 -    "using to connect to hosts in TrackHostsExit." },
 -  /* "TransPort", "TransListenAddress */
 -  { "UseEntryGuards", "Set to 0 if we want to pick from the whole set of "
 -    "servers for the first position in each circuit, rather than picking a "
 -    "set of 'Guards' to prevent profiling attacks." },
 -
 -  /* === server options */
 -  { "Address", "The advertised (external) address we should use." },
 -  /* Accounting* options. */
 -  /* AssumeReachable */
 -  { "ContactInfo", "Administrative contact information to advertise for this "
 -    "server." },
 -  { "ExitPolicy", "Address/port ranges for which to accept or reject outgoing "
 -    "connections on behalf of Tor users." },
 -  /*  { "ExitPolicyRejectPrivate, "" }, */
 -  { "MaxAdvertisedBandwidth", "If set, we will not advertise more than this "
 -    "amount of bandwidth for our bandwidth rate, regardless of how much "
 -    "bandwidth we actually detect." },
 -  { "MaxOnionsPending", "Reject new attempts to extend circuits when we "
 -    "already have this many pending." },
 -  { "MyFamily", "Declare a list of other servers as belonging to the same "
 -    "family as this one, so that clients will not use two from the same "
 -    "family in the same circuit." },
 -  { "Nickname", "Set the server nickname." },
 -  { "NoPublish", "{DEPRECATED}" },
 -  { "NumCPUs", "How many processes to use at once for public-key crypto." },
 -  { "ORPort", "Advertise this port to listen for connections from Tor clients "
 -    "and servers." },
 -  { "ORListenAddress", "Bind to this address to listen for connections from "
 -    "clients and servers, instead of the default 0.0.0.0:ORPort." },
 -  { "PublishServerDescriptor", "Set to 0 to keep the server from "
 -    "uploading info to the directory authorities." },
 -  /* ServerDNS: DetectHijacking, ResolvConfFile, SearchDomains */
 -  { "ShutdownWaitLength", "Wait this long for clients to finish when "
 -    "shutting down because of a SIGINT." },
 -
 -  /* === directory cache options */
 -  { "DirPort", "Serve directory information from this port, and act as a "
 -    "directory cache." },
 -  { "DirPortFrontPage", "Serve a static html disclaimer on DirPort." },
 -  { "DirListenAddress", "Bind to this address to listen for connections from "
 -    "clients and servers, instead of the default 0.0.0.0:DirPort." },
 -  { "DirPolicy", "Set a policy to limit who can connect to the directory "
 -    "port." },
 -
 -  /*  Authority options: AuthDirBadExit, AuthDirInvalid, AuthDirReject,
 -   * AuthDirRejectUnlisted, AuthDirListBadExits, AuthoritativeDirectory,
 -   * DirAllowPrivateAddresses, HSAuthoritativeDir,
 -   * NamingAuthoritativeDirectory, RecommendedVersions,
 -   * RecommendedClientVersions, RecommendedServerVersions, RendPostPeriod,
 -   * RunTesting, V1AuthoritativeDirectory, VersioningAuthoritativeDirectory, */
 -
 -  /* Hidden service options: HiddenService: dir,excludenodes, nodes,
 -   * options, port.  PublishHidServDescriptor */
 -
 -  /* Nonpersistent options: __LeaveStreamsUnattached, __AllDirActionsPrivate */
 -  { NULL, NULL },
 -};
 -
 -/** Online description of state variables. */
 -static config_var_description_t state_description[] = {
 -  { "AccountingBytesReadInInterval",
 -    "How many bytes have we read in this accounting period?" },
 -  { "AccountingBytesWrittenInInterval",
 -    "How many bytes have we written in this accounting period?" },
 -  { "AccountingExpectedUsage",
 -    "How many bytes did we expect to use per minute? (0 for no estimate.)" },
 -  { "AccountingIntervalStart", "When did this accounting period begin?" },
 -  { "AccountingSecondsActive", "How long have we been awake in this period?" },
 -
 -  { "BWHistoryReadEnds", "When does the last-recorded read-interval end?" },
 -  { "BWHistoryReadInterval", "How long is each read-interval (in seconds)?" },
 -  { "BWHistoryReadValues", "Number of bytes read in each interval." },
 -  { "BWHistoryWriteEnds", "When does the last-recorded write-interval end?" },
 -  { "BWHistoryWriteInterval", "How long is each write-interval (in seconds)?"},
 -  { "BWHistoryWriteValues", "Number of bytes written in each interval." },
 -
 -  { "EntryGuard", "One of the nodes we have chosen as a fixed entry" },
 -  { "EntryGuardDownSince",
 -    "The last entry guard has been unreachable since this time." },
 -  { "EntryGuardUnlistedSince",
 -    "The last entry guard has been unusable since this time." },
 -
 -  { "LastRotatedOnionKey",
 -    "The last time at which we changed the medium-term private key used for "
 -    "building circuits." },
 -  { "LastWritten", "When was this state file last regenerated?" },
 -
 -  { "TorVersion", "Which version of Tor generated this state file?" },
 -  { NULL, NULL },
 -};
 -
  /** Type of a callback to validate whether a given configuration is
   * well-formed and consistent. See options_trial_assign() for documentation
   * of arguments. */
@@@ -495,6 -635,8 +495,6 @@@ typedef struct 
    config_var_t *vars; /**< List of variables we recognize, their default
                         * values, and where we stick them in the structure. */
    validate_fn_t validate_fn; /**< Function to validate config. */
 -  /** Documentation for configuration variables. */
 -  config_var_description_t *descriptions;
    /** If present, extra is a LINELIST variable for unrecognized
     * lines.  Otherwise, unrecognized lines are an error. */
    config_var_t *extra;
@@@ -558,6 -700,20 +558,6 @@@ static uint64_t config_parse_memunit(co
  static int config_parse_interval(const char *s, int *ok);
  static void init_libevent(void);
  static int opt_streq(const char *s1, const char *s2);
 -/** Versions of libevent. */
 -typedef enum {
 -  /* Note: we compare these, so it's important that "old" precede everything,
 -   * and that "other" come last. */
 -  LE_OLD=0, LE_10C, LE_10D, LE_10E, LE_11, LE_11A, LE_11B, LE_12, LE_12A,
 -  LE_13, LE_13A, LE_13B, LE_13C, LE_13D, LE_13E,
 -  LE_140, LE_141, LE_142, LE_143, LE_144, LE_145, LE_146, LE_147, LE_148,
 -  LE_1499,
 -  LE_OTHER
 -} le_version_t;
 -static le_version_t decode_libevent_version(const char *v, int *bincompat_out);
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -static void check_libevent_version(const char *m, int server);
 -#endif
  
  /** Magic value for or_options_t. */
  #define OR_OPTIONS_MAGIC 9090909
@@@ -570,6 -726,7 +570,6 @@@ static config_format_t options_format 
    _option_abbrevs,
    _option_vars,
    (validate_fn_t)options_validate,
 -  options_description,
    NULL
  };
  
@@@ -590,6 -747,7 +590,6 @@@ static config_format_t state_format = 
    _state_abbrevs,
    _state_vars,
    (validate_fn_t)or_state_validate,
 -  state_description,
    &state_extra_var,
  };
  
@@@ -654,13 -812,13 +654,13 @@@ set_options(or_options_t *new_val, cha
              "Acting on config options left us in a broken state. Dying.");
      exit(1);
    }
 -  if (old_options)
 -    config_free(&options_format, old_options);
 +
 +  config_free(&options_format, old_options);
  
    return 0;
  }
  
 -extern const char tor_svn_revision[]; /* from tor_main.c */
 +extern const char tor_git_revision[]; /* from tor_main.c */
  
  /** The version of this Tor process, as parsed. */
  static char *_version = NULL;
@@@ -670,10 -828,10 +670,10 @@@ const char 
  get_version(void)
  {
    if (_version == NULL) {
 -    if (strlen(tor_svn_revision)) {
 -      size_t len = strlen(VERSION)+strlen(tor_svn_revision)+8;
 +    if (strlen(tor_git_revision)) {
 +      size_t len = strlen(VERSION)+strlen(tor_git_revision)+16;
        _version = tor_malloc(len);
 -      tor_snprintf(_version, len, "%s (r%s)", VERSION, tor_svn_revision);
 +      tor_snprintf(_version, len, "%s (git-%s)", VERSION, tor_git_revision);
      } else {
        _version = tor_strdup(VERSION);
      }
@@@ -686,10 -844,8 +686,10 @@@
  static void
  or_options_free(or_options_t *options)
  {
 -  if (options->_ExcludeExitNodesUnion)
 -    routerset_free(options->_ExcludeExitNodesUnion);
 +  if (!options)
 +    return;
 +
 +  routerset_free(options->_ExcludeExitNodesUnion);
    config_free(&options_format, options);
  }
  
@@@ -698,72 -854,43 +698,72 @@@
  void
  config_free_all(void)
  {
 -  if (global_options) {
 -    or_options_free(global_options);
 -    global_options = NULL;
 -  }
 -  if (global_state) {
 -    config_free(&state_format, global_state);
 -    global_state = NULL;
 -  }
 -  if (global_cmdline_options) {
 -    config_free_lines(global_cmdline_options);
 -    global_cmdline_options = NULL;
 -  }
 +  or_options_free(global_options);
 +  global_options = NULL;
 +
 +  config_free(&state_format, global_state);
 +  global_state = NULL;
 +
 +  config_free_lines(global_cmdline_options);
 +  global_cmdline_options = NULL;
 +
    tor_free(torrc_fname);
    tor_free(_version);
    tor_free(global_dirfrontpagecontents);
  }
  
 -/** If options->SafeLogging is on, return a not very useful string,
 - * else return address.
 +/** Make <b>address</b> -- a piece of information related to our operation as
 + * a client -- safe to log according to the settings in options->SafeLogging,
 + * and return it.
 + *
 + * (We return "[scrubbed]" if SafeLogging is "1", and address otherwise.)
 + */
 +const char *
 +safe_str_client(const char *address)
 +{
 +  tor_assert(address);
 +  if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL)
 +    return "[scrubbed]";
 +  else
 +    return address;
 +}
 +
 +/** Make <b>address</b> -- a piece of information of unspecified sensitivity
 + * -- safe to log according to the settings in options->SafeLogging, and
 + * return it.
 + *
 + * (We return "[scrubbed]" if SafeLogging is anything besides "0", and address
 + * otherwise.)
   */
  const char *
  safe_str(const char *address)
  {
    tor_assert(address);
 -  if (get_options()->SafeLogging)
 +  if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE)
      return "[scrubbed]";
    else
      return address;
  }
  
 +/** Equivalent to escaped(safe_str_client(address)).  See reentrancy note on
 + * escaped(): don't use this outside the main thread, or twice in the same
 + * log statement. */
 +const char *
 +escaped_safe_str_client(const char *address)
 +{
 +  if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL)
 +    return "[scrubbed]";
 +  else
 +    return escaped(address);
 +}
 +
  /** Equivalent to escaped(safe_str(address)).  See reentrancy note on
   * escaped(): don't use this outside the main thread, or twice in the same
   * log statement. */
  const char *
  escaped_safe_str(const char *address)
  {
 -  if (get_options()->SafeLogging)
 +  if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE)
      return "[scrubbed]";
    else
      return escaped(address);
@@@ -962,12 -1089,10 +962,12 @@@ options_act_reversible(or_options_t *ol
      }
  
      /* Launch the listeners.  (We do this before we setuid, so we can bind to
 -     * ports under 1024.) */
 -    if (retry_all_listeners(replaced_listeners, new_listeners) < 0) {
 -      *msg = tor_strdup("Failed to bind one of the listener ports.");
 -      goto rollback;
 +     * ports under 1024.)  We don't want to rebind if we're hibernating. */
 +    if (!we_are_hibernating()) {
 +      if (retry_all_listeners(replaced_listeners, new_listeners) < 0) {
 +        *msg = tor_strdup("Failed to bind one of the listener ports.");
 +        goto rollback;
 +      }
      }
    }
  
@@@ -981,15 -1106,6 +981,15 @@@
    }
  #endif
  
 +  /* Attempt to lock all current and future memory with mlockall() only once */
 +  if (options->DisableAllSwap) {
 +    if (tor_mlockall() == -1) {
 +      *msg = tor_strdup("DisableAllSwap failure. Do you have proper "
 +                        "permissions?");
 +      goto done;
 +    }
 +  }
 +
    /* Setuid/setgid as appropriate */
    if (options->User) {
      if (switch_id(options->User) != 0) {
@@@ -1002,9 -1118,11 +1002,9 @@@
    /* Ensure data directory is private; create if possible. */
    if (check_private_dir(options->DataDirectory,
                          running_tor ? CPD_CREATE : CPD_CHECK)<0) {
 -    char buf[1024];
 -    int tmp = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
                "Couldn't access/create private data directory \"%s\"",
                options->DataDirectory);
 -    *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
      goto done;
      /* No need to roll back, since you can't change the value. */
    }
@@@ -1015,8 -1133,10 +1015,8 @@@
      tor_snprintf(fn, len, "%s"PATH_SEPARATOR"cached-status",
                   options->DataDirectory);
      if (check_private_dir(fn, running_tor ? CPD_CREATE : CPD_CHECK) < 0) {
 -      char buf[1024];
 -      int tmp = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                  "Couldn't access/create private data directory \"%s\"", fn);
 -      *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
        tor_free(fn);
        goto done;
      }
@@@ -1116,6 -1236,7 +1116,6 @@@ get_effective_bwrate(or_options_t *opti
      bw = options->MaxAdvertisedBandwidth;
    if (options->RelayBandwidthRate > 0 && bw > options->RelayBandwidthRate)
      bw = options->RelayBandwidthRate;
 -
    /* ensure_bandwidth_cap() makes sure that this cast can't overflow. */
    return (uint32_t)bw;
  }
@@@ -1193,14 -1314,14 +1193,14 @@@ options_act(or_options_t *old_options
      return 0;
  
    /* Finish backgrounding the process */
 -  if (running_tor && options->RunAsDaemon) {
 +  if (options->RunAsDaemon) {
      /* We may be calling this for the n'th time (on SIGHUP), but it's safe. */
      finish_daemon(options->DataDirectory);
    }
  
    /* Write our PID to the PID file. If we do not have write permissions we
     * will log a warning */
 -  if (running_tor && options->PidFile)
 +  if (options->PidFile)
      write_pidfile(options->PidFile);
  
    /* Register addressmap directives */
@@@ -1233,66 -1354,18 +1233,66 @@@
    if (accounting_is_enabled(options))
      configure_accounting(time(NULL));
  
 +  /* parse RefuseUnknownExits tristate */
 +  if (!strcmp(options->RefuseUnknownExits, "0"))
 +    options->RefuseUnknownExits_ = 0;
 +  else if (!strcmp(options->RefuseUnknownExits, "1"))
 +    options->RefuseUnknownExits_ = 1;
 +  else if (!strcmp(options->RefuseUnknownExits, "auto"))
 +    options->RefuseUnknownExits_ = -1;
 +  else {
 +    /* Should have caught this in options_validate */
 +    return -1;
 +  }
 +
 +  /* Change the cell EWMA settings */
 +  cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus());
 +
    /* Check for transitions that need action. */
    if (old_options) {
 -    if (options->UseEntryGuards && !old_options->UseEntryGuards) {
 +
 +    if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
 +        (options->ExcludeNodes &&
 +         !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)) ||
 +        (options->ExcludeExitNodes &&
 +         !routerset_equal(old_options->ExcludeExitNodes,
 +                          options->ExcludeExitNodes)) ||
 +        (options->EntryNodes &&
 +         !routerset_equal(old_options->EntryNodes, options->EntryNodes)) ||
 +        (options->ExitNodes &&
 +         !routerset_equal(old_options->ExitNodes, options->ExitNodes)) ||
 +        options->StrictNodes != old_options->StrictNodes) {
        log_info(LD_CIRC,
 -               "Switching to entry guards; abandoning previous circuits");
 +               "Changed to using entry guards, or changed preferred or "
 +               "excluded node lists. Abandoning previous circuits.");
        circuit_mark_all_unused_circs();
        circuit_expire_all_dirty_circs();
      }
  
 +/* How long should we delay counting bridge stats after becoming a bridge?
 + * We use this so we don't count people who used our bridge thinking it is
 + * a relay. If you change this, don't forget to change the log message
 + * below. It's 4 hours (the time it takes to stop being used by clients)
 + * plus some extra time for clock skew. */
 +#define RELAY_BRIDGE_STATS_DELAY (6 * 60 * 60)
 +
      if (! bool_eq(options->BridgeRelay, old_options->BridgeRelay)) {
 -      log_info(LD_GENERAL, "Bridge status changed.  Forgetting GeoIP stats.");
 -      geoip_remove_old_clients(time(NULL)+(2*60*60));
 +      int was_relay = 0;
 +      if (options->BridgeRelay) {
 +        time_t int_start = time(NULL);
 +        if (old_options->ORPort == options->ORPort) {
 +          int_start += RELAY_BRIDGE_STATS_DELAY;
 +          was_relay = 1;
 +        }
 +        geoip_bridge_stats_init(int_start);
 +        log_info(LD_CONFIG, "We are acting as a bridge now.  Starting new "
 +                 "GeoIP stats interval%s.", was_relay ? " in 6 "
 +                 "hours from now" : "");
 +      } else {
 +        geoip_bridge_stats_term();
 +        log_info(LD_GENERAL, "We are no longer acting as a bridge.  "
 +                 "Forgetting GeoIP stats.");
 +      }
      }
  
      if (options_transition_affects_workers(old_options, options)) {
@@@ -1304,7 -1377,7 +1304,7 @@@
            return -1;
          }
          ip_address_changed(0);
 -        if (has_completed_circuit || !any_predicted_circuits(time(NULL)))
 +        if (can_complete_circuit || !any_predicted_circuits(time(NULL)))
            inform_testing_reachability();
        }
        cpuworkers_rotate();
@@@ -1317,10 -1390,6 +1317,10 @@@
  
      if (options->V3AuthoritativeDir && !old_options->V3AuthoritativeDir)
        init_keys();
 +
 +    if (options->PerConnBWRate != old_options->PerConnBWRate ||
 +        options->PerConnBWBurst != old_options->PerConnBWBurst)
 +      connection_or_update_token_buckets(get_connection_array(), options);
    }
  
    /* Maybe load geoip file */
@@@ -1343,63 -1412,13 +1343,63 @@@
      geoip_load_file(actual_fname, options);
      tor_free(actual_fname);
    }
 -#ifdef ENABLE_GEOIP_STATS
 -  log_warn(LD_CONFIG, "We are configured to measure GeoIP statistics, but "
 -           "the way these statistics are measured has changed "
 -           "significantly in later versions of Tor. The results may not be "
 -           "as expected if you are used to later versions.  Be sure you "
 -           "know what you are doing.");
 -#endif
 +
 +  if (options->DirReqStatistics && !geoip_is_loaded()) {
 +    /* Check if GeoIP database could be loaded. */
 +    log_warn(LD_CONFIG, "Configured to measure directory request "
 +             "statistics, but no GeoIP database found!");
 +    return -1;
 +  }
 +
 +  if (options->EntryStatistics) {
 +    if (should_record_bridge_info(options)) {
 +      /* Don't allow measuring statistics on entry guards when configured
 +       * as bridge. */
 +      log_warn(LD_CONFIG, "Bridges cannot be configured to measure "
 +               "additional GeoIP statistics as entry guards.");
 +      return -1;
 +    } else if (!geoip_is_loaded()) {
 +      /* Check if GeoIP database could be loaded. */
 +      log_warn(LD_CONFIG, "Configured to measure entry node statistics, "
 +               "but no GeoIP database found!");
 +      return -1;
 +    }
 +  }
 +
 +  if (options->CellStatistics || options->DirReqStatistics ||
 +      options->EntryStatistics || options->ExitPortStatistics) {
 +    time_t now = time(NULL);
 +    if ((!old_options || !old_options->CellStatistics) &&
 +        options->CellStatistics)
 +      rep_hist_buffer_stats_init(now);
 +    if ((!old_options || !old_options->DirReqStatistics) &&
 +        options->DirReqStatistics)
 +      geoip_dirreq_stats_init(now);
 +    if ((!old_options || !old_options->EntryStatistics) &&
 +        options->EntryStatistics)
 +      geoip_entry_stats_init(now);
 +    if ((!old_options || !old_options->ExitPortStatistics) &&
 +        options->ExitPortStatistics)
 +      rep_hist_exit_stats_init(now);
 +    if (!old_options)
 +      log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
 +                 "the *-stats files that will first be written to the "
 +                 "data directory in 24 hours from now.");
 +  }
 +
 +  if (old_options && old_options->CellStatistics &&
 +      !options->CellStatistics)
 +    rep_hist_buffer_stats_term();
 +  if (old_options && old_options->DirReqStatistics &&
 +      !options->DirReqStatistics)
 +    geoip_dirreq_stats_term();
 +  if (old_options && old_options->EntryStatistics &&
 +      !options->EntryStatistics)
 +    geoip_entry_stats_term();
 +  if (old_options && old_options->ExitPortStatistics &&
 +      !options->ExitPortStatistics)
 +    rep_hist_exit_stats_term();
 +
    /* Check if we need to parse and add the EntryNodes config option. */
    if (options->EntryNodes &&
        (!old_options ||
@@@ -1472,10 -1491,7 +1472,10 @@@ expand_abbrev(config_format_t *fmt, con
                   fmt->abbrevs[i].abbreviated,
                   fmt->abbrevs[i].full);
        }
 -      return fmt->abbrevs[i].full;
 +      /* Keep going through the list in case we want to rewrite it more.
 +       * (We could imagine recursing here, but I don't want to get the
 +       * user into an infinite loop if we craft our list wrong.) */
 +      option = fmt->abbrevs[i].full;
      }
    }
    return option;
@@@ -1520,10 -1536,7 +1520,10 @@@ config_get_commandlines(int argc, char 
      *new = tor_malloc_zero(sizeof(config_line_t));
      s = argv[i];
  
 -    while (*s == '-')
 +    /* Each keyword may be prefixed with one or two dashes. */
 +    if (*s == '-')
 +      s++;
 +    if (*s == '-')
        s++;
  
      (*new)->key = tor_strdup(expand_abbrev(&options_format, s, 1, 1));
@@@ -1615,6 -1628,19 +1615,6 @@@ config_free_lines(config_line_t *front
    }
  }
  
 -/** Return the description for a given configuration variable, or NULL if no
 - * description exists. */
 -static const char *
 -config_find_description(config_format_t *fmt, const char *name)
 -{
 -  int i;
 -  for (i=0; fmt->descriptions[i].name; ++i) {
 -    if (!strcasecmp(name, fmt->descriptions[i].name))
 -      return fmt->descriptions[i].description;
 -  }
 -  return NULL;
 -}
 -
  /** If <b>key</b> is a configuration option, return the corresponding
   * config_var_t.  Otherwise, if <b>key</b> is a non-standard abbreviation,
   * warn, and return the corresponding config_var_t.  Otherwise return NULL.
@@@ -1645,16 -1671,6 +1645,16 @@@ config_find_option(config_format_t *fmt
    return NULL;
  }
  
 +/** Return the number of option entries in <b>fmt</b>. */
 +static int
 +config_count_options(config_format_t *fmt)
 +{
 +  int i;
 +  for (i=0; fmt->vars[i].name; ++i)
 +    ;
 +  return i;
 +}
 +
  /*
   * Functions to assign config options.
   */
@@@ -1668,7 -1684,8 +1668,7 @@@ static in
  config_assign_value(config_format_t *fmt, or_options_t *options,
                      config_line_t *c, char **msg)
  {
 -  int i, r, ok;
 -  char buf[1024];
 +  int i, ok;
    config_var_t *var;
    void *lvalue;
  
@@@ -1684,9 -1701,10 +1684,9 @@@
    case CONFIG_TYPE_UINT:
      i = (int)tor_parse_long(c->value, 10, 0, INT_MAX, &ok, NULL);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Int keyword '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1695,9 -1713,10 +1695,9 @@@
    case CONFIG_TYPE_INTERVAL: {
      i = config_parse_interval(c->value, &ok);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Interval '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1707,9 -1726,10 +1707,9 @@@
    case CONFIG_TYPE_MEMUNIT: {
      uint64_t u64 = config_parse_memunit(c->value, &ok);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Value '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(uint64_t *)lvalue = u64;
@@@ -1719,9 -1739,10 +1719,9 @@@
    case CONFIG_TYPE_BOOL:
      i = (int)tor_parse_long(c->value, 10, 0, 1, &ok, NULL);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Boolean '%s %s' expects 0 or 1.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1739,8 -1760,9 +1739,8 @@@
  
    case CONFIG_TYPE_ISOTIME:
      if (parse_iso_time(c->value, (time_t *)lvalue)) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Invalid time '%s' for keyword '%s'", c->value, c->key);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      break;
@@@ -1751,8 -1773,9 +1751,8 @@@
      }
      *(routerset_t**)lvalue = routerset_new();
      if (routerset_parse(*(routerset_t**)lvalue, c->value, c->key)<0) {
 -      tor_snprintf(buf, sizeof(buf), "Invalid exit list '%s' for option '%s'",
 +      tor_asprintf(msg, "Invalid exit list '%s' for option '%s'",
                     c->value, c->key);
 -      *msg = tor_strdup(buf);
        return -1;
      }
      break;
@@@ -1777,8 -1800,9 +1777,8 @@@
      log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key);
      break;
    case CONFIG_TYPE_LINELIST_V:
 -    r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
          "You may not provide a value for virtual option '%s'", c->key);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    default:
      tor_assert(0);
@@@ -1799,7 -1823,7 +1799,7 @@@
  static int
  config_assign_line(config_format_t *fmt, or_options_t *options,
                     config_line_t *c, int use_defaults,
 -                   int clear_first, char **msg)
 +                   int clear_first, bitarray_t *options_seen, char **msg)
  {
    config_var_t *var;
  
@@@ -1814,12 -1838,13 +1814,12 @@@
        config_line_append((config_line_t**)lvalue, c->key, c->value);
        return 0;
      } else {
 -      char buf[1024];
 -      int tmp = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                  "Unknown option '%s'.  Failing.", c->key);
        return -1;
      }
    }
 +
    /* Put keyword into canonical case. */
    if (strcmp(var->name, c->key)) {
      tor_free(c->key);
@@@ -1842,18 -1867,6 +1842,18 @@@
      return 0;
    }
  
 +  if (options_seen && (var->type != CONFIG_TYPE_LINELIST &&
 +                       var->type != CONFIG_TYPE_LINELIST_S)) {
 +    /* We're tracking which options we've seen, and this option is not
 +     * supposed to occur more than once. */
 +    int var_index = (int)(var - fmt->vars);
 +    if (bitarray_is_set(options_seen, var_index)) {
 +      log_warn(LD_CONFIG, "Option '%s' used more than once; all but the last "
 +               "value will be ignored.", var->name);
 +    }
 +    bitarray_set(options_seen, var_index);
 +  }
 +
    if (config_assign_value(fmt, options, c, msg) < 0)
      return -2;
    return 0;
@@@ -1954,6 -1967,7 +1954,6 @@@ get_assigned_option(config_format_t *fm
  {
    config_var_t *var;
    const void *value;
 -  char buf[32];
    config_line_t *result;
    tor_assert(options && key);
  
@@@ -1994,16 -2008,19 +1994,16 @@@
      case CONFIG_TYPE_UINT:
        /* This means every or_options_t uint or bool element
         * needs to be an int. Not, say, a uint16_t or char. */
 -      tor_snprintf(buf, sizeof(buf), "%d", *(int*)value);
 -      result->value = tor_strdup(buf);
 +      tor_asprintf(&result->value, "%d", *(int*)value);
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_MEMUNIT:
 -      tor_snprintf(buf, sizeof(buf), U64_FORMAT,
 +      tor_asprintf(&result->value, U64_FORMAT,
                     U64_PRINTF_ARG(*(uint64_t*)value));
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_DOUBLE:
 -      tor_snprintf(buf, sizeof(buf), "%f", *(double*)value);
 -      result->value = tor_strdup(buf);
 +      tor_asprintf(&result->value, "%f", *(double*)value);
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_BOOL:
@@@ -2122,8 -2139,6 +2122,8 @@@ config_assign(config_format_t *fmt, voi
                int use_defaults, int clear_first, char **msg)
  {
    config_line_t *p;
 +  bitarray_t *options_seen;
 +  const int n_options = config_count_options(fmt);
  
    CHECK(fmt, options);
  
@@@ -2143,18 -2158,14 +2143,18 @@@
        config_reset_line(fmt, options, p->key, use_defaults);
    }
  
 +  options_seen = bitarray_init_zero(n_options);
    /* pass 3: assign. */
    while (list) {
      int r;
      if ((r=config_assign_line(fmt, options, list, use_defaults,
 -                              clear_first, msg)))
 +                              clear_first, options_seen, msg))) {
 +      bitarray_free(options_seen);
        return r;
 +    }
      list = list->next;
    }
 +  bitarray_free(options_seen);
    return 0;
  }
  
@@@ -2297,10 -2308,20 +2297,10 @@@ list_torrc_options(void
    smartlist_t *lines = smartlist_create();
    for (i = 0; _option_vars[i].name; ++i) {
      config_var_t *var = &_option_vars[i];
      if (var->type == CONFIG_TYPE_OBSOLETE ||
          var->type == CONFIG_TYPE_LINELIST_V)
        continue;
 -    desc = config_find_description(&options_format, var->name);
      printf("%s\n", var->name);
 -    if (desc) {
 -      wrap_string(lines, desc, 76, "    ", "    ");
 -      SMARTLIST_FOREACH(lines, char *, cp, {
 -          printf("%s", cp);
 -          tor_free(cp);
 -        });
 -      smartlist_clear(lines);
 -    }
    }
    smartlist_free(lines);
  }
@@@ -2319,7 -2340,7 +2319,7 @@@ resolve_my_address(int warn_severity, o
                     uint32_t *addr_out, char **hostname_out)
  {
    struct in_addr in;
 -  uint32_t addr;
 +  uint32_t addr; /* host order */
    char hostname[256];
    int explicit_ip=1;
    int explicit_hostname=1;
@@@ -2349,8 -2370,8 +2349,8 @@@
    if (tor_inet_aton(hostname, &in) == 0) {
      /* then we have to resolve it */
      explicit_ip = 0;
 -    if (tor_lookup_hostname(hostname, &addr)) {
 -      uint32_t interface_ip;
 +    if (tor_lookup_hostname(hostname, &addr)) { /* failed to resolve */
 +      uint32_t interface_ip; /* host order */
  
        if (explicit_hostname) {
          log_fn(warn_severity, LD_CONFIG,
@@@ -2371,7 -2392,7 +2371,7 @@@
        log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for "
               "local interface. Using that.", tmpbuf);
        strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname));
 -    } else {
 +    } else { /* resolved hostname into addr */
        in.s_addr = htonl(addr);
  
        if (!explicit_hostname &&
@@@ -2545,10 -2566,7 +2545,10 @@@ config_free(config_format_t *fmt, void 
  {
    int i;
  
 -  tor_assert(options);
 +  if (!options)
 +    return;
 +
 +  tor_assert(fmt);
  
    for (i=0; fmt->vars[i].name; ++i)
      option_clear(fmt, options, &(fmt->vars[i]));
@@@ -2650,8 -2668,6 +2650,8 @@@ is_listening_on_low_port(uint16_t port_
                           const config_line_t *listen_options)
  {
  #ifdef MS_WINDOWS
 +  (void) port_option;
 +  (void) listen_options;
    return 0; /* No port is too low for windows. */
  #else
    const config_line_t *l;
@@@ -2701,6 -2717,7 +2701,6 @@@ config_dump(config_format_t *fmt, void 
    config_line_t *line, *assigned;
    char *result;
    int i;
 -  const char *desc;
    char *msg = NULL;
  
    defaults = config_alloc(fmt);
@@@ -2728,13 -2745,24 +2728,13 @@@
               option_is_same(fmt, options, defaults, fmt->vars[i].name))
        comment_option = 1;
  
 -    desc = config_find_description(fmt, fmt->vars[i].name);
      line = assigned = get_assigned_option(fmt, options, fmt->vars[i].name, 1);
  
 -    if (line && desc) {
 -      /* Only dump the description if there's something to describe. */
 -      wrap_string(elements, desc, 78, "# ", "# ");
 -    }
 -
      for (; line; line = line->next) {
 -      size_t len = strlen(line->key) + strlen(line->value) + 5;
        char *tmp;
 -      tmp = tor_malloc(len);
 -      if (tor_snprintf(tmp, len, "%s%s %s\n",
 -                       comment_option ? "# " : "",
 -                       line->key, line->value)<0) {
 -        log_err(LD_BUG,"Internal error writing option value");
 -        tor_assert(0);
 -      }
 +      tor_asprintf(&tmp, "%s%s %s\n",
 +                   comment_option ? "# " : "",
 +                   line->key, line->value);
        smartlist_add(elements, tmp);
      }
      config_free_lines(assigned);
@@@ -2743,8 -2771,13 +2743,8 @@@
    if (fmt->extra) {
      line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->var_offset);
      for (; line; line = line->next) {
 -      size_t len = strlen(line->key) + strlen(line->value) + 3;
        char *tmp;
 -      tmp = tor_malloc(len);
 -      if (tor_snprintf(tmp, len, "%s %s\n", line->key, line->value)<0) {
 -        log_err(LD_BUG,"Internal error writing option value");
 -        tor_assert(0);
 -      }
 +      tor_asprintf(&tmp, "%s %s\n", line->key, line->value);
        smartlist_add(elements, tmp);
      }
    }
@@@ -2760,7 -2793,7 +2760,7 @@@
   * the configuration in <b>options</b>.  If <b>minimal</b> is true, do not
   * include options that are the same as Tor's defaults.
   */
 -static char *
 +char *
  options_dump(or_options_t *options, int minimal)
  {
    return config_dump(&options_format, options, minimal, 0);
@@@ -2773,6 -2806,7 +2773,6 @@@ static in
  validate_ports_csv(smartlist_t *sl, const char *name, char **msg)
  {
    int i;
 -  char buf[1024];
    tor_assert(name);
  
    if (!sl)
@@@ -2782,7 -2816,9 +2782,7 @@@
    {
      i = atoi(cp);
      if (i < 1 || i > 65535) {
 -      int r = tor_snprintf(buf, sizeof(buf),
 -                           "Port '%s' out of range in %s", cp, name);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +      tor_asprintf(msg, "Port '%s' out of range in %s", cp, name);
        return -1;
      }
    });
@@@ -2796,15 -2832,18 +2796,15 @@@
  static int
  ensure_bandwidth_cap(uint64_t *value, const char *desc, char **msg)
  {
 -  int r;
 -  char buf[1024];
    if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
      /* This handles an understandable special case where somebody says "2gb"
       * whereas our actual maximum is 2gb-1 (INT_MAX) */
      --*value;
    }
    if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
 -    r = tor_snprintf(buf, sizeof(buf), "%s ("U64_FORMAT") must be at most %d",
 -                     desc, U64_PRINTF_ARG(*value),
 -                     ROUTER_MAX_DECLARED_BANDWIDTH);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +    tor_asprintf(msg, "%s ("U64_FORMAT") must be at most %d",
 +                 desc, U64_PRINTF_ARG(*value),
 +                 ROUTER_MAX_DECLARED_BANDWIDTH);
      return -1;
    }
    return 0;
@@@ -2855,14 -2894,15 +2855,14 @@@ compute_publishserverdescriptor(or_opti
  /** Highest allowable value for RendPostPeriod. */
  #define MAX_DIR_PERIOD (MIN_ONION_KEY_LIFETIME/2)
  
 -/** Lowest allowable value for CircuitBuildTimeout; values too low will
 - * increase network load because of failing connections being retried, and
 - * might prevent users from connecting to the network at all. */
 -#define MIN_CIRCUIT_BUILD_TIMEOUT 30
 -
  /** Lowest allowable value for MaxCircuitDirtiness; if this is too low, Tor
   * will generate too many circuits and potentially overload the network. */
  #define MIN_MAX_CIRCUIT_DIRTINESS 10
  
 +/** Lowest allowable value for CircuitStreamTimeout; if this is too low, Tor
 + * will generate too many circuits and potentially overload the network. */
 +#define MIN_CIRCUIT_STREAM_TIMEOUT 10
 +
  /** Return 0 if every setting in <b>options</b> is reasonable, and a
   * permissible transition from <b>old_options</b>. Else return -1.
   * Should have no side effects, except for normalizing the contents of
@@@ -2879,9 -2919,10 +2879,9 @@@ static in
  options_validate(or_options_t *old_options, or_options_t *options,
                   int from_setconf, char **msg)
  {
 -  int i, r;
 +  int i;
    config_line_t *cl;
    const char *uname = get_uname();
 -  char buf[1024];
  #define REJECT(arg) \
    STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END
  #define COMPLAIN(arg) STMT_BEGIN log(LOG_WARN, LD_CONFIG, arg); STMT_END
@@@ -2898,7 -2939,7 +2898,7 @@@
         !strcmpstart(uname, "Windows Me"))) {
      log(LOG_WARN, LD_CONFIG, "Tor is running as a server, but you are "
          "running %s; this probably won't work. See "
 -        "http://wiki.noreply.org/noreply/TheOnionRouter/TorFAQ#ServerOS "
 +        "https://wiki.torproject.org/TheOnionRouter/TorFAQ#ServerOS "
          "for details.", uname);
    }
  
@@@ -2917,8 -2958,8 +2917,8 @@@
    if (options->TransPort == 0 && options->TransListenAddress != NULL)
      REJECT("TransPort must be defined if TransListenAddress is defined.");
  
 -  if (options->NatdPort == 0 && options->NatdListenAddress != NULL)
 -    REJECT("NatdPort must be defined if NatdListenAddress is defined.");
 +  if (options->NATDPort == 0 && options->NATDListenAddress != NULL)
 +    REJECT("NATDPort must be defined if NATDListenAddress is defined.");
  
    /* Don't gripe about SocksPort 0 with SocksListenAddress set; a standard
     * configuration does this. */
@@@ -2937,8 -2978,8 +2937,8 @@@
        old = old_options ? old_options->TransListenAddress : NULL;
        tp = "transparent proxy";
      } else {
 -      opt = options->NatdListenAddress;
 -      old = old_options ? old_options->NatdListenAddress : NULL;
 +      opt = options->NATDListenAddress;
 +      old = old_options ? old_options->NATDListenAddress : NULL;
        tp = "natd proxy";
      }
  
@@@ -2976,9 -3017,10 +2976,9 @@@
      }
    } else {
      if (!is_legal_nickname(options->Nickname)) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Nickname '%s' is wrong length or contains illegal characters.",
            options->Nickname);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
    }
@@@ -2995,6 -3037,14 +2995,6 @@@
    if (options_init_logs(options, 1)<0) /* Validate the log(s) */
      REJECT("Failed to validate Log options. See logs for details.");
  
 -  if (options->NoPublish) {
 -    log(LOG_WARN, LD_CONFIG,
 -        "NoPublish is obsolete. Use PublishServerDescriptor instead.");
 -    SMARTLIST_FOREACH(options->PublishServerDescriptor, char *, s,
 -                      tor_free(s));
 -    smartlist_clear(options->PublishServerDescriptor);
 -  }
 -
    if (authdir_mode(options)) {
      /* confirm that our address isn't broken, so we can complain now */
      uint32_t tmp;
@@@ -3002,12 -3052,6 +3002,12 @@@
        REJECT("Failed to resolve/guess local address. See logs for details.");
    }
  
 +  if (strcmp(options->RefuseUnknownExits, "0") &&
 +      strcmp(options->RefuseUnknownExits, "1") &&
 +      strcmp(options->RefuseUnknownExits, "auto")) {
 +    REJECT("RefuseUnknownExits must be 0, 1, or auto");
 +  }
 +
  #ifndef MS_WINDOWS
    if (options->RunAsDaemon && torrc_fname && path_is_relative(torrc_fname))
      REJECT("Can't use a relative path to torrc when RunAsDaemon is set.");
@@@ -3022,14 -3066,14 +3022,14 @@@
    if (options->TransPort < 0 || options->TransPort > 65535)
      REJECT("TransPort option out of bounds.");
  
 -  if (options->NatdPort < 0 || options->NatdPort > 65535)
 -    REJECT("NatdPort option out of bounds.");
 +  if (options->NATDPort < 0 || options->NATDPort > 65535)
 +    REJECT("NATDPort option out of bounds.");
  
    if (options->SocksPort == 0 && options->TransPort == 0 &&
 -      options->NatdPort == 0 && options->ORPort == 0 &&
 +      options->NATDPort == 0 && options->ORPort == 0 &&
        options->DNSPort == 0 && !options->RendConfigLines)
      log(LOG_WARN, LD_CONFIG,
 -        "SocksPort, TransPort, NatdPort, DNSPort, and ORPort are all "
 +        "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all "
          "undefined, and there aren't any hidden services configured.  "
          "Tor will still run, but probably won't do anything.");
  
@@@ -3063,11 -3107,19 +3063,11 @@@
      routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes);
    }
  
 -  if (options->StrictExitNodes &&
 -      (!options->ExitNodes) &&
 -      (!old_options ||
 -       (old_options->StrictExitNodes != options->StrictExitNodes) ||
 -       (!routerset_equal(old_options->ExitNodes,options->ExitNodes))))
 -    COMPLAIN("StrictExitNodes set, but no ExitNodes listed.");
 -
 -  if (options->StrictEntryNodes &&
 -      (!options->EntryNodes) &&
 -      (!old_options ||
 -       (old_options->StrictEntryNodes != options->StrictEntryNodes) ||
 -       (!routerset_equal(old_options->EntryNodes,options->EntryNodes))))
 -    COMPLAIN("StrictEntryNodes set, but no EntryNodes listed.");
 +  if (options->ExcludeNodes && options->StrictNodes) {
 +    COMPLAIN("You have asked to exclude certain relays from all positions "
 +             "in your circuits. Expect hidden services and other Tor "
 +             "features to be broken in unpredictable ways.");
 +  }
  
    if (options->EntryNodes && !routerset_is_list(options->EntryNodes)) {
      /* XXXX fix this; see entry_guards_prepend_from_config(). */
@@@ -3105,10 -3157,6 +3105,10 @@@
            options->V3AuthoritativeDir))
        REJECT("AuthoritativeDir is set, but none of "
               "(Bridge/HS/V1/V2/V3)AuthoritativeDir is set.");
 +    /* If we have a v3bandwidthsfile and it's broken, complain on startup */
 +    if (options->V3BandwidthsFile && !old_options) {
 +      dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL);
 +    }
    }
  
    if (options->AuthoritativeDir && !options->DirPort)
@@@ -3120,14 -3168,15 +3120,14 @@@
    if (options->AuthoritativeDir && options->ClientOnly)
      REJECT("Running as authoritative directory, but ClientOnly also set.");
  
 -  if (options->HSAuthorityRecordStats && !options->HSAuthoritativeDir)
 -    REJECT("HSAuthorityRecordStats is set but we're not running as "
 -           "a hidden service authority.");
 +  if (options->FetchDirInfoExtraEarly && !options->FetchDirInfoEarly)
 +    REJECT("FetchDirInfoExtraEarly requires that you also set "
 +           "FetchDirInfoEarly");
  
    if (options->ConnLimit <= 0) {
 -    r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
          "ConnLimit must be greater than 0, but was set to %d",
          options->ConnLimit);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    }
  
@@@ -3246,29 -3295,18 +3246,29 @@@
          else if (!strcasecmp(cp, "rendezvous"))
            options->_AllowInvalid |= ALLOW_INVALID_RENDEZVOUS;
          else {
 -          r = tor_snprintf(buf, sizeof(buf),
 +          tor_asprintf(msg,
                "Unrecognized value '%s' in AllowInvalidNodes", cp);
 -          *msg = tor_strdup(r >= 0 ? buf : "internal error");
            return -1;
          }
        });
    }
  
 +  if (!options->SafeLogging ||
 +      !strcasecmp(options->SafeLogging, "0")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_NONE;
 +  } else if (!strcasecmp(options->SafeLogging, "relay")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_RELAY;
 +  } else if (!strcasecmp(options->SafeLogging, "1")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_ALL;
 +  } else {
 +    tor_asprintf(msg,
 +                     "Unrecognized value '%s' in SafeLogging",
 +                     escaped(options->SafeLogging));
 +    return -1;
 +  }
 +
    if (compute_publishserverdescriptor(options) < 0) {
 -    r = tor_snprintf(buf, sizeof(buf),
 -                     "Unrecognized value in PublishServerDescriptor");
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +    tor_asprintf(msg, "Unrecognized value in PublishServerDescriptor");
      return -1;
    }
  
@@@ -3281,12 -3319,6 +3281,12 @@@
             "PublishServerDescriptor line.");
    }
  
 +  if (options->BridgeRelay && options->DirPort) {
 +    log_warn(LD_CONFIG, "Can't set a DirPort on a bridge relay; disabling "
 +             "DirPort");
 +    options->DirPort = 0;
 +  }
 +
    if (options->MinUptimeHidServDirectoryV2 < 0) {
      log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at "
                          "least 0 seconds. Changing to 0.");
@@@ -3294,30 -3326,29 +3294,30 @@@
    }
  
    if (options->RendPostPeriod < MIN_REND_POST_PERIOD) {
 -    log(LOG_WARN,LD_CONFIG,"RendPostPeriod option is too short; "
 -        "raising to %d seconds.", MIN_REND_POST_PERIOD);
 +    log_warn(LD_CONFIG, "RendPostPeriod option is too short; "
 +             "raising to %d seconds.", MIN_REND_POST_PERIOD);
      options->RendPostPeriod = MIN_REND_POST_PERIOD;
    }
  
    if (options->RendPostPeriod > MAX_DIR_PERIOD) {
 -    log(LOG_WARN, LD_CONFIG, "RendPostPeriod is too large; clipping to %ds.",
 -        MAX_DIR_PERIOD);
 +    log_warn(LD_CONFIG, "RendPostPeriod is too large; clipping to %ds.",
 +             MAX_DIR_PERIOD);
      options->RendPostPeriod = MAX_DIR_PERIOD;
    }
  
 -  if (options->CircuitBuildTimeout < MIN_CIRCUIT_BUILD_TIMEOUT) {
 -    log(LOG_WARN, LD_CONFIG, "CircuitBuildTimeout option is too short; "
 -      "raising to %d seconds.", MIN_CIRCUIT_BUILD_TIMEOUT);
 -    options->CircuitBuildTimeout = MIN_CIRCUIT_BUILD_TIMEOUT;
 -  }
 -
    if (options->MaxCircuitDirtiness < MIN_MAX_CIRCUIT_DIRTINESS) {
 -    log(LOG_WARN, LD_CONFIG, "MaxCircuitDirtiness option is too short; "
 -      "raising to %d seconds.", MIN_MAX_CIRCUIT_DIRTINESS);
 +    log_warn(LD_CONFIG, "MaxCircuitDirtiness option is too short; "
 +             "raising to %d seconds.", MIN_MAX_CIRCUIT_DIRTINESS);
      options->MaxCircuitDirtiness = MIN_MAX_CIRCUIT_DIRTINESS;
    }
  
 +  if (options->CircuitStreamTimeout &&
 +      options->CircuitStreamTimeout < MIN_CIRCUIT_STREAM_TIMEOUT) {
 +    log_warn(LD_CONFIG, "CircuitStreamTimeout option is too short; "
 +             "raising to %d seconds.", MIN_CIRCUIT_STREAM_TIMEOUT);
 +    options->CircuitStreamTimeout = MIN_CIRCUIT_STREAM_TIMEOUT;
 +  }
 +
    if (options->KeepalivePeriod < 1)
      REJECT("KeepalivePeriod option must be positive.");
  
@@@ -3336,37 -3367,34 +3336,37 @@@
    if (ensure_bandwidth_cap(&options->RelayBandwidthBurst,
                             "RelayBandwidthBurst", msg) < 0)
      return -1;
 +  if (ensure_bandwidth_cap(&options->PerConnBWRate,
 +                           "PerConnBWRate", msg) < 0)
 +    return -1;
 +  if (ensure_bandwidth_cap(&options->PerConnBWBurst,
 +                           "PerConnBWBurst", msg) < 0)
 +    return -1;
  
    if (server_mode(options)) {
      if (options->BandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "BandwidthRate is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->BandwidthRate,
                         ROUTER_REQUIRED_MIN_BANDWIDTH);
        return -1;
      } else if (options->MaxAdvertisedBandwidth <
                 ROUTER_REQUIRED_MIN_BANDWIDTH/2) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "MaxAdvertisedBandwidth is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->MaxAdvertisedBandwidth,
                         ROUTER_REQUIRED_MIN_BANDWIDTH/2);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      if (options->RelayBandwidthRate &&
        options->RelayBandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "RelayBandwidthRate is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->RelayBandwidthRate,
                         ROUTER_REQUIRED_MIN_BANDWIDTH);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
    }
@@@ -3391,73 -3419,34 +3391,73 @@@
    if (accounting_parse_options(options, 1)<0)
      REJECT("Failed to parse accounting options. See logs for details.");
  
 -  if (options->HttpProxy) { /* parse it now */
 -    if (parse_addr_port(LOG_WARN, options->HttpProxy, NULL,
 -                        &options->HttpProxyAddr, &options->HttpProxyPort) < 0)
 -      REJECT("HttpProxy failed to parse or resolve. Please fix.");
 -    if (options->HttpProxyPort == 0) { /* give it a default */
 -      options->HttpProxyPort = 80;
 +  if (options->HTTPProxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->HTTPProxy,
 +                        &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0)
 +      REJECT("HTTPProxy failed to parse or resolve. Please fix.");
 +    if (options->HTTPProxyPort == 0) { /* give it a default */
 +      options->HTTPProxyPort = 80;
 +    }
 +  }
 +
 +  if (options->HTTPProxyAuthenticator) {
 +    if (strlen(options->HTTPProxyAuthenticator) >= 48)
 +      REJECT("HTTPProxyAuthenticator is too long (>= 48 chars).");
 +  }
 +
 +  if (options->HTTPSProxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->HTTPSProxy,
 +                        &options->HTTPSProxyAddr, &options->HTTPSProxyPort) <0)
 +      REJECT("HTTPSProxy failed to parse or resolve. Please fix.");
 +    if (options->HTTPSProxyPort == 0) { /* give it a default */
 +      options->HTTPSProxyPort = 443;
      }
    }
  
 -  if (options->HttpProxyAuthenticator) {
 -    if (strlen(options->HttpProxyAuthenticator) >= 48)
 -      REJECT("HttpProxyAuthenticator is too long (>= 48 chars).");
 +  if (options->HTTPSProxyAuthenticator) {
 +    if (strlen(options->HTTPSProxyAuthenticator) >= 48)
 +      REJECT("HTTPSProxyAuthenticator is too long (>= 48 chars).");
    }
  
 -  if (options->HttpsProxy) { /* parse it now */
 -    if (parse_addr_port(LOG_WARN, options->HttpsProxy, NULL,
 -                        &options->HttpsProxyAddr, &options->HttpsProxyPort) <0)
 -      REJECT("HttpsProxy failed to parse or resolve. Please fix.");
 -    if (options->HttpsProxyPort == 0) { /* give it a default */
 -      options->HttpsProxyPort = 443;
 +  if (options->Socks4Proxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->Socks4Proxy,
 +                        &options->Socks4ProxyAddr,
 +                        &options->Socks4ProxyPort) <0)
 +      REJECT("Socks4Proxy failed to parse or resolve. Please fix.");
 +    if (options->Socks4ProxyPort == 0) { /* give it a default */
 +      options->Socks4ProxyPort = 1080;
      }
    }
  
 -  if (options->HttpsProxyAuthenticator) {
 -    if (strlen(options->HttpsProxyAuthenticator) >= 48)
 -      REJECT("HttpsProxyAuthenticator is too long (>= 48 chars).");
 +  if (options->Socks5Proxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->Socks5Proxy,
 +                            &options->Socks5ProxyAddr,
 +                            &options->Socks5ProxyPort) <0)
 +      REJECT("Socks5Proxy failed to parse or resolve. Please fix.");
 +    if (options->Socks5ProxyPort == 0) { /* give it a default */
 +      options->Socks5ProxyPort = 1080;
 +    }
    }
  
 +  if (options->Socks4Proxy && options->Socks5Proxy)
 +    REJECT("You cannot specify both Socks4Proxy and SOCKS5Proxy");
 +
 +  if (options->Socks5ProxyUsername) {
 +    size_t len;
 +
 +    len = strlen(options->Socks5ProxyUsername);
 +    if (len < 1 || len > 255)
 +      REJECT("Socks5ProxyUsername must be between 1 and 255 characters.");
 +
 +    if (!options->Socks5ProxyPassword)
 +      REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
 +
 +    len = strlen(options->Socks5ProxyPassword);
 +    if (len < 1 || len > 255)
 +      REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
 +  } else if (options->Socks5ProxyPassword)
 +    REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
 +
    if (options->HashedControlPassword) {
      smartlist_t *sl = decode_hashed_passwords(options->HashedControlPassword);
      if (!sl) {
@@@ -3521,13 -3510,6 +3521,13 @@@
               "upgrade your Tor controller as soon as possible.");
    }
  
 +  if (options->CookieAuthFileGroupReadable && !options->CookieAuthFile) {
 +    log_warn(LD_CONFIG, "You set the CookieAuthFileGroupReadable but did "
 +             "not configure a the path for the cookie file via "
 +             "CookieAuthFile. This means your cookie will not be group "
 +             "readable.");
 +  }
 +
    if (options->UseEntryGuards && ! options->NumEntryGuards)
      REJECT("Cannot enable UseEntryGuards with NumEntryGuards set to 0");
  
@@@ -3561,10 -3543,11 +3561,10 @@@
      if (options->ConstrainedSockSize < MIN_CONSTRAINED_TCP_BUFFER ||
          options->ConstrainedSockSize > MAX_CONSTRAINED_TCP_BUFFER ||
          options->ConstrainedSockSize % 1024) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "ConstrainedSockSize is invalid.  Must be a value between %d and %d "
            "in 1024 byte increments.",
            MIN_CONSTRAINED_TCP_BUFFER, MAX_CONSTRAINED_TCP_BUFFER);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      if (options->DirPort) {
@@@ -3612,12 -3595,6 +3612,12 @@@
    if (options->PreferTunneledDirConns && !options->TunnelDirConns)
      REJECT("Must set TunnelDirConns if PreferTunneledDirConns is set.");
  
 +  if ((options->Socks4Proxy || options->Socks5Proxy) &&
 +      !options->HTTPProxy && !options->PreferTunneledDirConns)
 +    REJECT("When Socks4Proxy or Socks5Proxy is configured, "
 +           "PreferTunneledDirConns and TunnelDirConns must both be "
 +           "set to 1, or HTTPProxy must be configured.");
 +
    if (options->AutomapHostsSuffixes) {
      SMARTLIST_FOREACH(options->AutomapHostsSuffixes, char *, suf,
      {
@@@ -3632,13 -3609,6 +3632,13 @@@
             "a non-default set of DirServers.");
    }
  
 +  if (options->AllowSingleHopExits && !options->DirServers) {
 +    COMPLAIN("You have set AllowSingleHopExits; now your relay will allow "
 +             "others to make one-hop exits. However, since by default most "
 +             "clients avoid relays that set this option, most clients will "
 +             "ignore you.");
 +  }
 +
    /*XXXX022 checking for defaults manually like this is a bit fragile.*/
  
    /* Keep changes to hard-coded values synchronous to man page and default
@@@ -3704,26 -3674,6 +3704,26 @@@
                          "testing Tor network!");
    }
  
 +  if (options->AccelName && !options->HardwareAccel)
 +    options->HardwareAccel = 1;
 +  if (options->AccelDir && !options->AccelName)
 +    REJECT("Can't use hardware crypto accelerator dir without engine name.");
 +
 +  if (options->PublishServerDescriptor)
 +    SMARTLIST_FOREACH(options->PublishServerDescriptor, const char *, pubdes, {
 +      if (!strcmp(pubdes, "1") || !strcmp(pubdes, "0"))
 +        if (smartlist_len(options->PublishServerDescriptor) > 1) {
 +          COMPLAIN("You have passed a list of multiple arguments to the "
 +                   "PublishServerDescriptor option that includes 0 or 1. "
 +                   "0 or 1 should only be used as the sole argument. "
 +                   "This configuration will be rejected in a future release.");
 +          break;
 +        }
 +    });
 +
 +  if (options->BridgeRelay == 1 && options->ORPort == 0)
 +      REJECT("BridgeRelay is 1, ORPort is 0. This is an invalid combination.");
 +
    return 0;
  #undef REJECT
  #undef COMPLAIN
@@@ -3762,10 -3712,12 +3762,10 @@@ options_transition_allowed(or_options_
    }
  
    if (strcmp(old->DataDirectory,new_val->DataDirectory)!=0) {
 -    char buf[1024];
 -    int r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
                 "While Tor is running, changing DataDirectory "
                 "(\"%s\"->\"%s\") is not allowed.",
                 old->DataDirectory, new_val->DataDirectory);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    }
  
@@@ -3774,22 -3726,19 +3774,22 @@@
      return -1;
    }
  
 -  if (!opt_streq(old->Group, new_val->Group)) {
 -    *msg = tor_strdup("While Tor is running, changing Group is not allowed.");
 +  if ((old->HardwareAccel != new_val->HardwareAccel)
 +      || !opt_streq(old->AccelName, new_val->AccelName)
 +      || !opt_streq(old->AccelDir, new_val->AccelDir)) {
 +    *msg = tor_strdup("While Tor is running, changing OpenSSL hardware "
 +                      "acceleration engine is not allowed.");
      return -1;
    }
  
 -  if (old->HardwareAccel != new_val->HardwareAccel) {
 -    *msg = tor_strdup("While Tor is running, changing HardwareAccel is "
 -                      "not allowed.");
 +  if (old->TestingTorNetwork != new_val->TestingTorNetwork) {
 +    *msg = tor_strdup("While Tor is running, changing TestingTorNetwork "
 +                      "is not allowed.");
      return -1;
    }
  
 -  if (old->TestingTorNetwork != new_val->TestingTorNetwork) {
 -    *msg = tor_strdup("While Tor is running, changing TestingTorNetwork "
 +  if (old->DisableAllSwap != new_val->DisableAllSwap) {
 +    *msg = tor_strdup("While Tor is running, changing DisableAllSwap "
                        "is not allowed.");
      return -1;
    }
@@@ -3804,7 -3753,7 +3804,7 @@@ options_transition_affects_workers(or_o
                                     or_options_t *new_options)
  {
    if (!opt_streq(old_options->DataDirectory, new_options->DataDirectory) ||
 -      old_options->NumCpus != new_options->NumCpus ||
 +      old_options->NumCPUs != new_options->NumCPUs ||
        old_options->ORPort != new_options->ORPort ||
        old_options->ServerDNSSearchDomains !=
                                         new_options->ServerDNSSearchDomains ||
@@@ -3836,6 -3785,7 +3836,6 @@@ options_transition_affects_descriptor(o
        old_options->ORPort != new_options->ORPort ||
        old_options->DirPort != new_options->DirPort ||
        old_options->ClientOnly != new_options->ClientOnly ||
 -      old_options->NoPublish != new_options->NoPublish ||
        old_options->_PublishServerDescriptor !=
          new_options->_PublishServerDescriptor ||
        get_effective_bwrate(old_options) != get_effective_bwrate(new_options) ||
@@@ -3858,7 -3808,6 +3858,7 @@@ get_windows_conf_root(void
  {
    static int is_set = 0;
    static char path[MAX_PATH+1];
 +  TCHAR tpath[MAX_PATH] = {0};
  
    LPITEMIDLIST idl;
    IMalloc *m;
@@@ -3876,7 -3825,7 +3876,7 @@@
  #define APPDATA_PATH CSIDL_APPDATA
  #endif
    if (!SUCCEEDED(SHGetSpecialFolderLocation(NULL, APPDATA_PATH, &idl))) {
 -    GetCurrentDirectory(MAX_PATH, path);
 +    getcwd(path,MAX_PATH);
      is_set = 1;
      log_warn(LD_CONFIG,
               "I couldn't find your application data folder: are you "
@@@ -3885,15 -3834,8 +3885,15 @@@
      return path;
    }
    /* Convert the path from an "ID List" (whatever that is!) to a path. */
 -  result = SHGetPathFromIDList(idl, path);
 -  /* Now we need to free the */
 +  result = SHGetPathFromIDList(idl, tpath);
 +#ifdef UNICODE
 +  wcstombs(path,tpath,MAX_PATH);
 +#else
 +  strlcpy(path,tpath,sizeof(path));
 +#endif
 +
 +  /* Now we need to free the memory that the path-idl was stored in.  In
 +   * typical Windows fashion, we can't just call 'free()' on it. */
    SHGetMalloc(&m);
    if (m) {
      m->lpVtbl->Free(m, idl);
@@@ -3941,7 -3883,10 +3941,7 @@@ check_nickname_list(const char *lst, co
    SMARTLIST_FOREACH(sl, const char *, s,
      {
        if (!is_legal_nickname_or_hexdigest(s)) {
 -        char buf[1024];
 -        int tmp = tor_snprintf(buf, sizeof(buf),
 -                  "Invalid nickname '%s' in %s line", s, name);
 -        *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
 +        tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
          r = -1;
          break;
        }
@@@ -3965,7 -3910,13 +3965,7 @@@ find_torrc_filename(int argc, char **ar
          log(LOG_WARN, LD_CONFIG, "Duplicate -f options on command line.");
          tor_free(fname);
        }
 -#ifdef MS_WINDOWS
 -      /* XXX one day we might want to extend expand_filename to work
 -       * under Windows as well. */
 -      fname = tor_strdup(argv[i+1]);
 -#else
        fname = expand_filename(argv[i+1]);
 -#endif
        *using_default_torrc = 0;
        ++i;
      } else if (!strcmp(argv[i],"--ignore-missing-torrc")) {
@@@ -4071,12 -4022,6 +4071,12 @@@ options_init_from_torrc(int argc, char 
      printf("Tor version %s.\n",get_version());
      exit(0);
    }
 +  if (argc > 1 && (!strcmp(argv[1],"--digests"))) {
 +    printf("Tor version %s.\n",get_version());
 +    printf("%s", libor_get_digests());
 +    printf("%s", tor_get_digests());
 +    exit(0);
 +  }
  
    /* Go through command-line variables */
    if (!global_cmdline_options) {
@@@ -4244,9 -4189,12 +4244,9 @@@ options_init_from_string(const char *cf
   err:
    config_free(&options_format, newoptions);
    if (*msg) {
 -    int len = (int)strlen(*msg)+256;
 -    char *newmsg = tor_malloc(len);
 -
 -    tor_snprintf(newmsg, len, "Failed to parse/validate config: %s", *msg);
 -    tor_free(*msg);
 -    *msg = newmsg;
 +    char *old_msg = *msg;
 +    tor_asprintf(msg, "Failed to parse/validate config: %s", old_msg);
 +    tor_free(old_msg);
    }
    return err;
  }
@@@ -4634,7 -4582,7 +4634,7 @@@ normalize_data_directory(or_options_t *
  }
  
  /** Check and normalize the value of options->DataDirectory; return 0 if it
 - * sane, -1 otherwise. */
 + * is sane, -1 otherwise. */
  static int
  validate_data_directory(or_options_t *options)
  {
@@@ -4666,6 -4614,7 +4666,6 @@@ write_configuration_file(const char *fn
  {
    char *old_val=NULL, *new_val=NULL, *new_conf=NULL;
    int rename_old = 0, r;
 -  size_t len;
  
    tor_assert(fname);
  
@@@ -4692,7 -4641,9 +4692,7 @@@
      goto err;
    }
  
 -  len = strlen(new_conf)+256;
 -  new_val = tor_malloc(len);
 -  tor_snprintf(new_val, len, "%s\n%s\n\n%s",
 +  tor_asprintf(&new_val, "%s\n%s\n\n%s",
                 GENERATED_FILE_PREFIX, GENERATED_FILE_COMMENT, new_conf);
  
    if (rename_old) {
@@@ -4742,12 -4693,15 +4742,12 @@@
  int
  options_save_current(void)
  {
 -  if (torrc_fname) {
 -    /* This fails if we can't write to our configuration file.
 -     *
 -     * If we try falling back to datadirectory or something, we have a better
 -     * chance of saving the configuration, but a better chance of doing
 -     * something the user never expected. Let's just warn instead. */
 -    return write_configuration_file(torrc_fname, get_options());
 -  }
 -  return write_configuration_file(get_default_conf_file(), get_options());
 +  /* This fails if we can't write to our configuration file.
 +   *
 +   * If we try falling back to datadirectory or something, we have a better
 +   * chance of saving the configuration, but a better chance of doing
 +   * something the user never expected. */
 +  return write_configuration_file(get_torrc_fname(), get_options());
  }
  
  /** Mapping from a unit name to a multiplier for converting that unit into a
@@@ -4812,47 -4766,30 +4812,47 @@@ static struct unit_table_t time_units[
  static uint64_t
  config_parse_units(const char *val, struct unit_table_t *u, int *ok)
  {
 -  uint64_t v;
 +  uint64_t v = 0;
 +  double d = 0;
 +  int use_float = 0;
    char *cp;
  
    tor_assert(ok);
  
    v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
 -  if (!*ok)
 -    return 0;
 +  if (!*ok || (cp && *cp == '.')) {
 +    d = tor_parse_double(val, 0, UINT64_MAX, ok, &cp);
 +    if (!*ok)
 +      goto done;
 +    use_float = 1;
 +  }
 +
    if (!cp) {
      *ok = 1;
 -    return v;
 +    v = use_float ? DBL_TO_U64(d) :  v;
 +    goto done;
    }
 -  while (TOR_ISSPACE(*cp))
 -    ++cp;
 +
 +  cp = (char*) eat_whitespace(cp);
 +
    for ( ;u->unit;++u) {
      if (!strcasecmp(u->unit, cp)) {
 -      v *= u->multiplier;
 +      if (use_float)
 +        v = u->multiplier * d;
 +      else
 +        v *= u->multiplier;
        *ok = 1;
 -      return v;
 +      goto done;
      }
    }
    log_warn(LD_CONFIG, "Unknown unit '%s'.", cp);
    *ok = 0;
 -  return 0;
 + done:
 +
 +  if (*ok)
 +    return v;
 +  else
 +    return 0;
  }
  
  /** Parse a string in the format "number unit", where unit is a unit of
@@@ -4862,8 -4799,7 +4862,8 @@@
  static uint64_t
  config_parse_memunit(const char *s, int *ok)
  {
 -  return config_parse_units(s, memory_units, ok);
 +  uint64_t u = config_parse_units(s, memory_units, ok);
 +  return u;
  }
  
  /** Parse a string in the format "number unit", where unit is a unit of time.
@@@ -4885,37 -4821,256 +4885,37 @@@ config_parse_interval(const char *s, in
    return (int)r;
  }
  
 -/* This is what passes for version detection on OSX.  We set
 - * MACOSX_KQUEUE_IS_BROKEN to true iff we're on a version of OSX before
 - * 10.4.0 (aka 1040). */
 -#ifdef __APPLE__
 -#ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
 -#define MACOSX_KQUEUE_IS_BROKEN \
 -  (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1040)
 -#else
 -#define MACOSX_KQUEUE_IS_BROKEN 0
 -#endif
 -#endif
 -
  /**
   * Initialize the libevent library.
   */
  static void
  init_libevent(void)
  {
 +  const char *badness=NULL;
 +
    configure_libevent_logging();
    /* If the kernel complains that some method (say, epoll) doesn't
     * exist, we don't care about it, since libevent will cope.
     */
    suppress_libevent_log_msg("Function not implemented");
 -#ifdef __APPLE__
 -  if (MACOSX_KQUEUE_IS_BROKEN ||
 -      decode_libevent_version(event_get_version(), NULL) < LE_11B) {
 -    setenv("EVENT_NOKQUEUE","1",1);
 -  }
 -#endif
  
 -  /* In libevent versions before 2.0, it's hard to keep binary compatibility
 -   * between upgrades, and unpleasant to detect when the version we compiled
 -   * against is unlike the version we have linked against. Here's how. */
 -#if defined(_EVENT_VERSION) && defined(HAVE_EVENT_GET_VERSION)
 -  /* We have a header-file version and a function-call version. Easy. */
 -  if (strcmp(_EVENT_VERSION, event_get_version())) {
 -    int compat1 = -1, compat2 = -1;
 -    int verybad, prettybad ;
 -    decode_libevent_version(_EVENT_VERSION, &compat1);
 -    decode_libevent_version(event_get_version(), &compat2);
 -    verybad = compat1 != compat2;
 -    prettybad = (compat1 == -1 || compat2 == -1) && compat1 != compat2;
 -
 -    log(verybad ? LOG_WARN : (prettybad ? LOG_NOTICE : LOG_INFO),
 -        LD_GENERAL, "We were compiled with headers from version %s "
 -        "of Libevent, but we're using a Libevent library that says it's "
 -        "version %s.", _EVENT_VERSION, event_get_version());
 -    if (verybad)
 -      log_warn(LD_GENERAL, "This will almost certainly make Tor crash.");
 -    else if (prettybad)
 -      log_notice(LD_GENERAL, "If Tor crashes, this might be why.");
 -    else
 -      log_info(LD_GENERAL, "I think these versions are binary-compatible.");
 -  }
 -#elif defined(HAVE_EVENT_GET_VERSION)
 -  /* event_get_version but no _EVENT_VERSION.  We might be in 1.4.0-beta or
 -     earlier, where that's normal.  To see whether we were compiled with an
 -     earlier version, let's see whether the struct event defines MIN_HEAP_IDX.
 -  */
 -#ifdef HAVE_STRUCT_EVENT_MIN_HEAP_IDX
 -  /* The header files are 1.4.0-beta or later. If the version is not
 -   * 1.4.0-beta, we are incompatible. */
 -  {
 -    if (strcmp(event_get_version(), "1.4.0-beta")) {
 -      log_warn(LD_GENERAL, "It's a little hard to tell, but you seem to have "
 -               "Libevent 1.4.0-beta header files, whereas you have linked "
 -               "against Libevent %s.  This will probably make Tor crash.",
 -               event_get_version());
 -    }
 -  }
 -#else
 -  /* Our headers are 1.3e or earlier. If the library version is not 1.4.x or
 -     later, we're probably fine. */
 -  {
 -    const char *v = event_get_version();
 -    if ((v[0] == '1' && v[2] == '.' && v[3] > '3') || v[0] > '1') {
 -      log_warn(LD_GENERAL, "It's a little hard to tell, but you seem to have "
 -               "Libevent header file from 1.3e or earlier, whereas you have "
 -               "linked against Libevent %s.  This will probably make Tor "
 -               "crash.", event_get_version());
 -    }
 -  }
 -#endif
 +  tor_check_libevent_header_compatibility();
  
 -#elif defined(_EVENT_VERSION)
 -#warn "_EVENT_VERSION is defined but not get_event_version(): Libevent is odd."
 -#else
 -  /* Your libevent is ancient. */
 -#endif
 +  tor_libevent_initialize();
  
 -  event_init();
    suppress_libevent_log_msg(NULL);
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -  /* Making this a NOTICE for now so we can link bugs to a libevent versions
 -   * or methods better. */
 -  log(LOG_NOTICE, LD_GENERAL,
 -      "Initialized libevent version %s using method %s. Good.",
 -      event_get_version(), event_get_method());
 -  check_libevent_version(event_get_method(), get_options()->ORPort != 0);
 -#else
 -  log(LOG_NOTICE, LD_GENERAL,
 -      "Initialized old libevent (version 1.0b or earlier).");
 -  log(LOG_WARN, LD_GENERAL,
 -      "You have a *VERY* old version of libevent.  It is likely to be buggy; "
 -      "please build Tor with a more recent version.");
 -#endif
 -}
 -
 -/** Table mapping return value of event_get_version() to le_version_t. */
 -static const struct {
 -  const char *name; le_version_t version; int bincompat;
 -} le_version_table[] = {
 -  /* earlier versions don't have get_version. */
 -  { "1.0c", LE_10C, 1},
 -  { "1.0d", LE_10D, 1},
 -  { "1.0e", LE_10E, 1},
 -  { "1.1",  LE_11,  1 },
 -  { "1.1a", LE_11A, 1 },
 -  { "1.1b", LE_11B, 1 },
 -  { "1.2",  LE_12,  1 },
 -  { "1.2a", LE_12A, 1 },
 -  { "1.3",  LE_13,  1 },
 -  { "1.3a", LE_13A, 1 },
 -  { "1.3b", LE_13B, 1 },
 -  { "1.3c", LE_13C, 1 },
 -  { "1.3d", LE_13D, 1 },
 -  { "1.3e", LE_13E, 1 },
 -  { "1.4.0-beta", LE_140, 2 },
 -  { "1.4.1-beta", LE_141, 2 },
 -  { "1.4.2-rc",   LE_142, 2 },
 -  { "1.4.3-stable", LE_143, 2 },
 -  { "1.4.4-stable", LE_144, 2 },
 -  { "1.4.5-stable", LE_145, 2 },
 -  { "1.4.6-stable", LE_146, 2 },
 -  { "1.4.7-stable", LE_147, 2 },
 -  { "1.4.8-stable", LE_148, 2 },
 -  { "1.4.99-trunk", LE_1499, 3 },
 -  { NULL, LE_OTHER, 0 }
 -};
  
 -/** Return the le_version_t for the current version of libevent.  If the
 - * version is very new, return LE_OTHER.  If the version is so old that it
 - * doesn't support event_get_version(), return LE_OLD. */
 -static le_version_t
 -decode_libevent_version(const char *v, int *bincompat_out)
 -{
 -  int i;
 -  for (i=0; le_version_table[i].name; ++i) {
 -    if (!strcmp(le_version_table[i].name, v)) {
 -      if (bincompat_out)
 -        *bincompat_out = le_version_table[i].bincompat;
 -      return le_version_table[i].version;
 -    }
 -  }
 -  if (v[0] != '1' && bincompat_out)
 -    *bincompat_out = 100;
 -  else if (!strcmpstart(v, "1.4") && bincompat_out)
 -    *bincompat_out = 2;
 -  return LE_OTHER;
 -}
 -
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -/**
 - * Compare the given libevent method and version to a list of versions
 - * which are known not to work.  Warn the user as appropriate.
 - */
 -static void
 -check_libevent_version(const char *m, int server)
 -{
 -  int buggy = 0, iffy = 0, slow = 0, thread_unsafe = 0;
 -  le_version_t version;
 -  const char *v = event_get_version();
 -  const char *badness = NULL;
 -  const char *sad_os = "";
 -
 -  version = decode_libevent_version(v, NULL);
 -
 -  /* XXX Would it be worthwhile disabling the methods that we know
 -   * are buggy, rather than just warning about them and then proceeding
 -   * to use them? If so, we should probably not wrap this whole thing
 -   * in HAVE_EVENT_GET_VERSION and HAVE_EVENT_GET_METHOD. -RD */
 -  /* XXXX The problem is that it's not trivial to get libevent to change it's
 -   * method once it's initialized, and it's not trivial to tell what method it
 -   * will use without initializing it.  I guess we could preemptively disable
 -   * buggy libevent modes based on the version _before_ initializing it,
 -   * though, but then there's no good way (afaict) to warn "I would have used
 -   * kqueue, but instead I'm using select." -NM */
 -  if (!strcmp(m, "kqueue")) {
 -    if (version < LE_11B)
 -      buggy = 1;
 -  } else if (!strcmp(m, "epoll")) {
 -    if (version < LE_11)
 -      iffy = 1;
 -  } else if (!strcmp(m, "poll")) {
 -    if (version < LE_10E)
 -      buggy = 1;
 -    else if (version < LE_11)
 -      slow = 1;
 -  } else if (!strcmp(m, "select")) {
 -    if (version < LE_11)
 -      slow = 1;
 -  } else if (!strcmp(m, "win32")) {
 -    if (version < LE_11B)
 -      buggy = 1;
 -  }
 -
 -  /* Libevent versions before 1.3b do very badly on operating systems with
 -   * user-space threading implementations. */
 -#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
 -  if (server && version < LE_13B) {
 -    thread_unsafe = 1;
 -    sad_os = "BSD variants";
 -  }
 -#elif defined(__APPLE__) || defined(__darwin__)
 -  if (server && version < LE_13B) {
 -    thread_unsafe = 1;
 -    sad_os = "Mac OS X";
 -  }
 -#endif
 -
 -  if (thread_unsafe) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "Libevent version %s often crashes when running a Tor server with %s. "
 -        "Please use the latest version of libevent (1.3b or later)",v,sad_os);
 -    badness = "BROKEN";
 -  } else if (buggy) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "There are serious bugs in using %s with libevent %s. "
 -        "Please use the latest version of libevent.", m, v);
 -    badness = "BROKEN";
 -  } else if (iffy) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "There are minor bugs in using %s with libevent %s. "
 -        "You may want to use the latest version of libevent.", m, v);
 -    badness = "BUGGY";
 -  } else if (slow && server) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "libevent %s can be very slow with %s. "
 -        "When running a server, please use the latest version of libevent.",
 -        v,m);
 -    badness = "SLOW";
 -  }
 +  tor_check_libevent_version(tor_libevent_get_method(),
 +                             get_options()->ORPort != 0,
 +                             &badness);
    if (badness) {
 +    const char *v = tor_libevent_get_version_str();
 +    const char *m = tor_libevent_get_method();
      control_event_general_status(LOG_WARN,
          "BAD_LIBEVENT VERSION=%s METHOD=%s BADNESS=%s RECOVERED=NO",
                                   v, m, badness);
    }
 -
  }
 -#endif
  
  /** Return the persistent state struct for this Tor. */
  or_state_t *
@@@ -4997,61 -5152,22 +4997,61 @@@ or_state_validate(or_state_t *old_state
  }
  
  /** Replace the current persistent state with <b>new_state</b> */
 -static void
 +static int
  or_state_set(or_state_t *new_state)
  {
    char *err = NULL;
 +  int ret = 0;
    tor_assert(new_state);
 -  if (global_state)
 -    config_free(&state_format, global_state);
 +  config_free(&state_format, global_state);
    global_state = new_state;
    if (entry_guards_parse_state(global_state, 1, &err)<0) {
      log_warn(LD_GENERAL,"%s",err);
      tor_free(err);
 +    ret = -1;
    }
    if (rep_hist_load_state(global_state, &err)<0) {
      log_warn(LD_GENERAL,"Unparseable bandwidth history state: %s",err);
      tor_free(err);
 +    ret = -1;
 +  }
 +  if (circuit_build_times_parse_state(&circ_times, global_state) < 0) {
 +    ret = -1;
 +  }
 +  return ret;
 +}
 +
 +/**
 + * Save a broken state file to a backup location.
 + */
 +static void
 +or_state_save_broken(char *fname)
 +{
 +  int i;
 +  file_status_t status;
 +  size_t len = strlen(fname)+16;
 +  char *fname2 = tor_malloc(len);
 +  for (i = 0; i < 100; ++i) {
 +    tor_snprintf(fname2, len, "%s.%d", fname, i);
 +    status = file_status(fname2);
 +    if (status == FN_NOENT)
 +      break;
 +  }
 +  if (i == 100) {
 +    log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad "
 +             "state files to move aside. Discarding the old state file.",
 +             fname);
 +    unlink(fname);
 +  } else {
 +    log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside "
 +             "to \"%s\".  This could be a bug in Tor; please tell "
 +             "the developers.", fname, fname2);
 +    if (rename(fname, fname2) < 0) {
 +      log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The "
 +               "OS gave an error of %s", strerror(errno));
 +    }
    }
 +  tor_free(fname2);
  }
  
  /** Reload the persistent state from disk, generating a new state as needed.
@@@ -5113,8 -5229,31 +5113,8 @@@ or_state_load(void
               " This is a bug in Tor.");
      goto done;
    } else if (badstate && contents) {
 -    int i;
 -    file_status_t status;
 -    size_t len = strlen(fname)+16;
 -    char *fname2 = tor_malloc(len);
 -    for (i = 0; i < 100; ++i) {
 -      tor_snprintf(fname2, len, "%s.%d", fname, i);
 -      status = file_status(fname2);
 -      if (status == FN_NOENT)
 -        break;
 -    }
 -    if (i == 100) {
 -      log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad "
 -               "state files to move aside. Discarding the old state file.",
 -               fname);
 -      unlink(fname);
 -    } else {
 -      log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside "
 -               "to \"%s\".  This could be a bug in Tor; please tell "
 -               "the developers.", fname, fname2);
 -      if (rename(fname, fname2) < 0) {
 -        log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The "
 -                 "OS gave an error of %s", strerror(errno));
 -      }
 -    }
 -    tor_free(fname2);
 +    or_state_save_broken(fname);
 +
      tor_free(contents);
      config_free(&state_format, new_state);
  
@@@ -5126,9 -5265,7 +5126,9 @@@
    } else {
      log_info(LD_GENERAL, "Initialized state");
    }
 -  or_state_set(new_state);
 +  if (or_state_set(new_state) == -1) {
 +    or_state_save_broken(fname);
 +  }
    new_state = NULL;
    if (!contents) {
      global_state->next_write = 0;
@@@ -5145,15 -5282,13 +5145,15 @@@
    return r;
  }
  
 +/** If writing the state to disk fails, try again after this many seconds. */
 +#define STATE_WRITE_RETRY_INTERVAL 3600
 +
  /** Write the persistent state to disk. Return 0 for success, <0 on failure. */
  int
  or_state_save(time_t now)
  {
    char *state, *contents;
    char tbuf[ISO_TIME_LEN+1];
 -  size_t len;
    char *fname;
  
    tor_assert(global_state);
@@@ -5165,16 -5300,20 +5165,16 @@@
     * to avoid redundant writes. */
    entry_guards_update_state(global_state);
    rep_hist_update_state(global_state);
 +  circuit_build_times_update_state(&circ_times, global_state);
    if (accounting_is_enabled(get_options()))
      accounting_run_housekeeping(now);
  
 -  global_state->LastWritten = time(NULL);
    tor_free(global_state->TorVersion);
 -  len = strlen(get_version())+8;
 -  global_state->TorVersion = tor_malloc(len);
 -  tor_snprintf(global_state->TorVersion, len, "Tor %s", get_version());
 +  tor_asprintf(&global_state->TorVersion, "Tor %s", get_version());
  
    state = config_dump(&state_format, global_state, 1, 0);
 -  len = strlen(state)+256;
 -  contents = tor_malloc(len);
    format_local_iso_time(tbuf, time(NULL));
 -  tor_snprintf(contents, len,
 +  tor_asprintf(&contents,
                 "# Tor state file last generated on %s local time\n"
                 "# Other times below are in GMT\n"
                 "# You *do not* need to edit this file.\n\n%s",
@@@ -5182,18 -5321,12 +5182,18 @@@
    tor_free(state);
    fname = get_datadir_fname("state");
    if (write_str_to_file(fname, contents, 0)<0) {
-     log_warn(LD_FS, "Unable to write state to file \"%s\"; will try later",
-              fname);
+     log_warn(LD_FS, "Unable to write state to file \"%s\"; "
+              "will try again later", fname);
 +    global_state->LastWritten = -1;
      tor_free(fname);
      tor_free(contents);
 +    /* Try again after STATE_WRITE_RETRY_INTERVAL (or sooner, if the state
 +     * changes sooner). */
 +    global_state->next_write = now + STATE_WRITE_RETRY_INTERVAL;
      return -1;
    }
 +
 +  global_state->LastWritten = time(NULL);
    log_info(LD_GENERAL, "Saved state to \"%s\"", fname);
    tor_free(fname);
    tor_free(contents);
@@@ -5225,18 -5358,18 +5225,18 @@@ remove_file_if_very_old(const char *fna
   * types. */
  int
  getinfo_helper_config(control_connection_t *conn,
 -                      const char *question, char **answer)
 +                      const char *question, char **answer,
 +                      const char **errmsg)
  {
    (void) conn;
 +  (void) errmsg;
    if (!strcmp(question, "config/names")) {
      smartlist_t *sl = smartlist_create();
      int i;
      for (i = 0; _option_vars[i].name; ++i) {
        config_var_t *var = &_option_vars[i];
 -      const char *type, *desc;
 +      const char *type;
        char *line;
 -      size_t len;
 -      desc = config_find_description(&options_format, var->name);
        switch (var->type) {
          case CONFIG_TYPE_STRING: type = "String"; break;
          case CONFIG_TYPE_FILENAME: type = "Filename"; break;
@@@ -5257,7 -5390,14 +5257,7 @@@
        }
        if (!type)
          continue;
 -      len = strlen(var->name)+strlen(type)+16;
 -      if (desc)
 -        len += strlen(desc);
 -      line = tor_malloc(len);
 -      if (desc)
 -        tor_snprintf(line, len, "%s %s %s\n",var->name,type,desc);
 -      else
 -        tor_snprintf(line, len, "%s %s\n",var->name,type);
 +      tor_asprintf(&line, "%s %s\n",var->name,type);
        smartlist_add(sl, line);
      }
      *answer = smartlist_join_strings(sl, "", 0, NULL);
diff --combined src/or/dnsserv.c
index c491656,57c4493..d5faffb
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@@ -9,21 -9,7 +9,21 @@@
   **/
  
  #include "or.h"
 +#include "dnsserv.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "main.h"
 +#include "policies.h"
 +#ifdef HAVE_EVENT2_DNS_H
 +#include <event2/dns.h>
 +#include <event2/dns_compat.h>
 +/* XXXX022 this implies we want an improved evdns  */
 +#include <event2/dns_struct.h>
 +#else
  #include "eventdns.h"
 +#endif
  
  /** Helper function: called by evdns whenever the client sends a request to our
   * DNSPort.  We need to eventually answer the request <b>req</b>.
@@@ -99,7 -85,12 +99,7 @@@ evdns_server_callback(struct evdns_serv
      evdns_server_request_respond(req, DNS_ERR_NONE);
      return;
    }
 -  if (q->type == EVDNS_TYPE_A) {
 -    /* Refuse any attempt to resolve a noconnect address, right now. */
 -    if (hostname_is_noconnect_address(q->name)) {
 -      err = DNS_ERR_REFUSED;
 -    }
 -  } else {
 +  if (q->type != EVDNS_TYPE_A) {
      tor_assert(q->type == EVDNS_TYPE_PTR);
    }
  
@@@ -141,18 -132,17 +141,18 @@@
  
    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
  
 -  /* Now, throw the connection over to get rewritten (which will answer it
 -  * immediately if it's in the cache, or completely bogus, or automapped),
 -  * and then attached to a circuit. */
 +  /* Now, unless a controller asked us to leave streams unattached,
 +  * throw the connection over to get rewritten (which will
 +  * answer it immediately if it's in the cache, or completely bogus, or
 +  * automapped), and then attached to a circuit. */
    log_info(LD_APP, "Passing request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q->name));
 +           escaped_safe_str_client(q->name));
    q_name = tor_strdup(q->name); /* q could be freed in rewrite_and_attach */
 -  connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
    /* Now, the connection is marked if it was bad. */
  
 -  log_info(LD_APP, "Passed request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q_name));
 +  log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.",
 +           escaped_safe_str_client(q_name));
    tor_free(q_name);
  }
  
@@@ -187,18 -177,17 +187,18 @@@ dnsserv_launch_request(const char *name
      return -1;
    }
  
 -  /* Now, throw the connection over to get rewritten (which will answer it
 -  * immediately if it's in the cache, or completely bogus, or automapped),
 -  * and then attached to a circuit. */
 +  /* Now, unless a controller asked us to leave streams unattached,
 +  * throw the connection over to get rewritten (which will
 +  * answer it immediately if it's in the cache, or completely bogus, or
 +  * automapped), and then attached to a circuit. */
    log_info(LD_APP, "Passing request for %s to rewrite_and_attach.",
 -           escaped_safe_str(name));
 +           escaped_safe_str_client(name));
    q_name = tor_strdup(name); /* q could be freed in rewrite_and_attach */
 -  connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
    /* Now, the connection is marked if it was bad. */
  
 -  log_info(LD_APP, "Passed request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q_name));
 +  log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.",
 +           escaped_safe_str_client(q_name));
    tor_free(q_name);
    return 0;
  }
@@@ -286,7 -275,7 +286,7 @@@ dnsserv_resolved(edge_connection_t *con
      char *ans = tor_strndup(answer, answer_len);
      evdns_server_request_add_ptr_reply(req, NULL,
                                         name,
-                                        (char*)answer, ttl);
+                                        ans, ttl);
      tor_free(ans);
    } else if (answer_type == RESOLVED_TYPE_ERROR) {
      err = DNS_ERR_NOTEXIST;
@@@ -308,8 -297,8 +308,8 @@@ dnsserv_configure_listener(connection_
    tor_assert(conn->s >= 0);
    tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER);
  
 -  conn->dns_server_port = evdns_add_server_port(conn->s, 0,
 -                                                evdns_server_callback, NULL);
 +  conn->dns_server_port =
 +    tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL);
  }
  
  /** Free the evdns server port for <b>conn</b>, which must be an
diff --combined src/or/networkstatus.c
index 94bcb41,7106294..dfc3a45
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@@ -11,20 -11,6 +11,20 @@@
   */
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "relay.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /* For tracking v2 networkstatus documents.  Only caches do this now. */
  
@@@ -49,22 -35,16 +49,22 @@@ static networkstatus_t *current_consens
  
  /** A v3 consensus networkstatus that we've received, but which we don't
   * have enough certificates to be happy about. */
 -static networkstatus_t *consensus_waiting_for_certs = NULL;
 -/** The encoded version of consensus_waiting_for_certs. */
 -static char *consensus_waiting_for_certs_body = NULL;
 -/** When did we set the current value of consensus_waiting_for_certs?  If this
 - * is too recent, we shouldn't try to fetch a new consensus for a little while,
 - * to give ourselves time to get certificates for this one. */
 -static time_t consensus_waiting_for_certs_set_at = 0;
 -/** Set to 1 if we've been holding on to consensus_waiting_for_certs so long
 - * that we should treat it as maybe being bad. */
 -static int consensus_waiting_for_certs_dl_failed = 0;
 +typedef struct consensus_waiting_for_certs_t {
 +  /** The consensus itself. */
 +  networkstatus_t *consensus;
 +  /** The encoded version of the consensus, nul-terminated. */
 +  char *body;
 +  /** When did we set the current value of consensus_waiting_for_certs?  If
 +   * this is too recent, we shouldn't try to fetch a new consensus for a
 +   * little while, to give ourselves time to get certificates for this one. */
 +  time_t set_at;
 +  /** Set to 1 if we've been holding on to it for so long we should maybe
 +   * treat it as being bad. */
 +  int dl_failed;
 +} consensus_waiting_for_certs_t;
 +
 +static consensus_waiting_for_certs_t
 +       consensus_waiting_for_certs[N_CONSENSUS_FLAVORS];
  
  /** The last time we tried to download a networkstatus, or 0 for "never".  We
   * use this to rate-limit download attempts for directory caches (including
@@@ -76,7 -56,7 +76,7 @@@ static time_t last_networkstatus_downlo
   * before the current consensus becomes invalid. */
  static time_t time_to_download_next_consensus = 0;
  /** Download status for the current consensus networkstatus. */
 -static download_status_t consensus_dl_status = { 0, 0, DL_SCHED_CONSENSUS };
 +static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS];
  
  /** True iff we have logged a warning about this OR's version being older than
   * listed by the authorities. */
@@@ -109,7 -89,6 +109,7 @@@ networkstatus_reset_warnings(void
  void
  networkstatus_reset_download_failures(void)
  {
 +  int i;
    const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list();
    SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
       SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs,
@@@ -118,8 -97,7 +118,8 @@@
             rs->need_to_mirror = 1;
         }));;
  
 -  download_status_reset(&consensus_dl_status);
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i)
 +    download_status_reset(&consensus_dl_status[i]);
    if (v2_download_status_map) {
      digestmap_iter_t *iter;
      digestmap_t *map = v2_download_status_map;
@@@ -192,7 -170,7 +192,7 @@@ router_reload_v2_networkstatus(void
    return 0;
  }
  
 -/** Read the cached v3 consensus networkstatus from the disk. */
 +/** Read every cached v3 consensus networkstatus from the disk. */
  int
  router_reload_consensus_networkstatus(void)
  {
@@@ -201,46 -179,31 +201,46 @@@
    struct stat st;
    or_options_t *options = get_options();
    const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS;
 +  int flav;
  
    /* FFFF Suppress warnings if cached consensus is bad? */
 +  for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
 +    char buf[128];
 +    const char *flavor = networkstatus_get_flavor_name(flav);
 +    if (flav == FLAV_NS) {
 +      filename = get_datadir_fname("cached-consensus");
 +    } else {
 +      tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor);
 +      filename = get_datadir_fname(buf);
 +    }
 +    s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 +    if (s) {
 +      if (networkstatus_set_current_consensus(s, flavor, flags) < -1) {
 +        log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
 +                 flavor, filename);
 +      }
 +      tor_free(s);
 +    }
 +    tor_free(filename);
  
 -  filename = get_datadir_fname("cached-consensus");
 -  s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 -  if (s) {
 -    if (networkstatus_set_current_consensus(s, flags) < -1) {
 -      log_warn(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
 -               filename);
 +    if (flav == FLAV_NS) {
 +      filename = get_datadir_fname("unverified-consensus");
 +    } else {
 +      tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor);
 +      filename = get_datadir_fname(buf);
      }
 -    tor_free(s);
 -  }
 -  tor_free(filename);
  
 -  filename = get_datadir_fname("unverified-consensus");
 -  s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 -  if (s) {
 -    if (networkstatus_set_current_consensus(s,
 +    s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 +    if (s) {
 +      if (networkstatus_set_current_consensus(s, flavor,
                                       flags|NSSET_WAS_WAITING_FOR_CERTS)) {
 -      log_info(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
 -               filename);
 +      log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
 +               flavor, filename);
      }
 -    tor_free(s);
 +      tor_free(s);
 +    }
 +    tor_free(filename);
    }
 -  tor_free(filename);
  
    if (!current_consensus ||
        (stat(options->FallbackNetworkstatusFile, &st)==0 &&
@@@ -248,7 -211,7 +248,7 @@@
      s = read_file_to_str(options->FallbackNetworkstatusFile,
                           RFTS_IGNORE_MISSING, NULL);
      if (s) {
 -      if (networkstatus_set_current_consensus(s,
 +      if (networkstatus_set_current_consensus(s, "ns",
                                                flags|NSSET_ACCEPT_OBSOLETE)) {
          log_info(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
                   options->FallbackNetworkstatusFile);
@@@ -279,16 -242,8 +279,16 @@@
  static void
  vote_routerstatus_free(vote_routerstatus_t *rs)
  {
 +  vote_microdesc_hash_t *h, *next;
 +  if (!rs)
 +    return;
    tor_free(rs->version);
    tor_free(rs->status.exitsummary);
 +  for (h = rs->microdesc; h; h = next) {
 +    tor_free(h->microdesc_hash_line);
 +    next = h->next;
 +    tor_free(h);
 +  }
    tor_free(rs);
  }
  
@@@ -296,8 -251,6 +296,8 @@@
  void
  routerstatus_free(routerstatus_t *rs)
  {
 +  if (!rs)
 +    return;
    tor_free(rs->exitsummary);
    tor_free(rs);
  }
@@@ -306,8 -259,6 +306,8 @@@
  void
  networkstatus_v2_free(networkstatus_v2_t *ns)
  {
 +  if (!ns)
 +    return;
    tor_free(ns->source_address);
    tor_free(ns->contact);
    if (ns->signing_key)
@@@ -322,25 -273,7 +322,25 @@@
    tor_free(ns);
  }
  
 -/** Clear all storage held in <b>ns</b>. */
 +/** Free all storage held in <b>sig</b> */
 +void
 +document_signature_free(document_signature_t *sig)
 +{
 +  tor_free(sig->signature);
 +  tor_free(sig);
 +}
 +
 +/** Return a newly allocated copy of <b>sig</b> */
 +document_signature_t *
 +document_signature_dup(const document_signature_t *sig)
 +{
 +  document_signature_t *r = tor_memdup(sig, sizeof(document_signature_t));
 +  if (r->signature)
 +    r->signature = tor_memdup(sig->signature, sig->signature_len);
 +  return r;
 +}
 +
 +/** Free all storage held in <b>ns</b>. */
  void
  networkstatus_vote_free(networkstatus_t *ns)
  {
@@@ -353,10 -286,6 +353,10 @@@
      SMARTLIST_FOREACH(ns->known_flags, char *, c, tor_free(c));
      smartlist_free(ns->known_flags);
    }
 +  if (ns->weight_params) {
 +    SMARTLIST_FOREACH(ns->weight_params, char *, c, tor_free(c));
 +    smartlist_free(ns->weight_params);
 +  }
    if (ns->net_params) {
      SMARTLIST_FOREACH(ns->net_params, char *, c, tor_free(c));
      smartlist_free(ns->net_params);
@@@ -366,20 -295,18 +366,20 @@@
      smartlist_free(ns->supported_methods);
    }
    if (ns->voters) {
 -    SMARTLIST_FOREACH(ns->voters, networkstatus_voter_info_t *, voter,
 -    {
 +    SMARTLIST_FOREACH_BEGIN(ns->voters, networkstatus_voter_info_t *, voter) {
        tor_free(voter->nickname);
        tor_free(voter->address);
        tor_free(voter->contact);
 -      tor_free(voter->signature);
 +      if (voter->sigs) {
 +        SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +                          document_signature_free(sig));
 +        smartlist_free(voter->sigs);
 +      }
        tor_free(voter);
 -    });
 +    } SMARTLIST_FOREACH_END(voter);
      smartlist_free(ns->voters);
    }
 -  if (ns->cert)
 -    authority_cert_free(ns->cert);
 +  authority_cert_free(ns->cert);
  
    if (ns->routerstatus_list) {
      if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) {
@@@ -392,8 -319,8 +392,8 @@@
  
      smartlist_free(ns->routerstatus_list);
    }
 -  if (ns->desc_digest_map)
 -    digestmap_free(ns->desc_digest_map, NULL);
 +
 +  digestmap_free(ns->desc_digest_map, NULL);
  
    memset(ns, 11, sizeof(*ns));
    tor_free(ns);
@@@ -414,38 -341,35 +414,39 @@@ networkstatus_get_voter_by_id(networkst
    return NULL;
  }
  
 -/** Check whether the signature on <b>voter</b> is correctly signed by
 - * the signing key of <b>cert</b>. Return -1 if <b>cert</b> doesn't match the
 +/** Check whether the signature <b>sig</b> is correctly signed with the
 + * signing key in <b>cert</b>.  Return -1 if <b>cert</b> doesn't match the
   * signing key; otherwise set the good_signature or bad_signature flag on
   * <b>voter</b>, and return 0. */
 -/* (private; exposed for testing.) */
  int
 -networkstatus_check_voter_signature(networkstatus_t *consensus,
 -                                    networkstatus_voter_info_t *voter,
 -                                    authority_cert_t *cert)
 +networkstatus_check_document_signature(const networkstatus_t *consensus,
 +                                       document_signature_t *sig,
 +                                       const authority_cert_t *cert)
  {
 -  char d[DIGEST_LEN];
 +  char key_digest[DIGEST_LEN];
 +  const int dlen = sig->alg == DIGEST_SHA1 ? DIGEST_LEN : DIGEST256_LEN;
    char *signed_digest;
    size_t signed_digest_len;
 -  if (crypto_pk_get_digest(cert->signing_key, d)<0)
 +
 +  if (crypto_pk_get_digest(cert->signing_key, key_digest)<0)
      return -1;
 -  if (memcmp(voter->signing_key_digest, d, DIGEST_LEN))
 +  if (memcmp(sig->signing_key_digest, key_digest, DIGEST_LEN) ||
 +      memcmp(sig->identity_digest, cert->cache_info.identity_digest,
 +             DIGEST_LEN))
      return -1;
 +
    signed_digest_len = crypto_pk_keysize(cert->signing_key);
    signed_digest = tor_malloc(signed_digest_len);
    if (crypto_pk_public_checksig(cert->signing_key,
                                  signed_digest,
+                                 signed_digest_len,
 -                                voter->signature,
 -                                voter->signature_len) != DIGEST_LEN ||
 -      memcmp(signed_digest, consensus->networkstatus_digest, DIGEST_LEN)) {
 +                                sig->signature,
 +                                sig->signature_len) < dlen ||
 +      memcmp(signed_digest, consensus->digests.d[sig->alg], dlen)) {
      log_warn(LD_DIR, "Got a bad signature on a networkstatus vote");
 -    voter->bad_signature = 1;
 +    sig->bad_signature = 1;
    } else {
 -    voter->good_signature = 1;
 +    sig->good_signature = 1;
    }
    tor_free(signed_digest);
    return 0;
@@@ -464,7 -388,7 +465,7 @@@ networkstatus_check_consensus_signature
                                          int warn)
  {
    int n_good = 0;
 -  int n_missing_key = 0;
 +  int n_missing_key = 0, n_dl_failed_key = 0;
    int n_bad = 0;
    int n_unknown = 0;
    int n_no_signature = 0;
@@@ -478,62 -402,37 +479,62 @@@
  
    tor_assert(consensus->type == NS_TYPE_CONSENSUS);
  
 -  SMARTLIST_FOREACH(consensus->voters, networkstatus_voter_info_t *, voter,
 -  {
 -    if (!voter->good_signature && !voter->bad_signature && voter->signature) {
 -      /* we can try to check the signature. */
 -      int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
 -                                          voter->identity_digest) != NULL;
 -      authority_cert_t *cert =
 -        authority_cert_get_by_digests(voter->identity_digest,
 -                                      voter->signing_key_digest);
 -      if (!is_v3_auth) {
 -        smartlist_add(unrecognized, voter);
 -        ++n_unknown;
 -        continue;
 -      } else if (!cert || cert->expires < now) {
 -        smartlist_add(need_certs_from, voter);
 -        ++n_missing_key;
 -        continue;
 -      }
 -      if (networkstatus_check_voter_signature(consensus, voter, cert) < 0) {
 -        smartlist_add(need_certs_from, voter);
 -        ++n_missing_key;
 -        continue;
 +  SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *,
 +                          voter) {
 +    int good_here = 0;
 +    int bad_here = 0;
 +    int unknown_here = 0;
 +    int missing_key_here = 0, dl_failed_key_here = 0;
 +    SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
 +      if (!sig->good_signature && !sig->bad_signature &&
 +          sig->signature) {
 +        /* we can try to check the signature. */
 +        int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
 +                                              sig->identity_digest) != NULL;
 +        authority_cert_t *cert =
 +          authority_cert_get_by_digests(sig->identity_digest,
 +                                        sig->signing_key_digest);
 +        tor_assert(!memcmp(sig->identity_digest, voter->identity_digest,
 +                           DIGEST_LEN));
 +
 +        if (!is_v3_auth) {
 +          smartlist_add(unrecognized, voter);
 +          ++unknown_here;
 +          continue;
 +        } else if (!cert || cert->expires < now) {
 +          smartlist_add(need_certs_from, voter);
 +          ++missing_key_here;
 +          if (authority_cert_dl_looks_uncertain(sig->identity_digest))
 +            ++dl_failed_key_here;
 +          continue;
 +        }
 +        if (networkstatus_check_document_signature(consensus, sig, cert) < 0) {
 +          smartlist_add(need_certs_from, voter);
 +          ++missing_key_here;
 +          if (authority_cert_dl_looks_uncertain(sig->identity_digest))
 +            ++dl_failed_key_here;
 +          continue;
 +        }
        }
 -    }
 -    if (voter->good_signature)
 +      if (sig->good_signature)
 +        ++good_here;
 +      else if (sig->bad_signature)
 +        ++bad_here;
 +    } SMARTLIST_FOREACH_END(sig);
 +    if (good_here)
        ++n_good;
 -    else if (voter->bad_signature)
 +    else if (bad_here)
        ++n_bad;
 -    else
 +    else if (missing_key_here) {
 +      ++n_missing_key;
 +      if (dl_failed_key_here)
 +        ++n_dl_failed_key;
 +    } else if (unknown_here) {
 +      ++n_unknown;
 +    } else {
        ++n_no_signature;
 -  });
 +    }
 +  } SMARTLIST_FOREACH_END(voter);
  
    /* Now see whether we're missing any voters entirely. */
    SMARTLIST_FOREACH(router_get_trusted_dir_servers(),
@@@ -544,71 -443,39 +545,71 @@@
          smartlist_add(missing_authorities, ds);
      });
  
 -  if (warn > 1 || (warn >= 0 && n_good < n_required))
 +  if (warn > 1 || (warn >= 0 &&
 +                   (n_good + n_missing_key - n_dl_failed_key < n_required))) {
      severity = LOG_WARN;
 -  else
 +  } else {
      severity = LOG_INFO;
 +  }
  
    if (warn >= 0) {
      SMARTLIST_FOREACH(unrecognized, networkstatus_voter_info_t *, voter,
        {
 -        log_info(LD_DIR, "Consensus includes unrecognized authority '%s' "
 -                 "at %s:%d (contact %s; identity %s)",
 +        log(severity, LD_DIR, "Consensus includes unrecognized authority "
 +                 "'%s' at %s:%d (contact %s; identity %s)",
                   voter->nickname, voter->address, (int)voter->dir_port,
                   voter->contact?voter->contact:"n/a",
                   hex_str(voter->identity_digest, DIGEST_LEN));
        });
      SMARTLIST_FOREACH(need_certs_from, networkstatus_voter_info_t *, voter,
        {
 -        log_info(LD_DIR, "Looks like we need to download a new certificate "
 -                 "from authority '%s' at %s:%d (contact %s; identity %s)",
 +        log(severity, LD_DIR, "Looks like we need to download a new "
 +                 "certificate from authority '%s' at %s:%d (contact %s; "
 +                 "identity %s)",
                   voter->nickname, voter->address, (int)voter->dir_port,
                   voter->contact?voter->contact:"n/a",
                   hex_str(voter->identity_digest, DIGEST_LEN));
        });
      SMARTLIST_FOREACH(missing_authorities, trusted_dir_server_t *, ds,
        {
 -        log_info(LD_DIR, "Consensus does not include configured "
 +        log(severity, LD_DIR, "Consensus does not include configured "
                   "authority '%s' at %s:%d (identity %s)",
                   ds->nickname, ds->address, (int)ds->dir_port,
                   hex_str(ds->v3_identity_digest, DIGEST_LEN));
        });
 -    log(severity, LD_DIR,
 -        "%d unknown, %d missing key, %d good, %d bad, %d no signature, "
 -        "%d required", n_unknown, n_missing_key, n_good, n_bad,
 -        n_no_signature, n_required);
 +    {
 +      smartlist_t *sl = smartlist_create();
 +      char *cp;
 +      tor_asprintf(&cp, "A consensus needs %d good signatures from recognized "
 +                   "authorities for us to accept it. This one has %d.",
 +                   n_required, n_good);
 +      smartlist_add(sl,cp);
 +      if (n_no_signature) {
 +        tor_asprintf(&cp, "%d of the authorities we know didn't sign it.",
 +                     n_no_signature);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_unknown) {
 +        tor_asprintf(&cp, "It has %d signatures from authorities we don't "
 +                      "recognize.", n_unknown);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_bad) {
 +        tor_asprintf(&cp, "%d of the signatures on it didn't verify "
 +                      "correctly.", n_bad);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_missing_key) {
 +        tor_asprintf(&cp, "We were unable to check %d of the signatures, "
 +                      "because we were missing the keys.", n_missing_key);
 +        smartlist_add(sl,cp);
 +      }
 +      cp = smartlist_join_strings(sl, " ", 0, NULL);
 +      log(severity, LD_DIR, "%s", cp);
 +      tor_free(cp);
 +      SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
 +      smartlist_free(sl);
 +    }
    }
  
    smartlist_free(unrecognized);
@@@ -918,8 -785,8 +919,8 @@@ networkstatus_v2_list_clean(time_t now
  
  /** Helper for bsearching a list of routerstatus_t pointers: compare a
   * digest in the key to the identity digest of a routerstatus_t. */
 -static int
 -_compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
 +int
 +compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
  {
    const char *key = _key;
    const routerstatus_t *rs = *_member;
@@@ -932,7 -799,7 +933,7 @@@ routerstatus_t 
  networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest)
  {
    return smartlist_bsearch(ns->entries, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or
@@@ -941,7 -808,7 +942,7 @@@ routerstatus_t 
  networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest)
  {
    return smartlist_bsearch(ns->routerstatus_list, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /*XXXX make this static once functions are moved into this file. */
@@@ -953,7 -820,7 +954,7 @@@ networkstatus_vote_find_entry_idx(netwo
                                    const char *digest, int *found_out)
  {
    return smartlist_bsearch_idx(ns->routerstatus_list, digest,
 -                               _compare_digest_to_routerstatus_entry,
 +                               compare_digest_to_routerstatus_entry,
                                 found_out);
  }
  
@@@ -1006,7 -873,7 +1007,7 @@@ router_get_consensus_status_by_id(cons
    if (!current_consensus)
      return NULL;
    return smartlist_bsearch(current_consensus->routerstatus_list, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return
@@@ -1210,30 -1077,28 +1211,30 @@@ update_v2_networkstatus_cache_downloads
  static void
  update_consensus_networkstatus_downloads(time_t now)
  {
 -  or_options_t *options = get_options();
 +  int i;
    if (!networkstatus_get_live_consensus(now))
      time_to_download_next_consensus = now; /* No live consensus? Get one now!*/
    if (time_to_download_next_consensus > now)
      return; /* Wait until the current consensus is older. */
 -  if (authdir_mode_v3(options))
 -    return; /* Authorities never fetch a consensus */
 -  if (!download_status_is_ready(&consensus_dl_status, now,
 +  /* XXXXNM Microdescs: may need to download more types. */
 +  if (!download_status_is_ready(&consensus_dl_status[FLAV_NS], now,
                                  CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES))
      return; /* We failed downloading a consensus too recently. */
    if (connection_get_by_type_purpose(CONN_TYPE_DIR,
                                       DIR_PURPOSE_FETCH_CONSENSUS))
      return; /* There's an in-progress download.*/
  
 -  if (consensus_waiting_for_certs) {
 -    /* XXXX make sure this doesn't delay sane downloads. */
 -    if (consensus_waiting_for_certs_set_at + DELAY_WHILE_FETCHING_CERTS > now)
 -      return; /* We're still getting certs for this one. */
 -    else {
 -      if (!consensus_waiting_for_certs_dl_failed) {
 -        download_status_failed(&consensus_dl_status, 0);
 -        consensus_waiting_for_certs_dl_failed=1;
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (waiting->consensus) {
 +      /* XXXX make sure this doesn't delay sane downloads. */
 +      if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now)
 +        return; /* We're still getting certs for this one. */
 +      else {
 +        if (!waiting->dl_failed) {
 +          download_status_failed(&consensus_dl_status[FLAV_NS], 0);
 +          waiting->dl_failed=1;
 +        }
        }
      }
    }
@@@ -1249,8 -1114,7 +1250,8 @@@
  void
  networkstatus_consensus_download_failed(int status_code)
  {
 -  download_status_failed(&consensus_dl_status, status_code);
 +  /* XXXXNM Microdescs: may need to handle more types. */
 +  download_status_failed(&consensus_dl_status[FLAV_NS], status_code);
    /* Retry immediately, if appropriate. */
    update_consensus_networkstatus_downloads(time(NULL));
  }
@@@ -1284,15 -1148,8 +1285,15 @@@ update_consensus_networkstatus_fetch_ti
        /* We want to cache the next one at some point after this one
         * is no longer fresh... */
        start = c->fresh_until + min_sec_before_caching;
 -      /* But only in the first half-interval after that. */
 -      dl_interval = interval/2;
 +      /* Some clients may need the consensus sooner than others. */
 +      if (options->FetchDirInfoExtraEarly || authdir_mode_v3(options)) {
 +        dl_interval = 60;
 +        if (min_sec_before_caching + dl_interval > interval)
 +          dl_interval = interval/2;
 +      } else {
 +        /* But only in the first half-interval after that. */
 +        dl_interval = interval/2;
 +      }
      } else {
        /* We're an ordinary client or a bridge. Give all the caches enough
         * time to download the consensus. */
@@@ -1311,7 -1168,7 +1312,7 @@@
      }
      if (dl_interval < 1)
        dl_interval = 1;
 -    /* We must not try to replace c while it's still the most valid: */
 +    /* We must not try to replace c while it's still fresh: */
      tor_assert(c->fresh_until < start);
      /* We must download the next one before c is invalid: */
      tor_assert(start+dl_interval < c->valid_until);
@@@ -1332,6 -1189,7 +1333,6 @@@
      time_to_download_next_consensus = now;
      log_info(LD_DIR, "No live consensus; we should fetch one immediately.");
    }
 -
  }
  
  /** Return 1 if there's a reason we shouldn't try any directory
@@@ -1366,14 -1224,10 +1367,14 @@@ update_networkstatus_downloads(time_t n
  void
  update_certificate_downloads(time_t now)
  {
 -  if (consensus_waiting_for_certs)
 -    authority_certs_fetch_missing(consensus_waiting_for_certs, now);
 -  else
 -    authority_certs_fetch_missing(current_consensus, now);
 +  int i;
 +  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    if (consensus_waiting_for_certs[i].consensus)
 +      authority_certs_fetch_missing(consensus_waiting_for_certs[i].consensus,
 +                                    now);
 +  }
 +
 +  authority_certs_fetch_missing(current_consensus, now);
  }
  
  /** Return 1 if we have a consensus but we don't have enough certificates
@@@ -1381,8 -1235,7 +1382,8 @@@
  int
  consensus_is_waiting_for_certs(void)
  {
 -  return consensus_waiting_for_certs ? 1 : 0;
 +  return consensus_waiting_for_certs[USABLE_CONSENSUS_FLAVOR].consensus
 +    ? 1 : 0;
  }
  
  /** Return the network status with a given identity digest. */
@@@ -1551,31 -1404,16 +1552,31 @@@ networkstatus_copy_old_consensus_info(n
   * user, and -2 for more serious problems.
   */
  int
 -networkstatus_set_current_consensus(const char *consensus, unsigned flags)
 +networkstatus_set_current_consensus(const char *consensus,
 +                                    const char *flavor,
 +                                    unsigned flags)
  {
 -  networkstatus_t *c;
 +  networkstatus_t *c=NULL;
    int r, result = -1;
    time_t now = time(NULL);
 +  or_options_t *options = get_options();
    char *unverified_fname = NULL, *consensus_fname = NULL;
 +  int flav = networkstatus_parse_flavor_name(flavor);
    const unsigned from_cache = flags & NSSET_FROM_CACHE;
    const unsigned was_waiting_for_certs = flags & NSSET_WAS_WAITING_FOR_CERTS;
    const unsigned dl_certs = !(flags & NSSET_DONT_DOWNLOAD_CERTS);
    const unsigned accept_obsolete = flags & NSSET_ACCEPT_OBSOLETE;
 +  const unsigned require_flavor = flags & NSSET_REQUIRE_FLAVOR;
 +  const digests_t *current_digests = NULL;
 +  consensus_waiting_for_certs_t *waiting = NULL;
 +  time_t current_valid_after = 0;
 +  int free_consensus = 1; /* Free 'c' at the end of the function */
 +
 +  if (flav < 0) {
 +    /* XXXX we don't handle unrecognized flavors yet. */
 +    log_warn(LD_BUG, "Unrecognized consensus flavor %s", flavor);
 +    return -2;
 +  }
  
    /* Make sure it's parseable. */
    c = networkstatus_parse_vote_from_string(consensus, NULL, NS_TYPE_CONSENSUS);
@@@ -1585,69 -1423,33 +1586,69 @@@
      goto done;
    }
  
 +  if ((int)c->flavor != flav) {
 +    /* This wasn't the flavor we thought we were getting. */
 +    if (require_flavor) {
 +      log_warn(LD_DIR, "Got consensus with unexpected flavor %s (wanted %s)",
 +               networkstatus_get_flavor_name(c->flavor), flavor);
 +      goto done;
 +    }
 +    flav = c->flavor;
 +    flavor = networkstatus_get_flavor_name(flav);
 +  }
 +
 +  if (flav != USABLE_CONSENSUS_FLAVOR &&
 +      !directory_caches_dir_info(options)) {
 +    /* This consensus is totally boring to us: we won't use it, and we won't
 +     * serve it.  Drop it. */
 +    goto done;
 +  }
 +
    if (from_cache && !accept_obsolete &&
        c->valid_until < now-OLD_ROUTER_DESC_MAX_AGE) {
      /* XXX022 when we try to make fallbackconsensus work again, we should
       * consider taking this out. Until then, believing obsolete consensuses
       * is causing more harm than good. See also bug 887. */
 -    log_info(LD_DIR, "Loaded an obsolete consensus. Discarding.");
 +    log_info(LD_DIR, "Loaded an expired consensus. Discarding.");
      goto done;
    }
  
 -  if (current_consensus &&
 -      !memcmp(c->networkstatus_digest, current_consensus->networkstatus_digest,
 -              DIGEST_LEN)) {
 +  if (!strcmp(flavor, "ns")) {
 +    consensus_fname = get_datadir_fname("cached-consensus");
 +    unverified_fname = get_datadir_fname("unverified-consensus");
 +    if (current_consensus) {
 +      current_digests = &current_consensus->digests;
 +      current_valid_after = current_consensus->valid_after;
 +    }
 +  } else {
 +    cached_dir_t *cur;
 +    char buf[128];
 +    tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor);
 +    consensus_fname = get_datadir_fname(buf);
 +    tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor);
 +    unverified_fname = get_datadir_fname(buf);
 +    cur = dirserv_get_consensus(flavor);
 +    if (cur) {
 +      current_digests = &cur->digests;
 +      current_valid_after = cur->published;
 +    }
 +  }
 +
 +  if (current_digests &&
 +      !memcmp(&c->digests, current_digests, sizeof(c->digests))) {
      /* We already have this one. That's a failure. */
 -    log_info(LD_DIR, "Got a consensus we already have");
 +    log_info(LD_DIR, "Got a %s consensus we already have", flavor);
      goto done;
    }
  
 -  if (current_consensus && c->valid_after <= current_consensus->valid_after) {
 +  if (current_valid_after && c->valid_after <= current_valid_after) {
      /* We have a newer one.  There's no point in accepting this one,
       * even if it's great. */
 -    log_info(LD_DIR, "Got a consensus at least as old as the one we have");
 +    log_info(LD_DIR, "Got a %s consensus at least as old as the one we have",
 +             flavor);
      goto done;
    }
  
 -  consensus_fname = get_datadir_fname("cached-consensus");
 -  unverified_fname = get_datadir_fname("unverified-consensus");
 -
    /* Make sure it's signed enough. */
    if ((r=networkstatus_check_consensus_signature(c, 1))<0) {
      if (r == -1) {
@@@ -1656,16 -1458,16 +1657,16 @@@
          log_info(LD_DIR,
                   "Not enough certificates to check networkstatus consensus");
        }
 -      if (!current_consensus ||
 -          c->valid_after > current_consensus->valid_after) {
 -        if (consensus_waiting_for_certs)
 -          networkstatus_vote_free(consensus_waiting_for_certs);
 -        tor_free(consensus_waiting_for_certs_body);
 -        consensus_waiting_for_certs = c;
 -        c = NULL; /* Prevent free. */
 -        consensus_waiting_for_certs_body = tor_strdup(consensus);
 -        consensus_waiting_for_certs_set_at = now;
 -        consensus_waiting_for_certs_dl_failed = 0;
 +      if (!current_valid_after ||
 +          c->valid_after > current_valid_after) {
 +        waiting = &consensus_waiting_for_certs[flav];
 +        networkstatus_vote_free(waiting->consensus);
 +        tor_free(waiting->body);
 +        waiting->consensus = c;
 +        free_consensus = 0;
 +        waiting->body = tor_strdup(consensus);
 +        waiting->set_at = now;
 +        waiting->dl_failed = 0;
          if (!from_cache) {
            write_str_to_file(unverified_fname, consensus, 0);
          }
@@@ -1694,75 -1496,56 +1695,75 @@@
      }
    }
  
 -  if (!from_cache)
 +  if (!from_cache && flav == USABLE_CONSENSUS_FLAVOR)
      control_event_client_status(LOG_NOTICE, "CONSENSUS_ARRIVED");
  
    /* Are we missing any certificates at all? */
    if (r != 1 && dl_certs)
      authority_certs_fetch_missing(c, now);
  
 -  notify_control_networkstatus_changed(current_consensus, c);
 +  if (flav == USABLE_CONSENSUS_FLAVOR) {
 +    notify_control_networkstatus_changed(current_consensus, c);
  
 -  if (current_consensus) {
 -    networkstatus_copy_old_consensus_info(c, current_consensus);
 -    networkstatus_vote_free(current_consensus);
 +    if (current_consensus) {
 +      networkstatus_copy_old_consensus_info(c, current_consensus);
 +      networkstatus_vote_free(current_consensus);
 +      /* Defensive programming : we should set current_consensus very soon,
 +       * but we're about to call some stuff in the meantime, and leaving this
 +       * dangling pointer around has proven to be trouble. */
 +       current_consensus = NULL;
 +    }
    }
  
 -  if (consensus_waiting_for_certs &&
 -      consensus_waiting_for_certs->valid_after <= c->valid_after) {
 -    networkstatus_vote_free(consensus_waiting_for_certs);
 -    consensus_waiting_for_certs = NULL;
 -    if (consensus != consensus_waiting_for_certs_body)
 -      tor_free(consensus_waiting_for_certs_body);
 +  waiting = &consensus_waiting_for_certs[flav];
 +  if (waiting->consensus &&
 +      waiting->consensus->valid_after <= c->valid_after) {
 +    networkstatus_vote_free(waiting->consensus);
 +    waiting->consensus = NULL;
 +    if (consensus != waiting->body)
 +      tor_free(waiting->body);
      else
 -      consensus_waiting_for_certs_body = NULL;
 -    consensus_waiting_for_certs_set_at = 0;
 -    consensus_waiting_for_certs_dl_failed = 0;
 +      waiting->body = NULL;
 +    waiting->set_at = 0;
 +    waiting->dl_failed = 0;
      unlink(unverified_fname);
    }
  
    /* Reset the failure count only if this consensus is actually valid. */
    if (c->valid_after <= now && now <= c->valid_until) {
 -    download_status_reset(&consensus_dl_status);
 +    download_status_reset(&consensus_dl_status[flav]);
    } else {
      if (!from_cache)
 -      download_status_failed(&consensus_dl_status, 0);
 +      download_status_failed(&consensus_dl_status[flav], 0);
    }
  
 -  current_consensus = c;
 -  c = NULL; /* Prevent free. */
 +  if (flav == USABLE_CONSENSUS_FLAVOR) {
 +    current_consensus = c;
 +    free_consensus = 0; /* Prevent free. */
 +
 +    /* XXXXNM Microdescs: needs a non-ns variant. */
 +    update_consensus_networkstatus_fetch_time(now);
 +    dirvote_recalculate_timing(options, now);
 +    routerstatus_list_update_named_server_map();
 +    cell_ewma_set_scale_factor(options, current_consensus);
 +
 +    /* XXX022 where is the right place to put this call? */
 +    connection_or_update_token_buckets(get_connection_array(), options);
  
 -  update_consensus_networkstatus_fetch_time(now);
 -  dirvote_recalculate_timing(get_options(), now);
 -  routerstatus_list_update_named_server_map();
 +    circuit_build_times_new_consensus_params(&circ_times, current_consensus);
 +  }
 +
 +  if (directory_caches_dir_info(options)) {
 +    dirserv_set_cached_consensus_networkstatus(consensus,
 +                                               flavor,
 +                                               &c->digests,
 +                                               c->valid_after);
 +  }
  
    if (!from_cache) {
      write_str_to_file(consensus_fname, consensus, 0);
    }
  
 -  if (directory_caches_dir_info(get_options()))
 -    dirserv_set_cached_networkstatus_v3(consensus,
 -                                        current_consensus->valid_after);
 -
    if (ftime_definitely_before(now, current_consensus->valid_after)) {
      char tbuf[ISO_TIME_LEN+1];
      char dbuf[64];
@@@ -1781,7 -1564,7 +1782,7 @@@
  
    result = 0;
   done:
 -  if (c)
 +  if (free_consensus)
      networkstatus_vote_free(c);
    tor_free(consensus_fname);
    tor_free(unverified_fname);
@@@ -1793,17 -1576,13 +1794,17 @@@
  void
  networkstatus_note_certs_arrived(void)
  {
 -  if (consensus_waiting_for_certs) {
 -    if (networkstatus_check_consensus_signature(
 -                                    consensus_waiting_for_certs, 0)>=0) {
 +  int i;
 +  for (i=0; i<N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (!waiting->consensus)
 +      continue;
 +    if (networkstatus_check_consensus_signature(waiting->consensus, 0)>=0) {
        if (!networkstatus_set_current_consensus(
 -                                 consensus_waiting_for_certs_body,
 +                                 waiting->body,
 +                                 networkstatus_get_flavor_name(i),
                                   NSSET_WAS_WAITING_FOR_CERTS)) {
 -        tor_free(consensus_waiting_for_certs_body);
 +        tor_free(waiting->body);
        }
      }
    }
@@@ -1889,8 -1668,10 +1890,8 @@@ download_status_map_update_from_v2_netw
      v2_download_status_map = digestmap_new();
  
    dl_status = digestmap_new();
 -  SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
 -  {
 -    SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs,
 -    {
 +  SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) {
 +    SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) {
        const char *d = rs->descriptor_digest;
        download_status_t *s;
        if (digestmap_get(dl_status, d))
@@@ -1899,8 -1680,8 +1900,8 @@@
          s = tor_malloc_zero(sizeof(download_status_t));
        }
        digestmap_set(dl_status, d, s);
 -    });
 -  });
 +    } SMARTLIST_FOREACH_END(rs);
 +  } SMARTLIST_FOREACH_END(ns);
    digestmap_free(v2_download_status_map, _tor_free);
    v2_download_status_map = dl_status;
    networkstatus_v2_list_has_changed = 0;
@@@ -1914,9 -1695,11 +1915,9 @@@ routerstatus_list_update_named_server_m
    if (!current_consensus)
      return;
  
 -  if (named_server_map)
 -    strmap_free(named_server_map, _tor_free);
 +  strmap_free(named_server_map, _tor_free);
    named_server_map = strmap_new();
 -  if (unnamed_server_map)
 -    strmap_free(unnamed_server_map, NULL);
 +  strmap_free(unnamed_server_map, NULL);
    unnamed_server_map = strmap_new();
    SMARTLIST_FOREACH(current_consensus->routerstatus_list, routerstatus_t *, rs,
      {
@@@ -1991,15 -1774,6 +1992,15 @@@ routers_update_status_from_consensus_ne
        router->is_bad_directory = rs->is_bad_directory;
        router->is_bad_exit = rs->is_bad_exit;
        router->is_hs_dir = rs->is_hs_dir;
 +    } else {
 +      /* If we _are_ an authority, we should check whether this router
 +       * is one that will cause us to need a reachability test. */
 +      routerinfo_t *old_router =
 +        router_get_by_digest(router->cache_info.identity_digest);
 +      if (old_router != router) {
 +        router->needs_retest_if_added =
 +          dirserv_should_launch_reachability_test(router, old_router);
 +      }
      }
      if (router->is_running && ds) {
        download_status_reset(&ds->v2_ns_dl_status);
@@@ -2062,7 -1836,7 +2063,7 @@@ char 
  networkstatus_getinfo_helper_single(routerstatus_t *rs)
  {
    char buf[RS_ENTRY_LEN+1];
 -  routerstatus_format_entry(buf, sizeof(buf), rs, NULL, 0, 1);
 +  routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT);
    return tor_strdup(buf);
  }
  
@@@ -2099,7 -1873,7 +2100,7 @@@ networkstatus_getinfo_by_purpose(const 
      if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE)
        dirserv_set_router_is_running(ri, now);
      /* then generate and write out status lines for each of them */
 -    set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0, 0);
 +    set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0);
      smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs));
    });
  
@@@ -2124,25 -1898,6 +2125,25 @@@ networkstatus_dump_bridge_status_to_fil
    tor_free(status);
  }
  
 +int32_t
 +get_net_param_from_list(smartlist_t *net_params, const char *param_name,
 +                        int default_val)
 +{
 +  size_t name_len = strlen(param_name);
 +
 +  SMARTLIST_FOREACH_BEGIN(net_params, const char *, p) {
 +    if (!strcmpstart(p, param_name) && p[name_len] == '=') {
 +      int ok=0;
 +      long v = tor_parse_long(p+name_len+1, 10, INT32_MIN,
 +                              INT32_MAX, &ok, NULL);
 +      if (ok)
 +        return (int32_t) v;
 +    }
 +  } SMARTLIST_FOREACH_END(p);
 +
 +  return default_val;
 +}
 +
  /** Return the value of a integer parameter from the networkstatus <b>ns</b>
   * whose name is <b>param_name</b>.  If <b>ns</b> is NULL, try loading the
   * latest consensus ourselves. Return <b>default_val</b> if no latest
@@@ -2151,59 -1906,27 +2152,59 @@@ int32_
  networkstatus_get_param(networkstatus_t *ns, const char *param_name,
                          int32_t default_val)
  {
 -  size_t name_len;
 -
    if (!ns) /* if they pass in null, go find it ourselves */
      ns = networkstatus_get_latest_consensus();
  
    if (!ns || !ns->net_params)
      return default_val;
  
 -  name_len = strlen(param_name);
 +  return get_net_param_from_list(ns->net_params, param_name, default_val);
 +}
  
 -  SMARTLIST_FOREACH_BEGIN(ns->net_params, const char *, p) {
 -    if (!strcmpstart(p, param_name) && p[name_len] == '=') {
 -      int ok=0;
 -      long v = tor_parse_long(p+name_len+1, 10, INT32_MIN, INT32_MAX, &ok,
 -                              NULL);
 -      if (ok)
 -        return (int32_t) v;
 -    }
 -  } SMARTLIST_FOREACH_END(p);
 +/** Return the value of a integer bw weight parameter from the networkstatus
 + * <b>ns</b> whose name is <b>weight_name</b>.  If <b>ns</b> is NULL, try
 + * loading the latest consensus ourselves. Return <b>default_val</b> if no
 + * latest consensus, or if it has no parameter called <b>param_name</b>. */
 +int32_t
 +networkstatus_get_bw_weight(networkstatus_t *ns, const char *weight_name,
 +                        int32_t default_val)
 +{
 +  if (!ns) /* if they pass in null, go find it ourselves */
 +    ns = networkstatus_get_latest_consensus();
  
 -  return default_val;
 +  if (!ns || !ns->weight_params)
 +    return default_val;
 +
 +  return get_net_param_from_list(ns->weight_params, weight_name, default_val);
 +}
 +
 +/** Return the name of the consensus flavor <b>flav</b> as used to identify
 + * the flavor in directory documents. */
 +const char *
 +networkstatus_get_flavor_name(consensus_flavor_t flav)
 +{
 +  switch (flav) {
 +    case FLAV_NS:
 +      return "ns";
 +    case FLAV_MICRODESC:
 +      return "microdesc";
 +    default:
 +      tor_fragile_assert();
 +      return "??";
 +  }
 +}
 +
 +/** Return the consensus_flavor_t value for the flavor called <b>flavname</b>,
 + * or -1 if the flavor is not recognized. */
 +int
 +networkstatus_parse_flavor_name(const char *flavname)
 +{
 +  if (!strcmp(flavname, "ns"))
 +    return FLAV_NS;
 +  else if (!strcmp(flavname, "microdesc"))
 +    return FLAV_MICRODESC;
 +  else
 +    return -1;
  }
  
  /** If <b>question</b> is a string beginning with "ns/" in a format the
@@@ -2212,8 -1935,7 +2213,8 @@@
   * ORs.  Return 0 on success, -1 on unrecognized question format. */
  int
  getinfo_helper_networkstatus(control_connection_t *conn,
 -                             const char *question, char **answer)
 +                             const char *question, char **answer,
 +                             const char **errmsg)
  {
    routerstatus_t *status;
    (void) conn;
@@@ -2237,10 -1959,8 +2238,10 @@@
    } else if (!strcmpstart(question, "ns/id/")) {
      char d[DIGEST_LEN];
  
 -    if (base16_decode(d, DIGEST_LEN, question+6, strlen(question+6)))
 +    if (base16_decode(d, DIGEST_LEN, question+6, strlen(question+6))) {
 +      *errmsg = "Data not decodeable as hex";
        return -1;
 +    }
      status = router_get_consensus_status_by_id(d);
    } else if (!strcmpstart(question, "ns/name/")) {
      status = router_get_consensus_status_by_nickname(question+8, 0);
@@@ -2248,7 -1968,7 +2249,7 @@@
      *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL));
      return *answer ? 0 : -1;
    } else {
 -    return -1;
 +    return 0;
    }
  
    if (status)
@@@ -2260,29 -1980,30 +2261,29 @@@
  void
  networkstatus_free_all(void)
  {
 +  int i;
    if (networkstatus_v2_list) {
      SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
                        networkstatus_v2_free(ns));
      smartlist_free(networkstatus_v2_list);
      networkstatus_v2_list = NULL;
    }
 -  if (v2_download_status_map) {
 -    digestmap_free(v2_download_status_map, _tor_free);
 -    v2_download_status_map = NULL;
 -  }
 -  if (current_consensus) {
 -    networkstatus_vote_free(current_consensus);
 -    current_consensus = NULL;
 -  }
 -  if (consensus_waiting_for_certs) {
 -    networkstatus_vote_free(consensus_waiting_for_certs);
 -    consensus_waiting_for_certs = NULL;
 -  }
 -  tor_free(consensus_waiting_for_certs_body);
 -  if (named_server_map) {
 -    strmap_free(named_server_map, _tor_free);
 -  }
 -  if (unnamed_server_map) {
 -    strmap_free(unnamed_server_map, NULL);
 +
 +  digestmap_free(v2_download_status_map, _tor_free);
 +  v2_download_status_map = NULL;
 +  networkstatus_vote_free(current_consensus);
 +  current_consensus = NULL;
 +
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (waiting->consensus) {
 +      networkstatus_vote_free(waiting->consensus);
 +      waiting->consensus = NULL;
 +    }
 +    tor_free(waiting->body);
    }
 +
 +  strmap_free(named_server_map, _tor_free);
 +  strmap_free(unnamed_server_map, NULL);
  }
  
diff --combined src/or/onion.c
index 9db9145,bf72b4c..323e000
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@@ -11,10 -11,6 +11,10 @@@
   **/
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "onion.h"
 +#include "rephist.h"
  
  /** Type for a linked list of circuits that are waiting for a free CPU worker
   * to process a waiting onion handshake. */
@@@ -62,18 -58,11 +62,18 @@@ onion_pending_add(or_circuit_t *circ, c
    tor_assert(!ol_tail->next);
  
    if (ol_length >= get_options()->MaxOnionsPending) {
 -    log_warn(LD_GENERAL,
 -             "Your computer is too slow to handle this many circuit "
 -             "creation requests! Please consider using the "
 -             "MaxAdvertisedBandwidth config option or choosing a more "
 -             "restricted exit policy.");
 +#define WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL (60)
 +    static ratelim_t last_warned =
 +      RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL);
 +    char *m;
 +    if ((m = rate_limit_log(&last_warned, approx_time()))) {
 +      log_warn(LD_GENERAL,
 +               "Your computer is too slow to handle this many circuit "
 +               "creation requests! Please consider using the "
 +               "MaxAdvertisedBandwidth config option or choosing a more "
 +               "restricted exit policy.%s",m);
 +      tor_free(m);
 +    }
      tor_free(tmp);
      return -1;
    }
@@@ -199,6 -188,7 +199,7 @@@ onion_skin_create(crypto_pk_env_t *dest
  
    /* set meeting point, meeting cookie, etc here. Leave zero for now. */
    if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out,
+                                       ONIONSKIN_CHALLENGE_LEN,
                                        challenge, DH_KEY_LEN,
                                        PK_PKCS1_OAEP_PADDING, 1)<0)
      goto err;
@@@ -241,6 -231,7 +242,7 @@@ onion_skin_server_handshake(const char 
        break;
      note_crypto_pk_op(DEC_ONIONSKIN);
      len = crypto_pk_private_hybrid_decrypt(k, challenge,
+                                            ONIONSKIN_CHALLENGE_LEN,
                                             onion_skin, ONIONSKIN_CHALLENGE_LEN,
                                             PK_PKCS1_OAEP_PADDING,0);
      if (len>0)
@@@ -264,9 -255,8 +266,9 @@@
  
    key_material_len = DIGEST_LEN+key_out_len;
    key_material = tor_malloc(key_material_len);
 -  len = crypto_dh_compute_secret(dh, challenge, DH_KEY_LEN,
 -                                 key_material, key_material_len);
 +  len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge,
 +                                 DH_KEY_LEN, key_material,
 +                                 key_material_len);
    if (len < 0) {
      log_info(LD_GENERAL, "crypto_dh_compute_secret failed.");
      goto err;
@@@ -316,9 -306,8 +318,9 @@@ onion_skin_client_handshake(crypto_dh_e
  
    key_material_len = DIGEST_LEN + key_out_len;
    key_material = tor_malloc(key_material_len);
 -  len = crypto_dh_compute_secret(handshake_state, handshake_reply, DH_KEY_LEN,
 -                                 key_material, key_material_len);
 +  len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state,
 +                                 handshake_reply, DH_KEY_LEN, key_material,
 +                                 key_material_len);
    if (len < 0)
      goto err;
  
diff --combined src/or/rendclient.c
index 7c626c6,ab18d35..b8526b6
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@@ -8,19 -8,6 +8,19 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "directory.h"
 +#include "main.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rephist.h"
 +#include "routerlist.h"
  
  /** Called when we've established a circuit to an introduction point:
   * send the introduction request. */
@@@ -76,7 -63,7 +76,7 @@@ rend_client_send_introduction(origin_ci
    rend_cache_entry_t *entry;
    crypt_path_t *cpath;
    off_t dh_offset;
 -  crypto_pk_env_t *intro_key; /* either Bob's public key or an intro key. */
 +  crypto_pk_env_t *intro_key = NULL;
  
    tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
    tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY);
@@@ -89,26 -76,48 +89,26 @@@
                                &entry) < 1) {
      log_warn(LD_REND,
               "query %s didn't have valid rend desc in cache. Failing.",
 -             escaped_safe_str(introcirc->rend_data->onion_address));
 +             escaped_safe_str_client(introcirc->rend_data->onion_address));
      goto err;
    }
  
 -  /* first 20 bytes of payload are the hash of Bob's pk */
 -  if (entry->parsed->version == 0) { /* un-versioned descriptor */
 -    intro_key = entry->parsed->pk;
 -  } else { /* versioned descriptor */
 -    intro_key = NULL;
 -    SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *,
 -                      intro, {
 -      if (!memcmp(introcirc->build_state->chosen_exit->identity_digest,
 -                  intro->extend_info->identity_digest, DIGEST_LEN)) {
 -        intro_key = intro->intro_key;
 -        break;
 -      }
 -    });
 -    if (!intro_key) {
 -      /** XXX This case probably means that the intro point vanished while
 -       * we were building a circuit to it. In the future, we should find
 -       * out how that happened and whether we should kill the circuits to
 -       * removed intro points immediately. See task 1073. */
 -      int num_intro_points = smartlist_len(entry->parsed->intro_nodes);
 -      if (rend_cache_lookup_entry(introcirc->rend_data->onion_address,
 -          0, &entry) > 0) {
 -        log_info(LD_REND, "We have both a v0 and a v2 rend desc for this "
 -                 "service. The v2 desc doesn't contain the introduction "
 -                 "point (and key) to send an INTRODUCE1/2 cell to this "
 -                 "introduction point. Assuming the introduction point "
 -                 "is for v0 rend clients and using the service key "
 -                 "from the v0 desc instead. (This is probably a bug, "
 -                 "because we shouldn't even have both a v0 and a v2 "
 -                 "descriptor for the same service.)");
 -        /* See flyspray task 1024. */
 -        intro_key = entry->parsed->pk;
 -      } else {
 -        log_info(LD_REND, "Internal error: could not find intro key; we "
 -                 "only have a v2 rend desc with %d intro points.",
 -                 num_intro_points);
 -        goto err;
 -      }
 +  /* first 20 bytes of payload are the hash of the intro key */
 +  intro_key = NULL;
 +  SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *,
 +                    intro, {
 +    if (!memcmp(introcirc->build_state->chosen_exit->identity_digest,
 +                intro->extend_info->identity_digest, DIGEST_LEN)) {
 +      intro_key = intro->intro_key;
 +      break;
      }
 +  });
 +  if (!intro_key) {
 +    log_info(LD_REND, "Our introduction point knowledge changed in "
 +             "mid-connect! Could not find intro key; we only have a "
 +             "v2 rend desc with %d intro points. Giving up.",
 +             smartlist_len(entry->parsed->intro_nodes));
 +    goto err;
    }
    if (crypto_pk_get_digest(intro_key, payload)<0) {
      log_warn(LD_BUG, "Internal error: couldn't hash public key.");
@@@ -184,6 -193,7 +184,7 @@@
    /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
     * to avoid buffer overflows? */
    r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
+                                       sizeof(payload)-DIGEST_LEN,
                                        tmp,
                                        (int)(dh_offset+DH_KEY_LEN),
                                        PK_PKCS1_OAEP_PADDING, 0);
@@@ -209,7 -219,7 +210,7 @@@
    introcirc->_base.purpose = CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT;
  
    return 0;
 -err:
 + err:
    circuit_mark_for_close(TO_CIRCUIT(introcirc), END_CIRC_REASON_INTERNAL);
    circuit_mark_for_close(TO_CIRCUIT(rendcirc), END_CIRC_REASON_INTERNAL);
    return -1;
@@@ -282,7 -292,7 +283,7 @@@ rend_client_introduction_acked(origin_c
        extend_info = rend_client_get_random_intro(circ->rend_data);
        if (!extend_info) {
          log_warn(LD_REND, "No introduction points left for %s. Closing.",
 -                 escaped_safe_str(circ->rend_data->onion_address));
 +                 escaped_safe_str_client(circ->rend_data->onion_address));
          circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
          return -1;
        }
@@@ -290,7 -300,7 +291,7 @@@
          log_info(LD_REND,
                   "Got nack for %s from %s. Re-extending circ %d, "
                   "this time to %s.",
 -                 escaped_safe_str(circ->rend_data->onion_address),
 +                 escaped_safe_str_client(circ->rend_data->onion_address),
                   circ->build_state->chosen_exit->nickname,
                   circ->_base.n_circ_id, extend_info->nickname);
          result = circuit_extend_to_new_exit(circ, extend_info);
@@@ -298,7 -308,7 +299,7 @@@
          log_info(LD_REND,
                   "Got nack for %s from %s. Building a new introduction "
                   "circuit, this time to %s.",
 -                 escaped_safe_str(circ->rend_data->onion_address),
 +                 escaped_safe_str_client(circ->rend_data->onion_address),
                   circ->build_state->chosen_exit->nickname,
                   extend_info->nickname);
          circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
@@@ -461,21 -471,45 +462,21 @@@ directory_get_from_hs_dir(const char *d
             rend_query->onion_address, desc_id_base32,
             rend_query->auth_type,
             (rend_query->auth_type == REND_NO_AUTH ? "[none]" :
 -           escaped_safe_str(descriptor_cookie_base64)),
 +           escaped_safe_str_client(descriptor_cookie_base64)),
             hs_dir->nickname, hs_dir->dir_port);
    return 1;
  }
  
 -/** If we are not currently fetching a rendezvous service descriptor
 - * for the service ID <b>query</b>, start a directory connection to fetch a
 - * new one.
 - */
 -void
 -rend_client_refetch_renddesc(const char *query)
 -{
 -  if (!get_options()->FetchHidServDescriptors)
 -    return;
 -  log_info(LD_REND, "Fetching rendezvous descriptor for service %s",
 -           escaped_safe_str(query));
 -  if (connection_get_by_type_state_rendquery(CONN_TYPE_DIR, 0, query, 0)) {
 -    log_info(LD_REND,"Would fetch a new renddesc here (for %s), but one is "
 -             "already in progress.", escaped_safe_str(query));
 -  } else {
 -    /* not one already; initiate a dir rend desc lookup */
 -    directory_get_from_dirserver(DIR_PURPOSE_FETCH_RENDDESC,
 -                                 ROUTER_PURPOSE_GENERAL, query,
 -                                 PDS_RETRY_IF_NO_SERVERS);
 -  }
 -}
 -
 -/** Start a connection to a hidden service directory to fetch a v2
 - * rendezvous service descriptor for the base32-encoded service ID
 - * <b>query</b>.
 - */
 +/** Unless we already have a descriptor for <b>rend_query</b> with at least
 + * one (possibly) working introduction point in it, start a connection to a
 + * hidden service directory to fetch a v2 rendezvous service descriptor. */
  void
  rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
  {
    char descriptor_id[DIGEST_LEN];
    int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
 -  int i, tries_left, r;
 +  int i, tries_left;
    rend_cache_entry_t *e = NULL;
 -  time_t now = time(NULL);
    tor_assert(rend_query);
    /* Are we configured to fetch descriptors? */
    if (!get_options()->FetchHidServDescriptors) {
@@@ -484,13 -518,15 +485,13 @@@
      return;
    }
    /* Before fetching, check if we already have the descriptor here. */
 -  r = rend_cache_lookup_entry(rend_query->onion_address, -1, &e);
 -  if (r > 0 && now - e->received < NUM_SECONDS_BEFORE_HS_REFETCH) {
 +  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0) {
      log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
 -                      "already have a fresh copy of that descriptor here. "
 -                      "Not fetching.");
 +                      "already have that descriptor here. Not fetching.");
      return;
    }
    log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
 -            safe_str(rend_query->onion_address));
 +            safe_str_client(rend_query->onion_address));
    /* Randomly iterate over the replicas until a descriptor can be fetched
     * from one of the consecutive nodes, or no options are left. */
    tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
@@@ -516,8 -552,8 +517,8 @@@
    log_info(LD_REND, "Could not pick one of the responsible hidden "
                      "service directories to fetch descriptors, because "
                      "we already tried them all unsuccessfully.");
 -  /* Close pending connections (unless a v0 request is still going on). */
 -  rend_client_desc_trynow(rend_query->onion_address, 2);
 +  /* Close pending connections. */
 +  rend_client_desc_trynow(rend_query->onion_address);
    return;
  }
  
@@@ -538,13 -574,18 +539,13 @@@ rend_client_remove_intro_point(extend_i
    r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
    if (r<0) {
      log_warn(LD_BUG, "Malformed service ID %s.",
 -             escaped_safe_str(rend_query->onion_address));
 +             escaped_safe_str_client(rend_query->onion_address));
      return -1;
    }
    if (r==0) {
      log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
 -             escaped_safe_str(rend_query->onion_address));
 -    /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -     * arrives first. Exception: When using client authorization, only
 -     * fetch v2 descriptors.*/
 +             escaped_safe_str_client(rend_query->onion_address));
      rend_client_refetch_v2_renddesc(rend_query);
 -    if (rend_query->auth_type == REND_NO_AUTH)
 -      rend_client_refetch_renddesc(rend_query->onion_address);
      return 0;
    }
  
@@@ -561,13 -602,18 +562,13 @@@
    if (smartlist_len(ent->parsed->intro_nodes) == 0) {
      log_info(LD_REND,
               "No more intro points remain for %s. Re-fetching descriptor.",
 -             escaped_safe_str(rend_query->onion_address));
 -    /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -     * arrives first. Exception: When using client authorization, only
 -     * fetch v2 descriptors.*/
 +             escaped_safe_str_client(rend_query->onion_address));
      rend_client_refetch_v2_renddesc(rend_query);
 -    if (rend_query->auth_type == REND_NO_AUTH)
 -      rend_client_refetch_renddesc(rend_query->onion_address);
  
      /* move all pending streams back to renddesc_wait */
      while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
                                     AP_CONN_STATE_CIRCUIT_WAIT,
 -                                   rend_query->onion_address, -1))) {
 +                                   rend_query->onion_address))) {
        conn->state = AP_CONN_STATE_RENDDESC_WAIT;
      }
  
@@@ -575,7 -621,7 +576,7 @@@
    }
    log_info(LD_REND,"%d options left for %s.",
             smartlist_len(ent->parsed->intro_nodes),
 -           escaped_safe_str(rend_query->onion_address));
 +           escaped_safe_str_client(rend_query->onion_address));
    return 1;
  }
  
@@@ -637,8 -683,7 +638,8 @@@ rend_client_receive_rendezvous(origin_c
    tor_assert(circ->build_state->pending_final_cpath);
    hop = circ->build_state->pending_final_cpath;
    tor_assert(hop->dh_handshake_state);
 -  if (crypto_dh_compute_secret(hop->dh_handshake_state, (char*)request,
 +  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
 +                               hop->dh_handshake_state, (char*)request,
                                 DH_KEY_LEN,
                                 keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
      log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
@@@ -678,18 -723,24 +679,18 @@@
    return -1;
  }
  
 -/** Find all the apconns in state AP_CONN_STATE_RENDDESC_WAIT that
 - * are waiting on query. If there's a working cache entry here
 - * with at least one intro point, move them to the next state. If
 - * <b>rend_version</b> is non-negative, fail connections that have
 - * requested <b>query</b> unless there are still descriptor fetch
 - * requests in progress for other descriptor versions than
 - * <b>rend_version</b>.
 - */
 +/** Find all the apconns in state AP_CONN_STATE_RENDDESC_WAIT that are
 + * waiting on <b>query</b>. If there's a working cache entry here with at
 + * least one intro point, move them to the next state. */
  void
 -rend_client_desc_trynow(const char *query, int rend_version)
 +rend_client_desc_trynow(const char *query)
  {
    edge_connection_t *conn;
    rend_cache_entry_t *entry;
    time_t now = time(NULL);
  
    smartlist_t *conns = get_connection_array();
 -  SMARTLIST_FOREACH(conns, connection_t *, _conn,
 -  {
 +  SMARTLIST_FOREACH_BEGIN(conns, connection_t *, _conn) {
      if (_conn->type != CONN_TYPE_AP ||
          _conn->state != AP_CONN_STATE_RENDDESC_WAIT ||
          _conn->marked_for_close)
@@@ -721,12 -772,17 +722,12 @@@
            connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
        }
      } else { /* 404, or fetch didn't get that far */
 -      /* Unless there are requests for another descriptor version pending,
 -       * close the connection. */
 -      if (rend_version >= 0 &&
 -          !connection_get_by_type_state_rendquery(CONN_TYPE_DIR, 0, query,
 -                                                  rend_version == 0 ? 2 : 0)) {
 -        log_notice(LD_REND,"Closing stream for '%s.onion': hidden service is "
 -                   "unavailable (try again later).", safe_str(query));
 -        connection_mark_unattached_ap(conn, END_STREAM_REASON_RESOLVEFAILED);
 -      }
 +      log_notice(LD_REND,"Closing stream for '%s.onion': hidden service is "
 +                 "unavailable (try again later).",
 +                 safe_str_client(query));
 +      connection_mark_unattached_ap(conn, END_STREAM_REASON_RESOLVEFAILED);
      }
 -  });
 +  } SMARTLIST_FOREACH_END(_conn);
  }
  
  /** Return a newly allocated extend_info_t* for a randomly chosen introduction
@@@ -744,7 -800,7 +745,7 @@@ rend_client_get_random_intro(const rend
    if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) {
      log_warn(LD_REND,
               "Query '%s' didn't have valid rend desc in cache. Failing.",
 -             safe_str(rend_query->onion_address));
 +             safe_str_client(rend_query->onion_address));
      return NULL;
    }
  
@@@ -756,10 -812,7 +757,10 @@@
    intro = smartlist_get(entry->parsed->intro_nodes, i);
    /* Do we need to look up the router or is the extend info complete? */
    if (!intro->extend_info->onion_key) {
 -    router = router_get_by_nickname(intro->extend_info->nickname, 0);
 +    if (tor_digest_is_zero(intro->extend_info->identity_digest))
 +      router = router_get_by_hexdigest(intro->extend_info->nickname);
 +    else
 +      router = router_get_by_digest(intro->extend_info->identity_digest);
      if (!router) {
        log_info(LD_REND, "Unknown router with nickname '%s'; trying another.",
                 intro->extend_info->nickname);
@@@ -897,7 -950,8 +898,7 @@@ rend_parse_service_authorization(or_opt
   err:
    res = -1;
   done:
 -  if (auth)
 -    rend_service_authorization_free(auth);
 +  rend_service_authorization_free(auth);
    SMARTLIST_FOREACH(sl, char *, c, tor_free(c););
    smartlist_free(sl);
    if (!validate_only && res == 0) {
diff --combined src/or/rendservice.c
index 44b5a4b,07f01ae..1d64cf4
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@@ -8,23 -8,10 +8,23 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "directory.h"
 +#include "networkstatus.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "router.h"
 +#include "relay.h"
 +#include "rephist.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro,
 -                                            const char *pk_digest,
 -                                            int desc_version);
 +                                            const char *pk_digest);
  
  /** Represents the mapping from a virtual port of a rendezvous service to
   * a real port on some IP.
@@@ -55,6 -42,8 +55,6 @@@ typedef struct rend_service_t 
    /* Fields specified in config file */
    char *directory; /**< where in the filesystem it stores it */
    smartlist_t *ports; /**< List of rend_service_port_config_t */
 -  int descriptor_version; /**< Rendezvous descriptor version that will be
 -                           * published. */
    rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
                                 * authorization is performed. */
    smartlist_t *clients; /**< List of rend_authorized_client_t's of
@@@ -69,7 -58,7 +69,7 @@@
                               * or are trying to establish. */
    time_t intro_period_started; /**< Start of the current period to build
                                  * introduction points. */
 -  int n_intro_circuits_launched; /**< count of intro circuits we have
 +  int n_intro_circuits_launched; /**< Count of intro circuits we have
                                    * established in this period. */
    rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
    time_t desc_is_dirty; /**< Time at which changes to the hidden service
@@@ -101,8 -90,7 +101,8 @@@ num_rend_services(void
  static void
  rend_authorized_client_free(rend_authorized_client_t *client)
  {
 -  if (!client) return;
 +  if (!client)
 +    return;
    if (client->client_key)
      crypto_free_pk_env(client->client_key);
    tor_free(client->client_name);
@@@ -121,9 -109,7 +121,9 @@@ rend_authorized_client_strmap_item_free
  static void
  rend_service_free(rend_service_t *service)
  {
 -  if (!service) return;
 +  if (!service)
 +    return;
 +
    tor_free(service->directory);
    SMARTLIST_FOREACH(service->ports, void*, p, tor_free(p));
    smartlist_free(service->ports);
@@@ -134,14 -120,15 +134,14 @@@
        rend_intro_point_free(intro););
      smartlist_free(service->intro_nodes);
    }
 -  if (service->desc)
 -    rend_service_descriptor_free(service->desc);
 +
 +  rend_service_descriptor_free(service->desc);
    if (service->clients) {
      SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, c,
        rend_authorized_client_free(c););
      smartlist_free(service->clients);
    }
 -  if (service->accepted_intros)
 -    digestmap_free(service->accepted_intros, _tor_free);
 +  digestmap_free(service->accepted_intros, _tor_free);
    tor_free(service);
  }
  
@@@ -150,9 -137,9 +150,9 @@@
  void
  rend_service_free_all(void)
  {
 -  if (!rend_service_list) {
 +  if (!rend_service_list)
      return;
 -  }
 +
    SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
                      rend_service_free(ptr));
    smartlist_free(rend_service_list);
@@@ -169,6 -156,36 +169,6 @@@ rend_add_service(rend_service_t *servic
  
    service->intro_nodes = smartlist_create();
  
 -  /* If the service is configured to publish unversioned (v0) and versioned
 -   * descriptors (v2 or higher), split it up into two separate services
 -   * (unless it is configured to perform client authorization). */
 -  if (service->descriptor_version == -1) {
 -    if (service->auth_type == REND_NO_AUTH) {
 -      rend_service_t *v0_service = tor_malloc_zero(sizeof(rend_service_t));
 -      v0_service->directory = tor_strdup(service->directory);
 -      v0_service->ports = smartlist_create();
 -      SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, {
 -        rend_service_port_config_t *copy =
 -          tor_malloc_zero(sizeof(rend_service_port_config_t));
 -        memcpy(copy, p, sizeof(rend_service_port_config_t));
 -        smartlist_add(v0_service->ports, copy);
 -      });
 -      v0_service->intro_period_started = service->intro_period_started;
 -      v0_service->descriptor_version = 0; /* Unversioned descriptor. */
 -      v0_service->auth_type = REND_NO_AUTH;
 -      rend_add_service(v0_service);
 -    }
 -
 -    service->descriptor_version = 2; /* Versioned descriptor. */
 -  }
 -
 -  if (service->auth_type != REND_NO_AUTH && !service->descriptor_version) {
 -    log_warn(LD_CONFIG, "Hidden service with client authorization and "
 -                        "version 0 descriptors configured; ignoring.");
 -    rend_service_free(service);
 -    return;
 -  }
 -
    if (service->auth_type != REND_NO_AUTH &&
        smartlist_len(service->clients) == 0) {
      log_warn(LD_CONFIG, "Hidden service with client authorization but no "
@@@ -280,7 -297,7 +280,7 @@@ rend_config_services(or_options_t *opti
  
    for (line = options->RendConfigLines; line; line = line->next) {
      if (!strcasecmp(line->key, "HiddenServiceDir")) {
 -      if (service) {
 +      if (service) { /* register the one we just finished parsing */
          if (validate_only)
            rend_service_free(service);
          else
@@@ -290,6 -307,7 +290,6 @@@
        service->directory = tor_strdup(line->value);
        service->ports = smartlist_create();
        service->intro_period_started = time(NULL);
 -      service->descriptor_version = -1; /**< All descriptor versions. */
        continue;
      }
      if (!service) {
@@@ -415,13 -433,35 +415,13 @@@
          return -1;
        }
      } else {
 -      smartlist_t *versions;
 -      char *version_str;
 -      int i, version, ver_ok=1, versions_bitmask = 0;
        tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
 -      versions = smartlist_create();
 -      smartlist_split_string(versions, line->value, ",",
 -                             SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 -      for (i = 0; i < smartlist_len(versions); i++) {
 -        version_str = smartlist_get(versions, i);
 -        if (strlen(version_str) != 1 || strspn(version_str, "02") != 1) {
 -          log_warn(LD_CONFIG,
 -                   "HiddenServiceVersion can only be 0 and/or 2.");
 -          SMARTLIST_FOREACH(versions, char *, cp, tor_free(cp));
 -          smartlist_free(versions);
 -          rend_service_free(service);
 -          return -1;
 -        }
 -        version = (int)tor_parse_long(version_str, 10, 0, INT_MAX, &ver_ok,
 -                                      NULL);
 -        if (!ver_ok)
 -          continue;
 -        versions_bitmask |= 1 << version;
 +      if (strcmp(line->value, "2")) {
 +        log_warn(LD_CONFIG,
 +                 "The only supported HiddenServiceVersion is 2.");
 +        rend_service_free(service);
 +        return -1;
        }
 -      /* If exactly one version is set, change descriptor_version to that
 -       * value; otherwise leave it at -1. */
 -      if (versions_bitmask == 1 << 0) service->descriptor_version = 0;
 -      if (versions_bitmask == 1 << 2) service->descriptor_version = 2;
 -      SMARTLIST_FOREACH(versions, char *, cp, tor_free(cp));
 -      smartlist_free(versions);
      }
    }
    if (service) {
@@@ -443,7 -483,8 +443,7 @@@
       * probably ok? */
      SMARTLIST_FOREACH(rend_service_list, rend_service_t *, new, {
        SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, {
 -        if (!strcmp(old->directory, new->directory) &&
 -            old->descriptor_version == new->descriptor_version) {
 +        if (!strcmp(old->directory, new->directory)) {
            smartlist_add_all(new->intro_nodes, old->intro_nodes);
            smartlist_clear(old->intro_nodes);
            smartlist_add(surviving_services, old);
@@@ -466,16 -507,18 +466,16 @@@
          tor_assert(oc->rend_data);
          SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, {
            if (!memcmp(ptr->pk_digest, oc->rend_data->rend_pk_digest,
 -                      DIGEST_LEN) &&
 -              ptr->descriptor_version == oc->rend_data->rend_desc_version) {
 +                      DIGEST_LEN)) {
              keep_it = 1;
              break;
            }
          });
          if (keep_it)
            continue;
 -        log_info(LD_REND, "Closing intro point %s for service %s version %d.",
 -                 safe_str(oc->build_state->chosen_exit->nickname),
 -                 oc->rend_data->onion_address,
 -                 oc->rend_data->rend_desc_version);
 +        log_info(LD_REND, "Closing intro point %s for service %s.",
 +                 safe_str_client(oc->build_state->chosen_exit->nickname),
 +                 oc->rend_data->onion_address);
          circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
          /* XXXX Is there another reason we should use here? */
        }
@@@ -498,13 -541,14 +498,13 @@@ rend_service_update_descriptor(rend_ser
    rend_service_descriptor_t *d;
    origin_circuit_t *circ;
    int i;
 -  if (service->desc) {
 -    rend_service_descriptor_free(service->desc);
 -    service->desc = NULL;
 -  }
 +
 +  rend_service_descriptor_free(service->desc);
 +  service->desc = NULL;
 +
    d = service->desc = tor_malloc_zero(sizeof(rend_service_descriptor_t));
    d->pk = crypto_pk_dup_key(service->private_key);
    d->timestamp = time(NULL);
 -  d->version = service->descriptor_version;
    d->intro_nodes = smartlist_create();
    /* Support intro protocols 2 and 3. */
    d->protocols = (1 << 2) + (1 << 3);
@@@ -512,7 -556,7 +512,7 @@@
    for (i = 0; i < smartlist_len(service->intro_nodes); ++i) {
      rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i);
      rend_intro_point_t *intro_desc;
 -    circ = find_intro_circuit(intro_svc, service->pk_digest, d->version);
 +    circ = find_intro_circuit(intro_svc, service->pk_digest);
      if (!circ || circ->_base.purpose != CIRCUIT_PURPOSE_S_INTRO)
        continue;
  
@@@ -753,15 -797,17 +753,15 @@@ rend_service_load_keys(void
    return r;
  }
  
 -/** Return the service whose public key has a digest of <b>digest</b> and
 - * which publishes the given descriptor <b>version</b>.  Return NULL if no
 - * such service exists.
 +/** Return the service whose public key has a digest of <b>digest</b>, or
 + * NULL if no such service exists.
   */
  static rend_service_t *
 -rend_service_get_by_pk_digest_and_version(const char* digest,
 -                                          uint8_t version)
 +rend_service_get_by_pk_digest(const char* digest)
  {
    SMARTLIST_FOREACH(rend_service_list, rend_service_t*, s,
 -                    if (!memcmp(s->pk_digest,digest,DIGEST_LEN) &&
 -                        s->descriptor_version == version) return s);
 +                    if (!memcmp(s->pk_digest,digest,DIGEST_LEN))
 +                        return s);
    return NULL;
  }
  
@@@ -898,16 -944,21 +898,16 @@@ rend_service_introduce(origin_circuit_
    }
  
    /* look up service depending on circuit. */
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.",
               escaped(serviceid));
      return -1;
    }
  
 -  /* if descriptor version is 2, use intro key instead of service key. */
 -  if (circuit->rend_data->rend_desc_version == 0) {
 -    intro_key = service->private_key;
 -  } else {
 -    intro_key = circuit->intro_key;
 -  }
 +  /* use intro key instead of service key. */
 +  intro_key = circuit->intro_key;
  
    /* first DIGEST_LEN bytes of request is intro or service pk digest */
    crypto_pk_get_digest(intro_key, intro_key_digest);
@@@ -928,7 -979,8 +928,8 @@@
    /* Next N bytes is encrypted with service key */
    note_crypto_pk_op(REND_SERVER);
    r = crypto_pk_private_hybrid_decrypt(
-        intro_key,buf,(char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
+        intro_key,buf,sizeof(buf),
+        (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
         PK_PKCS1_OAEP_PADDING,1);
    if (r<0) {
      log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell.");
@@@ -937,7 -989,7 +938,7 @@@
    len = r;
    if (*buf == 3) {
      /* Version 3 INTRODUCE2 cell. */
 -    time_t ts = 0, now = time(NULL);
 +    time_t ts = 0;
      v3_shift = 1;
      auth_type = buf[1];
      switch (auth_type) {
@@@ -1031,7 -1083,7 +1032,7 @@@
      router = router_get_by_nickname(rp_nickname, 0);
      if (!router) {
        log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.",
 -               escaped_safe_str(rp_nickname));
 +               escaped_safe_str_client(rp_nickname));
        /* XXXX Add a no-such-router reason? */
        reason = END_CIRC_REASON_TORPROTOCOL;
        goto err;
@@@ -1106,8 -1158,7 +1107,8 @@@
      reason = END_CIRC_REASON_INTERNAL;
      goto err;
    }
 -  if (crypto_dh_compute_secret(dh, ptr+REND_COOKIE_LEN, DH_KEY_LEN, keys,
 +  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN,
 +                               DH_KEY_LEN, keys,
                                 DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
      log_warn(LD_BUG, "Internal error: couldn't complete DH handshake");
      reason = END_CIRC_REASON_INTERNAL;
@@@ -1117,7 -1168,7 +1118,7 @@@
    circ_needs_uptime = rend_service_requires_uptime(service);
  
    /* help predict this next time */
 -  rep_hist_note_used_internal(time(NULL), circ_needs_uptime, 1);
 +  rep_hist_note_used_internal(now, circ_needs_uptime, 1);
  
    /* Launch a circuit to alice's chosen rendezvous point.
     */
@@@ -1133,16 -1184,14 +1134,16 @@@
    if (!launched) { /* give up */
      log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous "
               "point %s for service %s.",
 -             escaped_safe_str(extend_info->nickname), serviceid);
 +             escaped_safe_str_client(extend_info->nickname),
 +             serviceid);
      reason = END_CIRC_REASON_CONNECTFAILED;
      goto err;
    }
    log_info(LD_REND,
             "Accepted intro; launching circuit to %s "
             "(cookie %s) for service %s.",
 -           escaped_safe_str(extend_info->nickname), hexcookie, serviceid);
 +           escaped_safe_str_client(extend_info->nickname),
 +           hexcookie, serviceid);
    tor_assert(launched->build_state);
    /* Fill in the circuit's state. */
    launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
@@@ -1152,10 -1201,11 +1153,10 @@@
    memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
    strlcpy(launched->rend_data->onion_address, service->service_id,
            sizeof(launched->rend_data->onion_address));
    launched->build_state->pending_final_cpath = cpath =
      tor_malloc_zero(sizeof(crypt_path_t));
    cpath->magic = CRYPT_PATH_MAGIC;
 -  launched->build_state->expiry_time = time(NULL) + MAX_REND_TIMEOUT;
 +  launched->build_state->expiry_time = now + MAX_REND_TIMEOUT;
  
    cpath->dh_handshake_state = dh;
    dh = NULL;
@@@ -1237,7 -1287,7 +1238,7 @@@ rend_service_launch_establish_intro(ren
  
    log_info(LD_REND,
             "Launching circuit to introduction point %s for service %s",
 -           escaped_safe_str(intro->extend_info->nickname),
 +           escaped_safe_str_client(intro->extend_info->nickname),
             service->service_id);
  
    rep_hist_note_used_internal(time(NULL), 1, 0);
@@@ -1250,7 -1300,7 +1251,7 @@@
    if (!launched) {
      log_info(LD_REND,
               "Can't launch circuit to establish introduction at %s.",
 -             escaped_safe_str(intro->extend_info->nickname));
 +             escaped_safe_str_client(intro->extend_info->nickname));
      return -1;
    }
  
@@@ -1273,16 -1323,18 +1274,16 @@@
    strlcpy(launched->rend_data->onion_address, service->service_id,
            sizeof(launched->rend_data->onion_address));
    memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN);
 -  launched->rend_data->rend_desc_version = service->descriptor_version;
 -  if (service->descriptor_version == 2)
 -    launched->intro_key = crypto_pk_dup_key(intro->intro_key);
 +  launched->intro_key = crypto_pk_dup_key(intro->intro_key);
    if (launched->_base.state == CIRCUIT_STATE_OPEN)
      rend_service_intro_has_opened(launched);
    return 0;
  }
  
  /** Return the number of introduction points that are or have been
 - * established for the given service address and rendezvous version. */
 + * established for the given service address in <b>query</b>. */
  static int
 -count_established_intro_points(const char *query, int rend_version)
 +count_established_intro_points(const char *query)
  {
    int num_ipos = 0;
    circuit_t *circ;
@@@ -1293,6 -1345,7 +1294,6 @@@
           circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
        origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
        if (oc->rend_data &&
 -          oc->rend_data->rend_desc_version == rend_version &&
            !rend_cmp_service_ids(query, oc->rend_data->onion_address))
          num_ipos++;
      }
@@@ -1322,8 -1375,9 +1323,8 @@@ rend_service_intro_has_opened(origin_ci
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                  circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
  
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
               serviceid, circuit->_base.n_circ_id);
@@@ -1333,7 -1387,8 +1334,7 @@@
  
    /* If we already have enough introduction circuits for this service,
     * redefine this one as a general circuit. */
 -  if (count_established_intro_points(serviceid,
 -          circuit->rend_data->rend_desc_version) > NUM_INTRO_POINTS) {
 +  if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) {
      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
               "circuit, but we already have enough. Redefining purpose to "
               "general.");
@@@ -1346,8 -1401,13 +1347,8 @@@
             "Established circuit %d as introduction point for service %s",
             circuit->_base.n_circ_id, serviceid);
  
 -  /* If the introduction point will not be used in an unversioned
 -   * descriptor, use the intro key instead of the service key in
 -   * ESTABLISH_INTRO. */
 -  if (service->descriptor_version == 0)
 -    intro_key = service->private_key;
 -  else
 -    intro_key = circuit->intro_key;
 +  /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
 +  intro_key = circuit->intro_key;
    /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
    r = crypto_pk_asn1_encode(intro_key, buf+2,
                              RELAY_PAYLOAD_SIZE-2);
@@@ -1365,7 -1425,8 +1366,8 @@@
      goto err;
    len += 20;
    note_crypto_pk_op(REND_SERVER);
-   r = crypto_pk_private_sign_digest(intro_key, buf+len, buf, len);
+   r = crypto_pk_private_sign_digest(intro_key, buf+len, sizeof(buf)-len,
+                                     buf, len);
    if (r<0) {
      log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
      reason = END_CIRC_REASON_INTERNAL;
@@@ -1407,8 -1468,9 +1409,8 @@@ rend_service_intro_established(origin_c
      goto err;
    }
    tor_assert(circuit->rend_data);
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Unknown service on introduction circuit %d.",
               circuit->_base.n_circ_id);
@@@ -1458,8 -1520,9 +1460,8 @@@ rend_service_rendezvous_has_opened(orig
             "cookie %s for service %s",
             circuit->_base.n_circ_id, hexcookie, serviceid);
  
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
               "introduction circuit.");
@@@ -1515,12 -1578,13 +1517,12 @@@
   */
  
  /** Return the (possibly non-open) introduction circuit ending at
 - * <b>intro</b> for the service whose public key is <b>pk_digest</b> and
 - * which publishes descriptor of version <b>desc_version</b>.  Return
 - * NULL if no such service is found.
 + * <b>intro</b> for the service whose public key is <b>pk_digest</b>.
 + * (<b>desc_version</b> is ignored). Return NULL if no such service is
 + * found.
   */
  static origin_circuit_t *
 -find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest,
 -                   int desc_version)
 +find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
  {
    origin_circuit_t *circ = NULL;
  
@@@ -1529,7 -1593,8 +1531,7 @@@
                                                    CIRCUIT_PURPOSE_S_INTRO))) {
      if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                  intro->extend_info->identity_digest, DIGEST_LEN) &&
 -        circ->rend_data &&
 -        circ->rend_data->rend_desc_version == desc_version) {
 +        circ->rend_data) {
        return circ;
      }
    }
@@@ -1539,7 -1604,8 +1541,7 @@@
                                          CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
      if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                  intro->extend_info->identity_digest, DIGEST_LEN) &&
 -        circ->rend_data &&
 -        circ->rend_data->rend_desc_version == desc_version) {
 +        circ->rend_data) {
        return circ;
      }
    }
@@@ -1572,7 -1638,6 +1574,7 @@@ directory_post_to_hs_dir(rend_service_d
      }
      for (j = 0; j < smartlist_len(responsible_dirs); j++) {
        char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
 +      char *hs_dir_ip;
        hs_dir = smartlist_get(responsible_dirs, j);
        if (smartlist_digest_isin(renddesc->successful_uploads,
                                  hs_dir->identity_digest))
@@@ -1594,18 -1659,15 +1596,18 @@@
                                                strlen(desc->desc_str), 0);
        base32_encode(desc_id_base32, sizeof(desc_id_base32),
                      desc->desc_id, DIGEST_LEN);
 +      hs_dir_ip = tor_dup_ip(hs_dir->addr);
        log_info(LD_REND, "Sending publish request for v2 descriptor for "
                          "service '%s' with descriptor ID '%s' with validity "
                          "of %d seconds to hidden service directory '%s' on "
 -                        "port %d.",
 -               safe_str(service_id),
 -               safe_str(desc_id_base32),
 +                        "%s:%d.",
 +               safe_str_client(service_id),
 +               safe_str_client(desc_id_base32),
                 seconds_valid,
                 hs_dir->nickname,
 -               hs_dir->dir_port);
 +               hs_dir_ip,
 +               hs_dir->or_port);
 +      tor_free(hs_dir_ip);
        /* Remember successful upload to this router for next time. */
        if (!smartlist_digest_isin(successful_uploads, hs_dir->identity_digest))
          smartlist_add(successful_uploads, hs_dir->identity_digest);
@@@ -1635,8 -1697,9 +1637,8 @@@
    smartlist_free(successful_uploads);
  }
  
 -/** Encode and sign up-to-date v0 and/or v2 service descriptors for
 - * <b>service</b>, and upload it/them to all the dirservers/to the
 - * responsible hidden service directories.
 +/** Encode and sign an up-to-date service descriptor for <b>service</b>,
 + * and upload it/them to the responsible hidden service directories.
   */
  static void
  upload_service_descriptor(rend_service_t *service)
@@@ -1648,8 -1711,35 +1650,8 @@@
  
    rendpostperiod = get_options()->RendPostPeriod;
  
 -  /* Upload unversioned (v0) descriptor? */
 -  if (service->descriptor_version == 0 &&
 -      get_options()->PublishHidServDescriptors) {
 -    char *desc;
 -    size_t desc_len;
 -    /* Encode the descriptor. */
 -    if (rend_encode_service_descriptor(service->desc,
 -                                       service->private_key,
 -                                       &desc, &desc_len)<0) {
 -      log_warn(LD_BUG, "Internal error: couldn't encode service descriptor; "
 -               "not uploading.");
 -      return;
 -    }
 -
 -    /* Post it to the dirservers */
 -    rend_get_service_id(service->desc->pk, serviceid);
 -    log_info(LD_REND, "Sending publish request for hidden service %s",
 -             serviceid);
 -    directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_RENDDESC,
 -                                 ROUTER_PURPOSE_GENERAL,
 -                                 HIDSERV_AUTHORITY, desc, desc_len, 0);
 -    tor_free(desc);
 -    service->next_upload_time = now + rendpostperiod;
 -    uploaded = 1;
 -  }
 -
 -  /* Upload v2 descriptor? */
 -  if (service->descriptor_version == 2 &&
 -      get_options()->PublishHidServDescriptors) {
 +  /* Upload descriptor? */
 +  if (get_options()->PublishHidServDescriptors) {
      networkstatus_t *c = networkstatus_get_latest_consensus();
      if (c && smartlist_len(c->routerstatus_list) > 0) {
        int seconds_valid, i, j, num_descs;
@@@ -1788,7 -1878,8 +1790,7 @@@ rend_services_introduce(void
      for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
        intro = smartlist_get(service->intro_nodes, j);
        router = router_get_by_digest(intro->extend_info->identity_digest);
 -      if (!router || !find_intro_circuit(intro, service->pk_digest,
 -                                         service->descriptor_version)) {
 +      if (!router || !find_intro_circuit(intro, service->pk_digest)) {
          log_info(LD_REND,"Giving up on %s as intro point for %s.",
                   intro->extend_info->nickname, service->service_id);
          if (service->desc) {
@@@ -1840,7 -1931,7 +1842,7 @@@
        router_crn_flags_t flags = CRN_NEED_UPTIME;
        if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION)
          flags |= CRN_ALLOW_INVALID;
 -      router = router_choose_random_node(NULL, intro_routers,
 +      router = router_choose_random_node(intro_routers,
                                           options->ExcludeNodes, flags);
        if (!router) {
          log_warn(LD_REND,
@@@ -1852,8 -1943,10 +1854,8 @@@
        smartlist_add(intro_routers, router);
        intro = tor_malloc_zero(sizeof(rend_intro_point_t));
        intro->extend_info = extend_info_from_router(router);
 -      if (service->descriptor_version == 2) {
 -        intro->intro_key = crypto_new_pk_env();
 -        tor_assert(!crypto_pk_generate_key(intro->intro_key));
 -      }
 +      intro->intro_key = crypto_new_pk_env();
 +      tor_assert(!crypto_pk_generate_key(intro->intro_key));
        smartlist_add(service->intro_nodes, intro);
        log_info(LD_REND, "Picked router %s as an intro point for %s.",
                 router->nickname, service->service_id);
@@@ -1946,7 -2039,8 +1948,7 @@@ rend_consider_descriptor_republication(
  
    for (i=0; i < smartlist_len(rend_service_list); ++i) {
      service = smartlist_get(rend_service_list, i);
 -    if (service->descriptor_version && service->desc &&
 -        !service->desc->all_uploads_performed) {
 +    if (service->desc && !service->desc->all_uploads_performed) {
        /* If we failed in uploading a descriptor last time, try again *without*
         * updating the descriptor's contents. */
        upload_service_descriptor(service);
@@@ -1972,9 -2066,10 +1974,9 @@@ rend_service_dump_stats(int severity
          service->directory);
      for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
        intro = smartlist_get(service->intro_nodes, j);
 -      safe_name = safe_str(intro->extend_info->nickname);
 +      safe_name = safe_str_client(intro->extend_info->nickname);
  
 -      circ = find_intro_circuit(intro, service->pk_digest,
 -                                service->descriptor_version);
 +      circ = find_intro_circuit(intro, service->pk_digest);
        if (!circ) {
          log(severity, LD_GENERAL, "  Intro point %d at %s: no circuit",
              j, safe_name);
@@@ -2005,8 -2100,9 +2007,8 @@@ rend_service_set_connection_addr_port(e
    log_debug(LD_REND,"beginning to hunt for addr/port");
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                  circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 -  service = rend_service_get_by_pk_digest_and_version(
 -                circ->rend_data->rend_pk_digest,
 -                circ->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circ->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
               "rendezvous circuit %d; closing.",
diff --combined src/or/routerlist.c
index 253b787,7c8e36e..e29b4c4
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@@ -12,25 -12,6 +12,25 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  // #define DEBUG_ROUTERLIST
  
@@@ -45,8 -26,8 +45,8 @@@ static void mark_all_trusteddirservers_
  static int router_nickname_matches(routerinfo_t *router, const char *nickname);
  static void trusted_dir_server_free(trusted_dir_server_t *ds);
  static void launch_router_descriptor_downloads(smartlist_t *downloadable,
 +                                               routerstatus_t *source,
                                                 time_t now);
 -static void update_consensus_router_descriptor_downloads(time_t now);
  static int signed_desc_digest_is_recognized(signed_descriptor_t *desc);
  static void update_router_have_minimum_dir_info(void);
  static const char *signed_descriptor_get_body_impl(signed_descriptor_t *desc,
@@@ -175,24 -156,21 +175,24 @@@ already_have_cert(authority_cert_t *cer
  
  /** Load a bunch of new key certificates from the string <b>contents</b>.  If
   * <b>from_store</b> is true, the certificates are from the cache, and we
 - * don't need to flush them to disk.  If <b>from_store</b> is false, we need
 - * to flush any changed certificates to disk.  Return 0 on success, -1 on
 - * failure. */
 + * don't need to flush them to disk. If <b>flush</b> is true, we need
 + * to flush any changed certificates to disk now.  Return 0 on success, -1
 + * if any certs fail to parse. */
  int
  trusted_dirs_load_certs_from_string(const char *contents, int from_store,
                                      int flush)
  {
    trusted_dir_server_t *ds;
    const char *s, *eos;
 +  int failure_code = 0;
  
    for (s = contents; *s; s = eos) {
      authority_cert_t *cert = authority_cert_parse_from_string(s, &eos);
      cert_list_t *cl;
 -    if (!cert)
 +    if (!cert) {
 +      failure_code = -1;
        break;
 +    }
      ds = trusteddirserver_get_by_v3_auth_digest(
                                         cert->cache_info.identity_digest);
      log_debug(LD_DIR, "Parsed certificate for %s",
@@@ -203,15 -181,15 +203,15 @@@
        log_info(LD_DIR, "Skipping %s certificate for %s that we "
                 "already have.",
                 from_store ? "cached" : "downloaded",
 -               ds ? ds->nickname : "??");
 +               ds ? ds->nickname : "an old or new authority");
  
        /* a duplicate on a download should be treated as a failure, since it
         * probably means we wanted a different secret key or we are trying to
         * replace an expired cert that has not in fact been updated. */
        if (!from_store) {
 -        log_warn(LD_DIR, "Got a certificate for %s that we already have. "
 -                 "Maybe they haven't updated it.  Waiting for a while.",
 -                 ds ? ds->nickname : "??");
 +        log_warn(LD_DIR, "Got a certificate for %s, but we already have it. "
 +                 "Maybe they haven't updated it. Waiting for a while.",
 +                 ds ? ds->nickname : "an old or new authority");
          authority_cert_dl_failed(cert->cache_info.identity_digest, 404);
        }
  
@@@ -246,7 -224,7 +246,7 @@@
             ds->dir_port != cert->dir_port)) {
          char *a = tor_dup_ip(cert->addr);
          log_notice(LD_DIR, "Updating address for directory authority %s "
 -                   "from %s:%d to %s:%d based on in certificate.",
 +                   "from %s:%d to %s:%d based on certificate.",
                     ds->nickname, ds->address, (int)ds->dir_port,
                     a, cert->dir_port);
          tor_free(a);
@@@ -263,11 -241,8 +263,11 @@@
    if (flush)
      trusted_dirs_flush_certs_to_disk();
  
 +  /* call this even if failure_code is <0, since some certs might have
 +   * succeeded. */
    networkstatus_note_certs_arrived();
 -  return 0;
 +
 +  return failure_code;
  }
  
  /** Save all v3 key certificates to the cached-certs file. */
@@@ -440,23 -415,6 +440,23 @@@ authority_cert_dl_failed(const char *id
    download_status_failed(&cl->dl_status, status);
  }
  
 +/** Return true iff when we've been getting enough failures when trying to
 + * download the certificate with ID digest <b>id_digest</b> that we're willing
 + * to start bugging the user about it. */
 +int
 +authority_cert_dl_looks_uncertain(const char *id_digest)
 +{
 +#define N_AUTH_CERT_DL_FAILURES_TO_BUG_USER 2
 +  cert_list_t *cl;
 +  int n_failures;
 +  if (!trusted_dir_certs ||
 +      !(cl = digestmap_get(trusted_dir_certs, id_digest)))
 +    return 0;
 +
 +  n_failures = download_status_get_n_failures(&cl->dl_status);
 +  return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER;
 +}
 +
  /** How many times will we try to fetch a certificate before giving up? */
  #define MAX_CERT_DL_FAILURES 8
  
@@@ -484,18 -442,17 +484,18 @@@ authority_certs_fetch_missing(networkst
  
    list_pending_downloads(pending, DIR_PURPOSE_FETCH_CERTIFICATE, "fp/");
    if (status) {
 -    SMARTLIST_FOREACH(status->voters, networkstatus_voter_info_t *, voter,
 -      {
 -        if (tor_digest_is_zero(voter->signing_key_digest))
 -          continue; /* This authority never signed this consensus, so don't
 -                     * go looking for a cert with key digest 0000000000. */
 -        if (!cache &&
 -            !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
 -          continue; /* We are not a cache, and we don't know this authority.*/
 -        cl = get_cert_list(voter->identity_digest);
 +    SMARTLIST_FOREACH_BEGIN(status->voters, networkstatus_voter_info_t *,
 +                            voter) {
 +      if (!smartlist_len(voter->sigs))
 +        continue; /* This authority never signed this consensus, so don't
 +                   * go looking for a cert with key digest 0000000000. */
 +      if (!cache &&
 +          !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
 +        continue; /* We are not a cache, and we don't know this authority.*/
 +      cl = get_cert_list(voter->identity_digest);
 +      SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
          cert = authority_cert_get_by_digests(voter->identity_digest,
 -                                             voter->signing_key_digest);
 +                                             sig->signing_key_digest);
          if (cert) {
            if (now < cert->expires)
              download_status_reset(&cl->dl_status);
@@@ -506,36 -463,37 +506,36 @@@
              !digestmap_get(pending, voter->identity_digest)) {
            log_notice(LD_DIR, "We're missing a certificate from authority "
                       "with signing key %s: launching request.",
 -                     hex_str(voter->signing_key_digest, DIGEST_LEN));
 -          smartlist_add(missing_digests, voter->identity_digest);
 +                     hex_str(sig->signing_key_digest, DIGEST_LEN));
 +          smartlist_add(missing_digests, sig->identity_digest);
          }
 -      });
 +      } SMARTLIST_FOREACH_END(sig);
 +    } SMARTLIST_FOREACH_END(voter);
    }
 -  SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds,
 -    {
 -      int found = 0;
 -      if (!(ds->type & V3_AUTHORITY))
 -        continue;
 -      if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
 -        continue;
 -      cl = get_cert_list(ds->v3_identity_digest);
 -      SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert,
 -        {
 -          if (!ftime_definitely_after(now, cert->expires)) {
 -            /* It's not expired, and we weren't looking for something to
 -             * verify a consensus with.  Call it done. */
 -            download_status_reset(&cl->dl_status);
 -            found = 1;
 -            break;
 -          }
 -        });
 -      if (!found &&
 -          download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
 -          !digestmap_get(pending, ds->v3_identity_digest)) {
 -        log_notice(LD_DIR, "No current certificate known for authority %s; "
 -                   "launching request.", ds->nickname);
 -        smartlist_add(missing_digests, ds->v3_identity_digest);
 +  SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) {
 +    int found = 0;
 +    if (!(ds->type & V3_AUTHORITY))
 +      continue;
 +    if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
 +      continue;
 +    cl = get_cert_list(ds->v3_identity_digest);
 +    SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, {
 +      if (!ftime_definitely_after(now, cert->expires)) {
 +        /* It's not expired, and we weren't looking for something to
 +         * verify a consensus with.  Call it done. */
 +        download_status_reset(&cl->dl_status);
 +        found = 1;
 +        break;
        }
      });
 +    if (!found &&
 +        download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
 +        !digestmap_get(pending, ds->v3_identity_digest)) {
 +      log_notice(LD_DIR, "No current certificate known for authority %s; "
 +                 "launching request.", ds->nickname);
 +        smartlist_add(missing_digests, ds->v3_identity_digest);
 +    }
 +  } SMARTLIST_FOREACH_END(ds);
  
    if (!smartlist_len(missing_digests)) {
      goto done;
@@@ -791,7 -749,8 +791,7 @@@ router_rebuild_store(int flags, desc_st
    store->journal_len = 0;
    store->bytes_dropped = 0;
   done:
 -  if (signed_descriptors)
 -    smartlist_free(signed_descriptors);
 +  smartlist_free(signed_descriptors);
    tor_free(fname);
    tor_free(fname_tmp);
    if (chunk_list) {
@@@ -1009,9 -968,8 +1009,9 @@@ router_get_trusteddirserver_by_digest(c
    return NULL;
  }
  
 -/** Return the trusted_dir_server_t for the directory authority whose identity
 - * key hashes to <b>digest</b>, or NULL if no such authority is known.
 +/** Return the trusted_dir_server_t for the directory authority whose
 + * v3 identity key hashes to <b>digest</b>, or NULL if no such authority
 + * is known.
   */
  trusted_dir_server_t *
  trusteddirserver_get_by_v3_auth_digest(const char *digest)
@@@ -1131,10 -1089,9 +1131,10 @@@ router_pick_directory_server_impl(autho
    } SMARTLIST_FOREACH_END(status);
  
    if (smartlist_len(tunnel)) {
 -    result = routerstatus_sl_choose_by_bandwidth(tunnel);
 +    result = routerstatus_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR);
    } else if (smartlist_len(overloaded_tunnel)) {
 -    result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel);
 +    result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel,
 +                                                 WEIGHT_FOR_DIR);
    } else if (smartlist_len(trusted_tunnel)) {
      /* FFFF We don't distinguish between trusteds and overloaded trusteds
       * yet. Maybe one day we should. */
@@@ -1142,10 -1099,9 +1142,10 @@@
       * is a feature, but it could easily be a bug. -RD */
      result = smartlist_choose(trusted_tunnel);
    } else if (smartlist_len(direct)) {
 -    result = routerstatus_sl_choose_by_bandwidth(direct);
 +    result = routerstatus_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR);
    } else if (smartlist_len(overloaded_direct)) {
 -    result = routerstatus_sl_choose_by_bandwidth(overloaded_direct);
 +    result = routerstatus_sl_choose_by_bandwidth(overloaded_direct,
 +                                                 WEIGHT_FOR_DIR);
    } else {
      result = smartlist_choose(trusted_direct);
    }
@@@ -1274,13 -1230,6 +1274,13 @@@ mark_all_trusteddirservers_up(void
    router_dir_info_changed();
  }
  
 +/** Return true iff r1 and r2 have the same address and OR port. */
 +int
 +routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2)
 +{
 +  return r1->addr == r2->addr && r1->or_port == r2->or_port;
 +}
 +
  /** Reset all internal variables used to count failed downloads of network
   * status objects. */
  void
@@@ -1572,29 -1521,6 +1572,29 @@@ router_get_advertised_bandwidth_capped(
    return result;
  }
  
 +/** When weighting bridges, enforce these values as lower and upper
 + * bound for believable bandwidth, because there is no way for us
 + * to verify a bridge's bandwidth currently. */
 +#define BRIDGE_MIN_BELIEVABLE_BANDWIDTH 20000  /* 20 kB/sec */
 +#define BRIDGE_MAX_BELIEVABLE_BANDWIDTH 100000 /* 100 kB/sec */
 +
 +/** Return the smaller of the router's configured BandwidthRate
 + * and its advertised capacity, making sure to stay within the
 + * interval between bridge-min-believe-bw and
 + * bridge-max-believe-bw. */
 +static uint32_t
 +bridge_get_advertised_bandwidth_bounded(routerinfo_t *router)
 +{
 +  uint32_t result = router->bandwidthcapacity;
 +  if (result > router->bandwidthrate)
 +    result = router->bandwidthrate;
 +  if (result > BRIDGE_MAX_BELIEVABLE_BANDWIDTH)
 +    result = BRIDGE_MAX_BELIEVABLE_BANDWIDTH;
 +  else if (result < BRIDGE_MIN_BELIEVABLE_BANDWIDTH)
 +    result = BRIDGE_MIN_BELIEVABLE_BANDWIDTH;
 +  return result;
 +}
 +
  /** Return bw*1000, unless bw*1000 would overflow, in which case return
   * INT32_MAX. */
  static INLINE int32_t
@@@ -1605,219 -1531,6 +1605,219 @@@ kb_to_bytes(uint32_t bw
  
  /** Helper function:
   * choose a random element of smartlist <b>sl</b>, weighted by
 + * the advertised bandwidth of each element using the consensus
 + * bandwidth weights.
 + *
 + * If <b>statuses</b> is zero, then <b>sl</b> is a list of
 + * routerinfo_t's. Otherwise it's a list of routerstatus_t's.
 + *
 + * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all
 + * nodes' bandwidth equally regardless of their Exit status, since there may
 + * be some in the list because they exit to obscure ports. If
 + * <b>rule</b>==NO_WEIGHTING, we're picking a non-exit node: weight
 + * exit-node's bandwidth less depending on the smallness of the fraction of
 + * Exit-to-total bandwidth.  If <b>rule</b>==WEIGHT_FOR_GUARD, we're picking a
 + * guard node: consider all guard's bandwidth equally. Otherwise, weight
 + * guards proportionally less.
 + */
 +static void *
 +smartlist_choose_by_bandwidth_weights(smartlist_t *sl,
 +                                      bandwidth_weight_rule_t rule,
 +                                      int statuses)
 +{
 +  int64_t weight_scale;
 +  int64_t rand_bw;
 +  double Wg = -1, Wm = -1, We = -1, Wd = -1;
 +  double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1;
 +  double weighted_bw = 0;
 +  double *bandwidths;
 +  double tmp = 0;
 +  unsigned int i;
 +  int have_unknown = 0; /* true iff sl contains element not in consensus. */
 +
 +  /* Can't choose exit and guard at same time */
 +  tor_assert(rule == NO_WEIGHTING ||
 +             rule == WEIGHT_FOR_EXIT ||
 +             rule == WEIGHT_FOR_GUARD ||
 +             rule == WEIGHT_FOR_MID ||
 +             rule == WEIGHT_FOR_DIR);
 +
 +  if (smartlist_len(sl) == 0) {
 +    log_info(LD_CIRC,
 +             "Empty routerlist passed in to consensus weight node "
 +             "selection for rule %s",
 +             bandwidth_weight_rule_to_string(rule));
 +    return NULL;
 +  }
 +
 +  weight_scale = networkstatus_get_param(NULL, "bwweightscale",
 +                                         BW_WEIGHT_SCALE);
 +
 +  if (rule == WEIGHT_FOR_GUARD) {
 +    Wg = networkstatus_get_bw_weight(NULL, "Wgg", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wgm", -1); /* Bridges */
 +    We = 0;
 +    Wd = networkstatus_get_bw_weight(NULL, "Wgd", -1);
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_MID) {
 +    Wg = networkstatus_get_bw_weight(NULL, "Wmg", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wmm", -1);
 +    We = networkstatus_get_bw_weight(NULL, "Wme", -1);
 +    Wd = networkstatus_get_bw_weight(NULL, "Wmd", -1);
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_EXIT) {
 +    // Guards CAN be exits if they have weird exit policies
 +    // They are d then I guess...
 +    We = networkstatus_get_bw_weight(NULL, "Wee", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wem", -1); /* Odd exit policies */
 +    Wd = networkstatus_get_bw_weight(NULL, "Wed", -1);
 +    Wg = networkstatus_get_bw_weight(NULL, "Weg", -1); /* Odd exit policies */
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_DIR) {
 +    We = networkstatus_get_bw_weight(NULL, "Wbe", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wbm", -1);
 +    Wd = networkstatus_get_bw_weight(NULL, "Wbd", -1);
 +    Wg = networkstatus_get_bw_weight(NULL, "Wbg", -1);
 +
 +    Wgb = Wmb = Web = Wdb = weight_scale;
 +  } else if (rule == NO_WEIGHTING) {
 +    Wg = Wm = We = Wd = weight_scale;
 +    Wgb = Wmb = Web = Wdb = weight_scale;
 +  }
 +
 +  if (Wg < 0 || Wm < 0 || We < 0 || Wd < 0 || Wgb < 0 || Wmb < 0 || Wdb < 0
 +      || Web < 0) {
 +    log_debug(LD_CIRC,
 +              "Got negative bandwidth weights. Defaulting to old selection"
 +              " algorithm.");
 +    return NULL; // Use old algorithm.
 +  }
 +
 +  Wg /= weight_scale;
 +  Wm /= weight_scale;
 +  We /= weight_scale;
 +  Wd /= weight_scale;
 +
 +  Wgb /= weight_scale;
 +  Wmb /= weight_scale;
 +  Web /= weight_scale;
 +  Wdb /= weight_scale;
 +
 +  bandwidths = tor_malloc_zero(sizeof(double)*smartlist_len(sl));
 +
 +  // Cycle through smartlist and total the bandwidth.
 +  for (i = 0; i < (unsigned)smartlist_len(sl); ++i) {
 +    int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0, is_me = 0;
 +    double weight = 1;
 +    if (statuses) {
 +      routerstatus_t *status = smartlist_get(sl, i);
 +      is_exit = status->is_exit;
 +      is_guard = status->is_possible_guard;
 +      is_dir = (status->dir_port != 0);
 +      if (!status->has_bandwidth) {
 +        tor_free(bandwidths);
 +        /* This should never happen, unless all the authorites downgrade
 +         * to 0.2.0 or rogue routerstatuses get inserted into our consensus. */
 +        log_warn(LD_BUG,
 +                 "Consensus is not listing bandwidths. Defaulting back to "
 +                 "old router selection algorithm.");
 +        return NULL;
 +      }
 +      this_bw = kb_to_bytes(status->bandwidth);
 +      if (router_digest_is_me(status->identity_digest))
 +        is_me = 1;
 +    } else {
 +      routerstatus_t *rs;
 +      routerinfo_t *router = smartlist_get(sl, i);
 +      rs = router_get_consensus_status_by_id(
 +             router->cache_info.identity_digest);
 +      is_exit = router->is_exit;
 +      is_guard = router->is_possible_guard;
 +      is_dir = (router->dir_port != 0);
 +      if (rs && rs->has_bandwidth) {
 +        this_bw = kb_to_bytes(rs->bandwidth);
 +      } else { /* bridge or other descriptor not in our consensus */
 +        this_bw = bridge_get_advertised_bandwidth_bounded(router);
 +        have_unknown = 1;
 +      }
 +      if (router_digest_is_me(router->cache_info.identity_digest))
 +        is_me = 1;
 +    }
 +    if (is_guard && is_exit) {
 +      weight = (is_dir ? Wdb*Wd : Wd);
 +    } else if (is_guard) {
 +      weight = (is_dir ? Wgb*Wg : Wg);
 +    } else if (is_exit) {
 +      weight = (is_dir ? Web*We : We);
 +    } else { // middle
 +      weight = (is_dir ? Wmb*Wm : Wm);
 +    }
 +
 +    bandwidths[i] = weight*this_bw;
 +    weighted_bw += weight*this_bw;
 +    if (is_me)
 +      sl_last_weighted_bw_of_me = weight*this_bw;
 +  }
 +
 +  /* XXXX022 this is a kludge to expose these values. */
 +  sl_last_total_weighted_bw = weighted_bw;
 +
 +  log_debug(LD_CIRC, "Choosing node for rule %s based on weights "
 +            "Wg=%lf Wm=%lf We=%lf Wd=%lf with total bw %lf",
 +            bandwidth_weight_rule_to_string(rule),
 +            Wg, Wm, We, Wd, weighted_bw);
 +
 +  /* If there is no bandwidth, choose at random */
 +  if (DBL_TO_U64(weighted_bw) == 0) {
 +    /* Don't warn when using bridges/relays not in the consensus */
 +    if (!have_unknown)
 +      log_warn(LD_CIRC,
 +               "Weighted bandwidth is %lf in node selection for rule %s",
 +               weighted_bw, bandwidth_weight_rule_to_string(rule));
 +    tor_free(bandwidths);
 +    return smartlist_choose(sl);
 +  }
 +
 +  rand_bw = crypto_rand_uint64(DBL_TO_U64(weighted_bw));
 +  rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count
 +              * from 1 below. See bug 1203 for details. */
 +
 +  /* Last, count through sl until we get to the element we picked */
 +  tmp = 0.0;
 +  for (i=0; i < (unsigned)smartlist_len(sl); i++) {
 +    tmp += bandwidths[i];
 +    if (tmp >= rand_bw)
 +      break;
 +  }
 +
 +  if (i == (unsigned)smartlist_len(sl)) {
 +    /* This was once possible due to round-off error, but shouldn't be able
 +     * to occur any longer. */
 +    tor_fragile_assert();
 +    --i;
 +    log_warn(LD_BUG, "Round-off error in computing bandwidth had an effect on "
 +             " which router we chose. Please tell the developers. "
 +             "%lf " U64_FORMAT " %lf", tmp, U64_PRINTF_ARG(rand_bw),
 +             weighted_bw);
 +  }
 +  tor_free(bandwidths);
 +  return smartlist_get(sl, i);
 +}
 +
 +/** Helper function:
 + * choose a random element of smartlist <b>sl</b>, weighted by
   * the advertised bandwidth of each element.
   *
   * If <b>statuses</b> is zero, then <b>sl</b> is a list of
@@@ -1852,24 -1565,11 +1852,24 @@@ smartlist_choose_by_bandwidth(smartlist
    bitarray_t *guard_bits;
    int me_idx = -1;
  
 +  // This function does not support WEIGHT_FOR_DIR
 +  // or WEIGHT_FOR_MID
 +  if (rule == WEIGHT_FOR_DIR || rule == WEIGHT_FOR_MID) {
 +    rule = NO_WEIGHTING;
 +  }
 +
    /* Can't choose exit and guard at same time */
    tor_assert(rule == NO_WEIGHTING ||
               rule == WEIGHT_FOR_EXIT ||
               rule == WEIGHT_FOR_GUARD);
  
 +  if (smartlist_len(sl) == 0) {
 +    log_info(LD_CIRC,
 +             "Empty routerlist passed in to old node selection for rule %s",
 +             bandwidth_weight_rule_to_string(rule));
 +    return NULL;
 +  }
 +
    /* First count the total bandwidth weight, and make a list
     * of each value.  <0 means "unknown; no routerinfo."  We use the
     * bits of negative values to remember whether the router was fast (-x)&1
@@@ -1920,7 -1620,7 +1920,7 @@@
          flags |= is_exit ? 2 : 0;
          flags |= is_guard ? 4 : 0;
        } else /* bridge or other descriptor not in our consensus */
 -        this_bw = router_get_advertised_bandwidth_capped(router);
 +        this_bw = bridge_get_advertised_bandwidth_bounded(router);
      }
      if (is_exit)
        bitarray_set(exit_bits, i);
@@@ -1928,8 -1628,6 +1928,8 @@@
        bitarray_set(guard_bits, i);
      if (is_known) {
        bandwidths[i] = (int32_t) this_bw; // safe since MAX_BELIEVABLE<INT32_MAX
 +      // XXX this is no longer true! We don't always cap the bw anymore. Can
 +      // a consensus make us overflow?-sh
        tor_assert(bandwidths[i] >= 0);
        if (is_guard)
          total_guard_bw += this_bw;
@@@ -1993,12 -1691,12 +1993,12 @@@
       * For detailed derivation of this formula, see
       *   http://archives.seul.org/or/dev/Jul-2007/msg00056.html
       */
 -    if (rule == WEIGHT_FOR_EXIT)
 +    if (rule == WEIGHT_FOR_EXIT || !total_exit_bw)
        exit_weight = 1.0;
      else
        exit_weight = 1.0 - all_bw/(3.0*exit_bw);
  
 -    if (rule == WEIGHT_FOR_GUARD)
 +    if (rule == WEIGHT_FOR_GUARD || !total_guard_bw)
        guard_weight = 1.0;
      else
        guard_weight = 1.0 - all_bw/(3.0*guard_bw);
@@@ -2047,8 -1745,6 +2047,8 @@@
  
    /* Almost done: choose a random value from the bandwidth weights. */
    rand_bw = crypto_rand_uint64(total_bw);
 +  rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count
 +              * from 1 below. See bug 1203 for details. */
  
    /* Last, count through sl until we get to the element we picked */
    tmp = 0;
@@@ -2092,34 -1788,26 +2092,34 @@@ routerinfo_t 
  routerlist_sl_choose_by_bandwidth(smartlist_t *sl,
                                    bandwidth_weight_rule_t rule)
  {
 -  return smartlist_choose_by_bandwidth(sl, rule, 0);
 +  routerinfo_t *ret;
 +  if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 0))) {
 +    return ret;
 +  } else {
 +    return smartlist_choose_by_bandwidth(sl, rule, 0);
 +  }
  }
  
  /** Choose a random element of status list <b>sl</b>, weighted by
   * the advertised bandwidth of each status.
   */
  routerstatus_t *
 -routerstatus_sl_choose_by_bandwidth(smartlist_t *sl)
 +routerstatus_sl_choose_by_bandwidth(smartlist_t *sl,
 +                                    bandwidth_weight_rule_t rule)
  {
    /* We are choosing neither exit nor guard here. Weight accordingly. */
 -  return smartlist_choose_by_bandwidth(sl, NO_WEIGHTING, 1);
 +  routerstatus_t *ret;
 +  if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 1))) {
 +    return ret;
 +  } else {
 +    return smartlist_choose_by_bandwidth(sl, rule, 1);
 +  }
  }
  
 -/** Return a random running router from the routerlist.  If any node
 - * named in <b>preferred</b> is available, pick one of those.  Never
 +/** Return a random running router from the routerlist. Never
   * pick a node whose routerinfo is in
   * <b>excludedsmartlist</b>, or whose routerinfo matches <b>excludedset</b>,
 - * even if they are the only nodes
 - * available.  If <b>CRN_STRICT_PREFERRED</b> is set in flags, never pick
 - * any node besides those in <b>preferred</b>.
 + * even if they are the only nodes available.
   * If <b>CRN_NEED_UPTIME</b> is set in flags and any router has more than
   * a minimum uptime, return one of those.
   * If <b>CRN_NEED_CAPACITY</b> is set in flags, weight your choice by the
@@@ -2132,7 -1820,8 +2132,7 @@@
   * node (that is, possibly discounting exit nodes).
   */
  routerinfo_t *
 -router_choose_random_node(const char *preferred,
 -                          smartlist_t *excludedsmartlist,
 +router_choose_random_node(smartlist_t *excludedsmartlist,
                            routerset_t *excludedset,
                            router_crn_flags_t flags)
  {
@@@ -2140,16 -1829,18 +2140,16 @@@
    const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0;
    const int need_guard = (flags & CRN_NEED_GUARD) != 0;
    const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0;
 -  const int strict = (flags & CRN_STRICT_PREFERRED) != 0;
    const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0;
  
 -  smartlist_t *sl, *excludednodes;
 +  smartlist_t *sl=smartlist_create(),
 +              *excludednodes=smartlist_create();
    routerinfo_t *choice = NULL, *r;
    bandwidth_weight_rule_t rule;
  
    tor_assert(!(weight_for_exit && need_guard));
    rule = weight_for_exit ? WEIGHT_FOR_EXIT :
 -    (need_guard ? WEIGHT_FOR_GUARD : NO_WEIGHTING);
 -
 -  excludednodes = smartlist_create();
 +    (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID);
  
    /* Exclude relays that allow single hop exit circuits, if the user
     * wants to (such relays might be risky) */
@@@ -2166,35 -1857,60 +2166,35 @@@
      routerlist_add_family(excludednodes, r);
    }
  
 -  /* Try the preferred nodes first. Ignore need_uptime and need_capacity
 -   * and need_guard, since the user explicitly asked for these nodes. */
 -  if (preferred) {
 -    sl = smartlist_create();
 -    add_nickname_list_to_smartlist(sl,preferred,1);
 -    smartlist_subtract(sl,excludednodes);
 -    if (excludedsmartlist)
 -      smartlist_subtract(sl,excludedsmartlist);
 -    if (excludedset)
 -      routerset_subtract_routers(sl,excludedset);
 -    choice = smartlist_choose(sl);
 -    smartlist_free(sl);
 -  }
 -  if (!choice && !strict) {
 -    /* Then give up on our preferred choices: any node
 -     * will do that has the required attributes. */
 -    sl = smartlist_create();
 -    router_add_running_routers_to_smartlist(sl, allow_invalid,
 -                                            need_uptime, need_capacity,
 -                                            need_guard);
 -    smartlist_subtract(sl,excludednodes);
 -    if (excludedsmartlist)
 -      smartlist_subtract(sl,excludedsmartlist);
 -    if (excludedset)
 -      routerset_subtract_routers(sl,excludedset);
 -
 -    if (need_capacity || need_guard)
 -      choice = routerlist_sl_choose_by_bandwidth(sl, rule);
 -    else
 -      choice = smartlist_choose(sl);
 -
 -    smartlist_free(sl);
 -    if (!choice && (need_uptime || need_capacity || need_guard)) {
 -      /* try once more -- recurse but with fewer restrictions. */
 -      log_info(LD_CIRC,
 -               "We couldn't find any live%s%s%s routers; falling back "
 -               "to list of all routers.",
 -               need_capacity?", fast":"",
 -               need_uptime?", stable":"",
 -               need_guard?", guard":"");
 -      flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD);
 -      choice = router_choose_random_node(
 -                       NULL, excludedsmartlist, excludedset, flags);
 -    }
 +  router_add_running_routers_to_smartlist(sl, allow_invalid,
 +                                          need_uptime, need_capacity,
 +                                          need_guard);
 +  smartlist_subtract(sl,excludednodes);
 +  if (excludedsmartlist)
 +    smartlist_subtract(sl,excludedsmartlist);
 +  if (excludedset)
 +    routerset_subtract_routers(sl,excludedset);
 +
 +  // Always weight by bandwidth
 +  choice = routerlist_sl_choose_by_bandwidth(sl, rule);
 +
 +  smartlist_free(sl);
 +  if (!choice && (need_uptime || need_capacity || need_guard)) {
 +    /* try once more -- recurse but with fewer restrictions. */
 +    log_info(LD_CIRC,
 +             "We couldn't find any live%s%s%s routers; falling back "
 +             "to list of all routers.",
 +             need_capacity?", fast":"",
 +             need_uptime?", stable":"",
 +             need_guard?", guard":"");
 +    flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD);
 +    choice = router_choose_random_node(
 +                     excludedsmartlist, excludedset, flags);
    }
    smartlist_free(excludednodes);
    if (!choice) {
 -    if (strict) {
 -      log_warn(LD_CIRC, "All preferred nodes were down when trying to choose "
 -               "node, and the Strict[...]Nodes option is set. Failing.");
 -    } else {
 -      log_warn(LD_CIRC,
 -               "No available nodes when trying to choose node. Failing.");
 -    }
 +    log_warn(LD_CIRC,
 +             "No available nodes when trying to choose node. Failing.");
    }
    return choice;
  }
@@@ -2273,6 -1989,9 +2273,6 @@@ router_get_by_nickname(const char *nick
      return router_get_by_hexdigest(nickname);
    if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME))
      return NULL;
 -  if (server_mode(get_options()) &&
 -      !strcasecmp(nickname, get_options()->Nickname))
 -    return router_get_my_routerinfo();
  
    maybedigest = (strlen(nickname) >= HEX_DIGEST_LEN) &&
      (base16_decode(digest,DIGEST_LEN,nickname,HEX_DIGEST_LEN) == 0);
@@@ -2651,9 -2370,6 +2651,9 @@@ extrainfo_free(extrainfo_t *extrainfo
  static void
  signed_descriptor_free(signed_descriptor_t *sd)
  {
 +  if (!sd)
 +    return;
 +
    tor_free(sd->signed_descriptor_body);
  
    /* XXXX remove this once more bugs go away. */
@@@ -2661,15 -2377,12 +2661,15 @@@
    tor_free(sd);
  }
  
 -/** Extract a signed_descriptor_t from a routerinfo, and free the routerinfo.
 +/** Extract a signed_descriptor_t from a general routerinfo, and free the
 + * routerinfo.
   */
  static signed_descriptor_t *
  signed_descriptor_from_routerinfo(routerinfo_t *ri)
  {
 -  signed_descriptor_t *sd = tor_malloc_zero(sizeof(signed_descriptor_t));
 +  signed_descriptor_t *sd;
 +  tor_assert(ri->purpose == ROUTER_PURPOSE_GENERAL);
 +  sd = tor_malloc_zero(sizeof(signed_descriptor_t));
    memcpy(sd, &(ri->cache_info), sizeof(signed_descriptor_t));
    sd->routerlist_index = -1;
    ri->cache_info.signed_descriptor_body = NULL;
@@@ -2688,8 -2401,7 +2688,8 @@@ _extrainfo_free(void *e
  void
  routerlist_free(routerlist_t *rl)
  {
 -  tor_assert(rl);
 +  if (!rl)
 +    return;
    rimap_free(rl->identity_map, NULL);
    sdmap_free(rl->desc_digest_map, NULL);
    sdmap_free(rl->desc_by_eid_map, NULL);
@@@ -2728,6 -2440,46 +2728,6 @@@ dump_routerlist_mem_usage(int severity
        "In %d old descriptors: "U64_FORMAT" bytes.",
        smartlist_len(routerlist->routers), U64_PRINTF_ARG(livedescs),
        smartlist_len(routerlist->old_routers), U64_PRINTF_ARG(olddescs));
 -
 -#if 0
 -  {
 -    const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list();
 -    networkstatus_t *consensus = networkstatus_get_latest_consensus();
 -    log(severity, LD_DIR, "Now let's look through old_descriptors!");
 -    SMARTLIST_FOREACH(routerlist->old_routers, signed_descriptor_t *, sd, {
 -        int in_v2 = 0;
 -        int in_v3 = 0;
 -        char published[ISO_TIME_LEN+1];
 -        char last_valid_until[ISO_TIME_LEN+1];
 -        char last_served_at[ISO_TIME_LEN+1];
 -        char id[HEX_DIGEST_LEN+1];
 -        routerstatus_t *rs;
 -        format_iso_time(published, sd->published_on);
 -        format_iso_time(last_valid_until, sd->last_listed_as_valid_until);
 -        format_iso_time(last_served_at, sd->last_served_at);
 -        base16_encode(id, sizeof(id), sd->identity_digest, DIGEST_LEN);
 -        SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
 -          {
 -            rs = networkstatus_v2_find_entry(ns, sd->identity_digest);
 -            if (rs && !memcmp(rs->descriptor_digest,
 -                              sd->signed_descriptor_digest, DIGEST_LEN)) {
 -              in_v2 = 1; break;
 -            }
 -          });
 -        if (consensus) {
 -          rs = networkstatus_vote_find_entry(consensus, sd->identity_digest);
 -          if (rs && !memcmp(rs->descriptor_digest,
 -                            sd->signed_descriptor_digest, DIGEST_LEN))
 -            in_v3 = 1;
 -        }
 -        log(severity, LD_DIR,
 -            "Old descriptor for %s (published %s) %sin v2 ns, %sin v3 "
 -            "consensus.  Last valid until %s; last served at %s.",
 -            id, published, in_v2 ? "" : "not ", in_v3 ? "" : "not ",
 -            last_valid_until, last_served_at);
 -    });
 -  }
 -#endif
  }
  
  /** Debugging helper: If <b>idx</b> is nonnegative, assert that <b>ri</b> is
@@@ -2760,7 -2512,6 +2760,7 @@@ static voi
  routerlist_insert(routerlist_t *rl, routerinfo_t *ri)
  {
    routerinfo_t *ri_old;
 +  signed_descriptor_t *sd_old;
    {
      /* XXXX Remove if this slows us down. */
      routerinfo_t *ri_generated = router_get_my_routerinfo();
@@@ -2770,16 -2521,8 +2770,16 @@@
  
    ri_old = rimap_set(rl->identity_map, ri->cache_info.identity_digest, ri);
    tor_assert(!ri_old);
 -  sdmap_set(rl->desc_digest_map, ri->cache_info.signed_descriptor_digest,
 -            &(ri->cache_info));
 +
 +  sd_old = sdmap_set(rl->desc_digest_map,
 +                     ri->cache_info.signed_descriptor_digest,
 +                     &(ri->cache_info));
 +  if (sd_old) {
 +    rl->desc_store.bytes_dropped += sd_old->signed_descriptor_len;
 +    sdmap_remove(rl->desc_by_eid_map, sd_old->extra_info_digest);
 +    signed_descriptor_free(sd_old);
 +  }
 +
    if (!tor_digest_is_zero(ri->cache_info.extra_info_digest))
      sdmap_set(rl->desc_by_eid_map, ri->cache_info.extra_info_digest,
                &ri->cache_info);
@@@ -2998,7 -2741,6 +2998,7 @@@ routerlist_replace(routerlist_t *rl, ro
                     routerinfo_t *ri_new)
  {
    int idx;
 +  int same_descriptors;
  
    routerinfo_t *ri_tmp;
    extrainfo_t *ei_tmp;
@@@ -3043,15 -2785,8 +3043,15 @@@
                &ri_new->cache_info);
    }
  
 +  same_descriptors = ! memcmp(ri_old->cache_info.signed_descriptor_digest,
 +                              ri_new->cache_info.signed_descriptor_digest,
 +                              DIGEST_LEN);
 +
    if (should_cache_old_descriptors() &&
 -      ri_old->purpose == ROUTER_PURPOSE_GENERAL) {
 +      ri_old->purpose == ROUTER_PURPOSE_GENERAL &&
 +      !same_descriptors) {
 +    /* ri_old is going to become a signed_descriptor_t and go into
 +     * old_routers */
      signed_descriptor_t *sd = signed_descriptor_from_routerinfo(ri_old);
      smartlist_add(rl->old_routers, sd);
      sd->routerlist_index = smartlist_len(rl->old_routers)-1;
@@@ -3059,27 -2794,24 +3059,27 @@@
      if (!tor_digest_is_zero(sd->extra_info_digest))
        sdmap_set(rl->desc_by_eid_map, sd->extra_info_digest, sd);
    } else {
 -    if (memcmp(ri_old->cache_info.signed_descriptor_digest,
 -               ri_new->cache_info.signed_descriptor_digest,
 -               DIGEST_LEN)) {
 -      /* digests don't match; digestmap_set didn't replace */
 +    /* We're dropping ri_old. */
 +    if (!same_descriptors) {
 +      /* digests don't match; The sdmap_set above didn't replace */
        sdmap_remove(rl->desc_digest_map,
                     ri_old->cache_info.signed_descriptor_digest);
 -    }
  
 -    ei_tmp = eimap_remove(rl->extra_info_map,
 -                          ri_old->cache_info.extra_info_digest);
 -    if (ei_tmp) {
 -      rl->extrainfo_store.bytes_dropped +=
 -        ei_tmp->cache_info.signed_descriptor_len;
 -      extrainfo_free(ei_tmp);
 -    }
 -    if (!tor_digest_is_zero(ri_old->cache_info.extra_info_digest)) {
 -      sdmap_remove(rl->desc_by_eid_map,
 -                   ri_old->cache_info.extra_info_digest);
 +      if (memcmp(ri_old->cache_info.extra_info_digest,
 +                 ri_new->cache_info.extra_info_digest, DIGEST_LEN)) {
 +        ei_tmp = eimap_remove(rl->extra_info_map,
 +                              ri_old->cache_info.extra_info_digest);
 +        if (ei_tmp) {
 +          rl->extrainfo_store.bytes_dropped +=
 +            ei_tmp->cache_info.signed_descriptor_len;
 +          extrainfo_free(ei_tmp);
 +        }
 +      }
 +
 +      if (!tor_digest_is_zero(ri_old->cache_info.extra_info_digest)) {
 +        sdmap_remove(rl->desc_by_eid_map,
 +                     ri_old->cache_info.extra_info_digest);
 +      }
      }
      rl->desc_store.bytes_dropped += ri_old->cache_info.signed_descriptor_len;
      routerinfo_free(ri_old);
@@@ -3117,7 -2849,8 +3117,7 @@@ routerlist_reparse_old(routerlist_t *rl
  void
  routerlist_free_all(void)
  {
 -  if (routerlist)
 -    routerlist_free(routerlist);
 +  routerlist_free(routerlist);
    routerlist = NULL;
    if (warned_nicknames) {
      SMARTLIST_FOREACH(warned_nicknames, char *, cp, tor_free(cp));
@@@ -3224,33 -2957,15 +3224,33 @@@ router_add_to_routerlist(routerinfo_t *
  
    id_digest = router->cache_info.identity_digest;
  
 +  old_router = router_get_by_digest(id_digest);
 +
    /* Make sure that we haven't already got this exact descriptor. */
    if (sdmap_get(routerlist->desc_digest_map,
                  router->cache_info.signed_descriptor_digest)) {
 -    log_info(LD_DIR,
 -             "Dropping descriptor that we already have for router '%s'",
 -             router->nickname);
 -    *msg = "Router descriptor was not new.";
 -    routerinfo_free(router);
 -    return ROUTER_WAS_NOT_NEW;
 +    /* If we have this descriptor already and the new descriptor is a bridge
 +     * descriptor, replace it. If we had a bridge descriptor before and the
 +     * new one is not a bridge descriptor, don't replace it. */
 +
 +    /* Only members of routerlist->identity_map can be bridges; we don't
 +     * put bridges in old_routers. */
 +    const int was_bridge = old_router &&
 +      old_router->purpose == ROUTER_PURPOSE_BRIDGE;
 +
 +    if (routerinfo_is_a_configured_bridge(router) &&
 +        router->purpose == ROUTER_PURPOSE_BRIDGE &&
 +        !was_bridge) {
 +      log_info(LD_DIR, "Replacing non-bridge descriptor with bridge "
 +               "descriptor for router '%s'", router->nickname);
 +    } else {
 +      log_info(LD_DIR,
 +               "Dropping descriptor that we already have for router '%s'",
 +               router->nickname);
 +      *msg = "Router descriptor was not new.";
 +      routerinfo_free(router);
 +      return ROUTER_WAS_NOT_NEW;
 +    }
    }
  
    if (authdir) {
@@@ -3287,14 -3002,15 +3287,14 @@@
    SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
    {
      routerstatus_t *rs =
 -      networkstatus_v2_find_entry(ns, router->cache_info.identity_digest);
 +      networkstatus_v2_find_entry(ns, id_digest);
      if (rs && !memcmp(rs->descriptor_digest,
                        router->cache_info.signed_descriptor_digest,
                        DIGEST_LEN))
        rs->need_to_mirror = 0;
    });
    if (consensus) {
 -    routerstatus_t *rs = networkstatus_vote_find_entry(consensus,
 -                                        router->cache_info.identity_digest);
 +    routerstatus_t *rs = networkstatus_vote_find_entry(consensus, id_digest);
      if (rs && !memcmp(rs->descriptor_digest,
                        router->cache_info.signed_descriptor_digest,
                        DIGEST_LEN)) {
@@@ -3316,11 -3032,13 +3316,11 @@@
    }
  
    /* If we have a router with the same identity key, choose the newer one. */
 -  old_router = rimap_get(routerlist->identity_map,
 -                         router->cache_info.identity_digest);
    if (old_router) {
      if (!in_consensus && (router->cache_info.published_on <=
                            old_router->cache_info.published_on)) {
        /* Same key, but old.  This one is not listed in the consensus. */
 -      log_debug(LD_DIR, "Skipping not-new descriptor for router '%s'",
 +      log_debug(LD_DIR, "Not-new descriptor for router '%s'",
                  router->nickname);
        /* Only journal this desc if we'll be serving it. */
        if (!from_cache && should_cache_old_descriptors())
@@@ -3334,7 -3052,8 +3334,7 @@@
        log_debug(LD_DIR, "Replacing entry for router '%s/%s' [%s]",
                  router->nickname, old_router->nickname,
                  hex_str(id_digest,DIGEST_LEN));
 -      if (router->addr == old_router->addr &&
 -          router->or_port == old_router->or_port) {
 +      if (routers_have_same_or_addr(router, old_router)) {
          /* these carry over when the address and orport are unchanged. */
          router->last_reachable = old_router->last_reachable;
          router->testing_since = old_router->testing_since;
@@@ -3362,10 -3081,9 +3362,10 @@@
    /* We haven't seen a router with this identity before. Add it to the end of
     * the list. */
    routerlist_insert(routerlist, router);
 -  if (!from_cache)
 +  if (!from_cache) {
      signed_desc_append_to_journal(&router->cache_info,
                                    &routerlist->desc_store);
 +  }
    directory_set_dirty();
    return ROUTER_ADDED_SUCCESSFULLY;
  }
@@@ -3682,19 -3400,15 +3682,19 @@@ routerlist_remove_old_routers(void
  
  /** We just added a new set of descriptors. Take whatever extra steps
   * we need. */
 -static void
 +void
  routerlist_descriptors_added(smartlist_t *sl, int from_cache)
  {
    tor_assert(sl);
    control_event_descriptors_changed(sl);
 -  SMARTLIST_FOREACH(sl, routerinfo_t *, ri,
 +  SMARTLIST_FOREACH_BEGIN(sl, routerinfo_t *, ri) {
      if (ri->purpose == ROUTER_PURPOSE_BRIDGE)
        learned_bridge_descriptor(ri, from_cache);
 -  );
 +    if (ri->needs_retest_if_added) {
 +      ri->needs_retest_if_added = 0;
 +      dirserv_single_reachability_test(approx_time(), ri);
 +    }
 +  } SMARTLIST_FOREACH_END(ri);
  }
  
  /**
@@@ -4025,8 -3739,12 +4025,8 @@@ add_trusted_dir_server(const char *nick
  
    if (ent->or_port)
      ent->fake_status.version_supports_begindir = 1;
 -/* XX021 - wait until authorities are upgraded */
 -#if 0
 +
    ent->fake_status.version_supports_conditional_consensus = 1;
 -#else
 -  ent->fake_status.version_supports_conditional_consensus = 0;
 -#endif
  
    smartlist_add(trusted_dir_servers, ent);
    router_dir_info_changed();
@@@ -4041,8 -3759,10 +4041,8 @@@ authority_cert_free(authority_cert_t *c
      return;
  
    tor_free(cert->cache_info.signed_descriptor_body);
 -  if (cert->signing_key)
 -    crypto_free_pk_env(cert->signing_key);
 -  if (cert->identity_key)
 -    crypto_free_pk_env(cert->identity_key);
 +  crypto_free_pk_env(cert->signing_key);
 +  crypto_free_pk_env(cert->identity_key);
  
    tor_free(cert);
  }
@@@ -4051,9 -3771,6 +4051,9 @@@
  static void
  trusted_dir_server_free(trusted_dir_server_t *ds)
  {
 +  if (!ds)
 +    return;
 +
    tor_free(ds->nickname);
    tor_free(ds->description);
    tor_free(ds->address);
@@@ -4107,7 -3824,7 +4107,7 @@@ list_pending_downloads(digestmap_t *res
        const char *resource = TO_DIR_CONN(conn)->requested_resource;
        if (!strcmpstart(resource, prefix))
          dir_split_resource_into_fingerprints(resource + p_len,
 -                                             tmp, NULL, 1, 0);
 +                                             tmp, NULL, DSR_HEX);
      }
    });
    SMARTLIST_FOREACH(tmp, char *, d,
@@@ -4209,7 -3926,7 +4209,7 @@@ client_would_use_router(routerstatus_t 
   * this number per server. */
  #define MIN_DL_PER_REQUEST 4
  /** To prevent a single screwy cache from confusing us by selective reply,
 - * try to split our requests into at least this this many requests. */
 + * try to split our requests into at least this many requests. */
  #define MIN_REQUESTS 3
  /** If we want fewer than this many descriptors, wait until we
   * want more, or until MAX_CLIENT_INTERVAL_WITHOUT_REQUEST has
@@@ -4223,8 -3940,7 +4223,8 @@@
   * whether to delay fetching until we have more.  If we don't want to delay,
   * launch one or more requests to the appropriate directory authorities. */
  static void
 -launch_router_descriptor_downloads(smartlist_t *downloadable, time_t now)
 +launch_router_descriptor_downloads(smartlist_t *downloadable,
 +                                   routerstatus_t *source, time_t now)
  {
    int should_delay = 0, n_downloadable;
    or_options_t *options = get_options();
@@@ -4277,7 -3993,7 +4277,7 @@@
        pds_flags |= PDS_NO_EXISTING_SERVERDESC_FETCH;
      }
  
 -    n_per_request = (n_downloadable+MIN_REQUESTS-1) / MIN_REQUESTS;
 +    n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS);
      if (n_per_request > MAX_DL_PER_REQUEST)
        n_per_request = MAX_DL_PER_REQUEST;
      if (n_per_request < MIN_DL_PER_REQUEST)
@@@ -4290,11 -4006,11 +4290,11 @@@
  
      log_info(LD_DIR,
               "Launching %d request%s for %d router%s, %d at a time",
 -             (n_downloadable+n_per_request-1)/n_per_request,
 +             CEIL_DIV(n_downloadable, n_per_request),
               req_plural, n_downloadable, rtr_plural, n_per_request);
      smartlist_sort_digests(downloadable);
      for (i=0; i < n_downloadable; i += n_per_request) {
 -      initiate_descriptor_downloads(NULL, DIR_PURPOSE_FETCH_SERVERDESC,
 +      initiate_descriptor_downloads(source, DIR_PURPOSE_FETCH_SERVERDESC,
                                      downloadable, i, i+n_per_request,
                                      pds_flags);
      }
@@@ -4450,18 -4166,18 +4450,18 @@@ update_router_descriptor_cache_download
    digestmap_free(map,NULL);
  }
  
 -/** For any descriptor that we want that's currently listed in the live
 - * consensus, download it as appropriate. */
 -static void
 -update_consensus_router_descriptor_downloads(time_t now)
 +/** For any descriptor that we want that's currently listed in
 + * <b>consensus</b>, download it as appropriate. */
 +void
 +update_consensus_router_descriptor_downloads(time_t now, int is_vote,
 +                                             networkstatus_t *consensus)
  {
    or_options_t *options = get_options();
    digestmap_t *map = NULL;
    smartlist_t *no_longer_old = smartlist_create();
    smartlist_t *downloadable = smartlist_create();
 +  routerstatus_t *source = NULL;
    int authdir = authdir_mode(options);
 -  networkstatus_t *consensus =
 -    networkstatus_get_reasonably_live_consensus(now);
    int n_delayed=0, n_have=0, n_would_reject=0, n_wouldnt_use=0,
      n_inprogress=0, n_in_oldrouters=0;
  
@@@ -4470,24 -4186,10 +4470,24 @@@
    if (!consensus)
      goto done;
  
 +  if (is_vote) {
 +    /* where's it from, so we know whom to ask for descriptors */
 +    trusted_dir_server_t *ds;
 +    networkstatus_voter_info_t *voter = smartlist_get(consensus->voters, 0);
 +    tor_assert(voter);
 +    ds = trusteddirserver_get_by_v3_auth_digest(voter->identity_digest);
 +    if (ds)
 +      source = &(ds->fake_status);
 +    else
 +      log_warn(LD_DIR, "couldn't lookup source from vote?");
 +  }
 +
    map = digestmap_new();
    list_pending_descriptor_downloads(map, 0);
 -  SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs,
 +  SMARTLIST_FOREACH(consensus->routerstatus_list, void *, rsp,
      {
 +      routerstatus_t *rs =
 +        is_vote ? &(((vote_routerstatus_t *)rsp)->status) : rsp;
        signed_descriptor_t *sd;
        if ((sd = router_get_by_descriptor_digest(rs->descriptor_digest))) {
          routerinfo_t *ri;
@@@ -4522,18 -4224,6 +4522,18 @@@
          ++n_wouldnt_use;
          continue; /* We would never use it ourself. */
        }
 +      if (is_vote && source) {
 +        char time_bufnew[ISO_TIME_LEN+1];
 +        char time_bufold[ISO_TIME_LEN+1];
 +        routerinfo_t *oldrouter = router_get_by_digest(rs->identity_digest);
 +        format_iso_time(time_bufnew, rs->published_on);
 +        if (oldrouter)
 +          format_iso_time(time_bufold, oldrouter->cache_info.published_on);
 +        log_info(LD_DIR, "Learned about %s (%s vs %s) from %s's vote (%s)",
 +                 rs->nickname, time_bufnew,
 +                 oldrouter ? time_bufold : "none",
 +                 source->nickname, oldrouter ? "known" : "unknown");
 +      }
        smartlist_add(downloadable, rs->descriptor_digest);
      });
  
@@@ -4567,7 -4257,7 +4567,7 @@@
             smartlist_len(downloadable), n_delayed, n_have, n_in_oldrouters,
             n_would_reject, n_wouldnt_use, n_inprogress);
  
 -  launch_router_descriptor_downloads(downloadable, now);
 +  launch_router_descriptor_downloads(downloadable, source, now);
  
    digestmap_free(map, NULL);
   done:
@@@ -4592,8 -4282,7 +4592,8 @@@ update_router_descriptor_downloads(time
    if (directory_fetches_dir_info_early(options)) {
      update_router_descriptor_cache_downloads_v2(now);
    }
 -  update_consensus_router_descriptor_downloads(now);
 +  update_consensus_router_descriptor_downloads(now, 0,
 +    networkstatus_get_reasonably_live_consensus(now));
  
    /* XXXX021 we could be smarter here; see notes on bug 652. */
    /* If we're a server that doesn't have a configured address, we rely on
@@@ -4730,21 -4419,16 +4730,21 @@@ get_dir_info_status_string(void
  /** Iterate over the servers listed in <b>consensus</b>, and count how many of
   * them seem like ones we'd use, and how many of <em>those</em> we have
   * descriptors for.  Store the former in *<b>num_usable</b> and the latter in
 - * *<b>num_present</b>.  */
 + * *<b>num_present</b>.  If <b>in_set</b> is non-NULL, only consider those
 + * routers in <b>in_set</b>.
 + */
  static void
  count_usable_descriptors(int *num_present, int *num_usable,
                           const networkstatus_t *consensus,
 -                         or_options_t *options, time_t now)
 +                         or_options_t *options, time_t now,
 +                         routerset_t *in_set)
  {
    *num_present = 0, *num_usable=0;
  
    SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs,
       {
 +       if (in_set && ! routerset_contains_routerstatus(in_set, rs))
 +         continue;
         if (client_would_use_router(rs, now, options)) {
           ++*num_usable; /* the consensus says we want it. */
           if (router_get_by_descriptor_digest(rs->descriptor_digest)) {
@@@ -4773,7 -4457,7 +4773,7 @@@ count_loading_descriptors_progress(void
      return 0; /* can't count descriptors if we have no list of them */
  
    count_usable_descriptors(&num_present, &num_usable,
 -                           consensus, get_options(), now);
 +                           consensus, get_options(), now, NULL);
  
    if (num_usable == 0)
      return 0; /* don't div by 0 */
@@@ -4817,39 -4501,22 +4817,39 @@@ update_router_have_minimum_dir_info(voi
      goto done;
    }
  
 -  count_usable_descriptors(&num_present, &num_usable, consensus, options, now);
 +  count_usable_descriptors(&num_present, &num_usable, consensus, options, now,
 +                           NULL);
  
    if (num_present < num_usable/4) {
      tor_snprintf(dir_info_status, sizeof(dir_info_status),
              "We have only %d/%d usable descriptors.", num_present, num_usable);
      res = 0;
      control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0);
 +    goto done;
    } else if (num_present < 2) {
      tor_snprintf(dir_info_status, sizeof(dir_info_status),
                   "Only %d descriptor%s here and believed reachable!",
                   num_present, num_present ? "" : "s");
      res = 0;
 -  } else {
 -    res = 1;
 +    goto done;
    }
  
 +  /* Check for entry nodes. */
 +  if (options->EntryNodes) {
 +    count_usable_descriptors(&num_present, &num_usable, consensus, options,
 +                             now, options->EntryNodes);
 +
 +    if (!num_usable || !num_present) {
 +      tor_snprintf(dir_info_status, sizeof(dir_info_status),
 +                   "We have only %d/%d usable entry node descriptors.",
 +                   num_present, num_usable);
 +      res = 0;
 +      goto done;
 +    }
 +  }
 +
 +  res = 1;
 +
   done:
    if (res && !have_min_dir_info) {
      log(LOG_NOTICE, LD_DIR,
@@@ -4862,13 -4529,6 +4862,13 @@@
      log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR,
          "Our directory information is no longer up-to-date "
          "enough to build circuits: %s", dir_info_status);
 +
 +    /* a) make us log when we next complete a circuit, so we know when Tor
 +     * is back up and usable, and b) disable some activities that Tor
 +     * should only do while circuits are working, like reachability tests
 +     * and fetching bridge descriptors only over circuits. */
 +    can_complete_circuit = 0;
 +
      control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO");
    }
    have_min_dir_info = res;
@@@ -5016,7 -4676,8 +5016,8 @@@ routerinfo_incompatible_with_extrainfo(
  
    if (ei->pending_sig) {
      char signed_digest[128];
-     if (crypto_pk_public_checksig(ri->identity_pkey, signed_digest,
+     if (crypto_pk_public_checksig(ri->identity_pkey,
+                        signed_digest, sizeof(signed_digest),
                         ei->pending_sig, ei->pending_sig_len) != DIGEST_LEN ||
          memcmp(signed_digest, ei->cache_info.signed_descriptor_digest,
                 DIGEST_LEN)) {
@@@ -5154,8 -4815,8 +5155,8 @@@ esc_router_info(routerinfo_t *router
    static char *info=NULL;
    char *esc_contact, *esc_platform;
    size_t len;
 -  if (info)
 -    tor_free(info);
 +  tor_free(info);
 +
    if (!router)
      return NULL; /* we're exiting; just free the memory we use */
  
@@@ -5290,8 -4951,9 +5291,8 @@@ voi
  routerset_refresh_countries(routerset_t *target)
  {
    int cc;
 -  if (target->countries) {
 -    bitarray_free(target->countries);
 -  }
 +  bitarray_free(target->countries);
 +
    if (!geoip_is_loaded()) {
      target->countries = NULL;
      target->n_countries = 0;
@@@ -5365,9 -5027,7 +5366,9 @@@ routerset_parse(routerset_t *target, co
    return r;
  }
  
 -/** DOCDOC */
 +/** Called when we change a node set, or when we reload the geoip list:
 + * recompute all country info in all configuration node sets and in the
 + * routerlist. */
  void
  refresh_all_country_info(void)
  {
@@@ -5535,13 -5195,9 +5536,13 @@@ routerset_get_all_routers(smartlist_t *
    }
  }
  
 -/** Add to <b>target</b> every routerinfo_t from <b>source</b> that is in
 - * <b>include</b>, but not excluded in a more specific fashion by
 - * <b>exclude</b>.  If <b>running_only</b>, only include running routers.
 +/** Add to <b>target</b> every routerinfo_t from <b>source</b> except:
 + *
 + * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in
 + * <b>include</b>; and
 + * 2) Don't add it if <b>exclude</b> is non-empty and the relay is
 + * excluded in a more specific fashion by <b>exclude</b>.
 + * 3) If <b>running_only</b>, don't add non-running routers.
   */
  void
  routersets_get_disjunction(smartlist_t *target,
@@@ -5611,15 -5267,35 +5612,15 @@@ routerset_equal(const routerset_t *old
    });
  
    return 1;
 -
 -#if 0
 -  /* XXXX: This won't work if the names/digests are identical but in a
 -     different order. Checking for exact equality would be heavy going,
 -     is it worth it? -RH*/
 -  /* This code is totally bogus; sizeof doesn't work even remotely like this
 -   * code seems to think.  Let's revert to a string-based comparison for
 -   * now. -NM*/
 -  if (sizeof(old->names) != sizeof(new->names))
 -    return 0;
 -
 -  if (memcmp(old->names,new->names,sizeof(new->names)))
 -    return 0;
 -  if (sizeof(old->digests) != sizeof(new->digests))
 -    return 0;
 -  if (memcmp(old->digests,new->digests,sizeof(new->digests)))
 -    return 0;
 -  if (sizeof(old->countries) != sizeof(new->countries))
 -    return 0;
 -  if (memcmp(old->countries,new->countries,sizeof(new->countries)))
 -    return 0;
 -  return 1;
 -#endif
  }
  
  /** Free all storage held in <b>routerset</b>. */
  void
  routerset_free(routerset_t *routerset)
  {
 +  if (!routerset)
 +    return;
 +
    SMARTLIST_FOREACH(routerset->list, char *, cp, tor_free(cp));
    smartlist_free(routerset->list);
    SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p,
@@@ -5630,7 -5306,8 +5631,7 @@@
  
    strmap_free(routerset->names, NULL);
    digestmap_free(routerset->digests, NULL);
 -  if (routerset->countries)
 -    bitarray_free(routerset->countries);
 +  bitarray_free(routerset->countries);
    tor_free(routerset);
  }
  
diff --combined src/or/routerparse.c
index 691b9be,9ad84ed..66d024e
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@@ -10,20 -10,7 +10,20 @@@
   **/
  
  #include "or.h"
 +#include "config.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>
  
  /****************************************************************************/
  
@@@ -68,7 -55,6 +68,7 @@@ typedef enum 
    K_S,
    K_V,
    K_W,
 +  K_M,
    K_EVENTDNS,
    K_EXTRA_INFO,
    K_EXTRA_INFO_DIGEST,
@@@ -76,31 -62,6 +76,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,
@@@ -117,18 -78,13 +117,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,
@@@ -166,7 -122,7 +166,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. */
@@@ -302,31 -258,6 +302,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
@@@ -336,11 -267,10 +336,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
  };
@@@ -445,7 -375,7 +445,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 ),
@@@ -473,7 -403,7 +473,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 ),
@@@ -500,29 -430,17 +500,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
  };
  
@@@ -535,13 -453,9 +535,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,
@@@ -565,7 -479,6 +565,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,
@@@ -586,34 -499,6 +586,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.
   */
@@@ -621,8 -506,7 +621,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
@@@ -632,8 -516,7 +632,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
@@@ -643,8 -526,7 +643,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
@@@ -654,31 -536,18 +654,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
@@@ -687,7 -556,7 +687,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
@@@ -699,15 -568,16 +699,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;
+   size_t i, keysize;
 +  int siglen;
  
-   signature = tor_malloc(crypto_pk_keysize(private_key));
-   siglen = crypto_pk_private_sign(private_key, signature, digest, digest_len);
+   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;
    }
@@@ -715,7 -585,7 +717,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;
    }
@@@ -822,7 -692,7 +824,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();
@@@ -858,11 -728,11 +860,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);
  
@@@ -895,12 -765,11 +897,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) {
@@@ -922,7 -791,7 +924,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)) {
@@@ -951,7 -820,7 +953,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;
@@@ -963,10 -832,9 +965,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) {
@@@ -1016,7 -884,7 +1018,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);
@@@ -1052,13 -920,13 +1054,14 @@@ 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,
                        const char *doctype)
  {
    char *signed_digest;
+   size_t keysize;
    const int check_authority = (flags & CST_CHECK_AUTHORITY);
    const int check_objtype = ! (flags & CST_NO_CHECK_OBJTYPE);
  
@@@ -1080,17 -948,18 +1083,18 @@@
      }
    }
  
-   signed_digest = tor_malloc(tok->object_size);
-   if (crypto_pk_public_checksig(pkey, signed_digest, tok->object_body,
-                                 tok->object_size)
-       < digest_len) {
+   keysize = crypto_pk_keysize(pkey);
+   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;
@@@ -1276,7 -1145,7 +1280,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;
@@@ -1554,7 -1423,7 +1558,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;
  
@@@ -1572,15 -1441,16 +1576,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);
@@@ -1605,7 -1475,6 +1609,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);
@@@ -1680,8 -1549,7 +1684,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)
@@@ -1695,12 -1563,12 +1699,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) {
@@@ -1724,7 -1592,6 +1728,7 @@@ authority_cert_parse_from_string(const 
    size_t len;
    int found;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    s = eat_whitespace(s);
    eos = strstr(s, "\ndir-key-certification");
@@@ -1749,7 -1616,7 +1753,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")) {
@@@ -1842,7 -1709,7 +1846,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;
      }
@@@ -1851,7 -1718,6 +1855,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,
@@@ -1871,7 -1737,7 +1875,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");
@@@ -1879,9 -1745,8 +1883,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");
@@@ -1892,28 -1757,23 +1896,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
@@@ -1927,29 -1787,22 +1931,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)) {
@@@ -1961,15 -1814,7 +1965,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 {
@@@ -1990,34 -1835,29 +1994,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) {
@@@ -2103,17 -1943,6 +2107,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;
        }
      }
    }
@@@ -2135,29 -1964,16 +2139,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");
@@@ -2169,8 -1985,8 +2173,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);
@@@ -2194,7 -2010,7 +2198,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;
@@@ -2308,17 -2124,17 +2312,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)) {
@@@ -2337,19 -2153,19 +2341,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");
@@@ -2358,395 -2174,6 +2362,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 = networkstatus_get_param(ns, "bwweightscale", BW_WEIGHT_SCALE);
 +  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 *
@@@ -2757,21 -2184,19 +2761,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;
    }
@@@ -2787,23 -2212,7 +2791,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;
@@@ -2957,9 -2366,8 +2961,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 ||
@@@ -3051,7 -2459,7 +3055,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);
@@@ -3061,8 -2469,7 +3065,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);
      }
    }
@@@ -3095,73 -2502,14 +3099,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") ||
@@@ -3170,11 -2518,11 +3174,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))) {
@@@ -3182,15 -2530,11 +3186,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;
      }
  
@@@ -3199,49 -2543,35 +3203,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)
 +      if (tok->object_size >= INT_MAX) {
 +        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)
@@@ -3249,31 -2579,27 +3253,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) {
@@@ -3286,35 -2612,6 +3290,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 *
@@@ -3324,13 -2621,10 +3328,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);
@@@ -3342,57 -2636,18 +3346,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)) {
@@@ -3412,102 -2667,57 +3416,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)
 -        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) {
 +      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");
@@@ -3563,7 -2773,7 +3567,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);
@@@ -3686,8 -2896,9 +3690,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);
  }
@@@ -3699,7 -2910,7 +3703,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);                                      \
@@@ -3980,7 -3191,7 +3984,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];
@@@ -4094,11 -3305,17 +4098,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);
@@@ -4125,214 -3342,14 +4129,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.
@@@ -4357,7 -3374,7 +4361,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 */
@@@ -4453,23 -3470,6 +4457,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;
@@@ -4495,14 -3495,8 +4499,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.
@@@ -4591,7 -3585,7 +4595,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;
    }
@@@ -4710,7 -3704,7 +4714,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. */
@@@ -4724,11 -3718,12 +4728,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)
@@@ -4886,7 -3881,7 +4890,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. */
@@@ -4959,7 -3954,7 +4963,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);
@@@ -4998,7 -3993,7 +5002,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. */
@@@ -5070,7 -4065,7 +5074,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);
diff --combined src/test/test_crypto.c
index 4638786,0000000..8093c9c
mode 100644,000000..100644
--- a/src/test/test_crypto.c
+++ b/src/test/test_crypto.c
@@@ -1,788 -1,0 +1,794 @@@
 +/* Copyright (c) 2001-2004, Roger Dingledine.
 + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 + * Copyright (c) 2007-2011, The Tor Project, Inc. */
 +/* See LICENSE for licensing information */
 +
 +#include "orconfig.h"
 +#define CRYPTO_PRIVATE
 +#include "or.h"
 +#include "test.h"
 +
 +/** Run unit tests for Diffie-Hellman functionality. */
 +static void
 +test_crypto_dh(void)
 +{
 +  crypto_dh_env_t *dh1 = crypto_dh_new();
 +  crypto_dh_env_t *dh2 = crypto_dh_new();
 +  char p1[DH_BYTES];
 +  char p2[DH_BYTES];
 +  char s1[DH_BYTES];
 +  char s2[DH_BYTES];
 +  ssize_t s1len, s2len;
 +
 +  test_eq(crypto_dh_get_bytes(dh1), DH_BYTES);
 +  test_eq(crypto_dh_get_bytes(dh2), DH_BYTES);
 +
 +  memset(p1, 0, DH_BYTES);
 +  memset(p2, 0, DH_BYTES);
 +  test_memeq(p1, p2, DH_BYTES);
 +  test_assert(! crypto_dh_get_public(dh1, p1, DH_BYTES));
 +  test_memneq(p1, p2, DH_BYTES);
 +  test_assert(! crypto_dh_get_public(dh2, p2, DH_BYTES));
 +  test_memneq(p1, p2, DH_BYTES);
 +
 +  memset(s1, 0, DH_BYTES);
 +  memset(s2, 0xFF, DH_BYTES);
 +  s1len = crypto_dh_compute_secret(LOG_WARN, dh1, p2, DH_BYTES, s1, 50);
 +  s2len = crypto_dh_compute_secret(LOG_WARN, dh2, p1, DH_BYTES, s2, 50);
 +  test_assert(s1len > 0);
 +  test_eq(s1len, s2len);
 +  test_memeq(s1, s2, s1len);
 +
 +  {
 +    /* XXXX Now fabricate some bad values and make sure they get caught,
 +     * Check 0, 1, N-1, >= N, etc.
 +     */
 +  }
 +
 + done:
 +  crypto_dh_free(dh1);
 +  crypto_dh_free(dh2);
 +}
 +
 +/** Run unit tests for our random number generation function and its wrappers.
 + */
 +static void
 +test_crypto_rng(void)
 +{
 +  int i, j, allok;
 +  char data1[100], data2[100];
 +  double d;
 +
 +  /* Try out RNG. */
 +  test_assert(! crypto_seed_rng(0));
 +  crypto_rand(data1, 100);
 +  crypto_rand(data2, 100);
 +  test_memneq(data1,data2,100);
 +  allok = 1;
 +  for (i = 0; i < 100; ++i) {
 +    uint64_t big;
 +    char *host;
 +    j = crypto_rand_int(100);
 +    if (i < 0 || i >= 100)
 +      allok = 0;
 +    big = crypto_rand_uint64(U64_LITERAL(1)<<40);
 +    if (big >= (U64_LITERAL(1)<<40))
 +      allok = 0;
 +    big = crypto_rand_uint64(U64_LITERAL(5));
 +    if (big >= 5)
 +      allok = 0;
 +    d = crypto_rand_double();
 +    test_assert(d >= 0);
 +    test_assert(d < 1.0);
 +    host = crypto_random_hostname(3,8,"www.",".onion");
 +    if (strcmpstart(host,"www.") ||
 +        strcmpend(host,".onion") ||
 +        strlen(host) < 13 ||
 +        strlen(host) > 18)
 +      allok = 0;
 +    tor_free(host);
 +  }
 +  test_assert(allok);
 + done:
 +  ;
 +}
 +
 +/** Run unit tests for our AES functionality */
 +static void
 +test_crypto_aes(void)
 +{
 +  char *data1 = NULL, *data2 = NULL, *data3 = NULL;
 +  crypto_cipher_env_t *env1 = NULL, *env2 = NULL;
 +  int i, j;
 +  char *mem_op_hex_tmp=NULL;
 +
 +  data1 = tor_malloc(1024);
 +  data2 = tor_malloc(1024);
 +  data3 = tor_malloc(1024);
 +
 +  /* Now, test encryption and decryption with stream cipher. */
 +  data1[0]='\0';
 +  for (i = 1023; i>0; i -= 35)
 +    strncat(data1, "Now is the time for all good onions", i);
 +
 +  memset(data2, 0, 1024);
 +  memset(data3, 0, 1024);
 +  env1 = crypto_new_cipher_env();
 +  test_neq(env1, 0);
 +  env2 = crypto_new_cipher_env();
 +  test_neq(env2, 0);
 +  j = crypto_cipher_generate_key(env1);
 +  crypto_cipher_set_key(env2, crypto_cipher_get_key(env1));
 +  crypto_cipher_encrypt_init_cipher(env1);
 +  crypto_cipher_decrypt_init_cipher(env2);
 +
 +  /* Try encrypting 512 chars. */
 +  crypto_cipher_encrypt(env1, data2, data1, 512);
 +  crypto_cipher_decrypt(env2, data3, data2, 512);
 +  test_memeq(data1, data3, 512);
 +  test_memneq(data1, data2, 512);
 +
 +  /* Now encrypt 1 at a time, and get 1 at a time. */
 +  for (j = 512; j < 560; ++j) {
 +    crypto_cipher_encrypt(env1, data2+j, data1+j, 1);
 +  }
 +  for (j = 512; j < 560; ++j) {
 +    crypto_cipher_decrypt(env2, data3+j, data2+j, 1);
 +  }
 +  test_memeq(data1, data3, 560);
 +  /* Now encrypt 3 at a time, and get 5 at a time. */
 +  for (j = 560; j < 1024-5; j += 3) {
 +    crypto_cipher_encrypt(env1, data2+j, data1+j, 3);
 +  }
 +  for (j = 560; j < 1024-5; j += 5) {
 +    crypto_cipher_decrypt(env2, data3+j, data2+j, 5);
 +  }
 +  test_memeq(data1, data3, 1024-5);
 +  /* Now make sure that when we encrypt with different chunk sizes, we get
 +     the same results. */
 +  crypto_free_cipher_env(env2);
 +  env2 = NULL;
 +
 +  memset(data3, 0, 1024);
 +  env2 = crypto_new_cipher_env();
 +  test_neq(env2, 0);
 +  crypto_cipher_set_key(env2, crypto_cipher_get_key(env1));
 +  crypto_cipher_encrypt_init_cipher(env2);
 +  for (j = 0; j < 1024-16; j += 17) {
 +    crypto_cipher_encrypt(env2, data3+j, data1+j, 17);
 +  }
 +  for (j= 0; j < 1024-16; ++j) {
 +    if (data2[j] != data3[j]) {
 +      printf("%d:  %d\t%d\n", j, (int) data2[j], (int) data3[j]);
 +    }
 +  }
 +  test_memeq(data2, data3, 1024-16);
 +  crypto_free_cipher_env(env1);
 +  env1 = NULL;
 +  crypto_free_cipher_env(env2);
 +  env2 = NULL;
 +
 +  /* NIST test vector for aes. */
 +  env1 = crypto_new_cipher_env(); /* IV starts at 0 */
 +  crypto_cipher_set_key(env1, "\x80\x00\x00\x00\x00\x00\x00\x00"
 +                              "\x00\x00\x00\x00\x00\x00\x00\x00");
 +  crypto_cipher_encrypt_init_cipher(env1);
 +  crypto_cipher_encrypt(env1, data1,
 +                        "\x00\x00\x00\x00\x00\x00\x00\x00"
 +                        "\x00\x00\x00\x00\x00\x00\x00\x00", 16);
 +  test_memeq_hex(data1, "0EDD33D3C621E546455BD8BA1418BEC8");
 +
 +  /* Now test rollover.  All these values are originally from a python
 +   * script. */
 +  crypto_cipher_set_iv(env1, "\x00\x00\x00\x00\x00\x00\x00\x00"
 +                             "\xff\xff\xff\xff\xff\xff\xff\xff");
 +  memset(data2, 0,  1024);
 +  crypto_cipher_encrypt(env1, data1, data2, 32);
 +  test_memeq_hex(data1, "335fe6da56f843199066c14a00a40231"
 +                        "cdd0b917dbc7186908a6bfb5ffd574d3");
 +
 +  crypto_cipher_set_iv(env1, "\x00\x00\x00\x00\xff\xff\xff\xff"
 +                             "\xff\xff\xff\xff\xff\xff\xff\xff");
 +  memset(data2, 0,  1024);
 +  crypto_cipher_encrypt(env1, data1, data2, 32);
 +  test_memeq_hex(data1, "e627c6423fa2d77832a02b2794094b73"
 +                        "3e63c721df790d2c6469cc1953a3ffac");
 +
 +  crypto_cipher_set_iv(env1, "\xff\xff\xff\xff\xff\xff\xff\xff"
 +                             "\xff\xff\xff\xff\xff\xff\xff\xff");
 +  memset(data2, 0,  1024);
 +  crypto_cipher_encrypt(env1, data1, data2, 32);
 +  test_memeq_hex(data1, "2aed2bff0de54f9328efd070bf48f70a"
 +                        "0EDD33D3C621E546455BD8BA1418BEC8");
 +
 +  /* Now check rollover on inplace cipher. */
 +  crypto_cipher_set_iv(env1, "\xff\xff\xff\xff\xff\xff\xff\xff"
 +                             "\xff\xff\xff\xff\xff\xff\xff\xff");
 +  crypto_cipher_crypt_inplace(env1, data2, 64);
 +  test_memeq_hex(data2, "2aed2bff0de54f9328efd070bf48f70a"
 +                        "0EDD33D3C621E546455BD8BA1418BEC8"
 +                        "93e2c5243d6839eac58503919192f7ae"
 +                        "1908e67cafa08d508816659c2e693191");
 +  crypto_cipher_set_iv(env1, "\xff\xff\xff\xff\xff\xff\xff\xff"
 +                             "\xff\xff\xff\xff\xff\xff\xff\xff");
 +  crypto_cipher_crypt_inplace(env1, data2, 64);
 +  test_assert(tor_mem_is_zero(data2, 64));
 +
 + done:
 +  tor_free(mem_op_hex_tmp);
 +  if (env1)
 +    crypto_free_cipher_env(env1);
 +  if (env2)
 +    crypto_free_cipher_env(env2);
 +  tor_free(data1);
 +  tor_free(data2);
 +  tor_free(data3);
 +}
 +
 +/** Run unit tests for our SHA-1 functionality */
 +static void
 +test_crypto_sha(void)
 +{
 +  crypto_digest_env_t *d1 = NULL, *d2 = NULL;
 +  int i;
 +  char key[80];
 +  char digest[32];
 +  char data[50];
 +  char d_out1[DIGEST_LEN], d_out2[DIGEST256_LEN];
 +  char *mem_op_hex_tmp=NULL;
 +
 +  /* Test SHA-1 with a test vector from the specification. */
 +  i = crypto_digest(data, "abc", 3);
 +  test_memeq_hex(data, "A9993E364706816ABA3E25717850C26C9CD0D89D");
 +
 +  /* Test SHA-256 with a test vector from the specification. */
 +  i = crypto_digest256(data, "abc", 3, DIGEST_SHA256);
 +  test_memeq_hex(data, "BA7816BF8F01CFEA414140DE5DAE2223B00361A3"
 +                       "96177A9CB410FF61F20015AD");
 +
 +  /* Test HMAC-SHA-1 with test cases from RFC2202. */
 +
 +  /* Case 1. */
 +  memset(key, 0x0b, 20);
 +  crypto_hmac_sha1(digest, key, 20, "Hi There", 8);
 +  test_streq(hex_str(digest, 20),
 +             "B617318655057264E28BC0B6FB378C8EF146BE00");
 +  /* Case 2. */
 +  crypto_hmac_sha1(digest, "Jefe", 4, "what do ya want for nothing?", 28);
 +  test_streq(hex_str(digest, 20),
 +             "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79");
 +
 +  /* Case 4. */
 +  base16_decode(key, 25,
 +                "0102030405060708090a0b0c0d0e0f10111213141516171819", 50);
 +  memset(data, 0xcd, 50);
 +  crypto_hmac_sha1(digest, key, 25, data, 50);
 +  test_streq(hex_str(digest, 20),
 +             "4C9007F4026250C6BC8414F9BF50C86C2D7235DA");
 +
 +  /* Case 5. */
 +  memset(key, 0xaa, 80);
 +  crypto_hmac_sha1(digest, key, 80,
 +                   "Test Using Larger Than Block-Size Key - Hash Key First",
 +                   54);
 +  test_streq(hex_str(digest, 20),
 +             "AA4AE5E15272D00E95705637CE8A3B55ED402112");
 +
 +  /* Incremental digest code. */
 +  d1 = crypto_new_digest_env();
 +  test_assert(d1);
 +  crypto_digest_add_bytes(d1, "abcdef", 6);
 +  d2 = crypto_digest_dup(d1);
 +  test_assert(d2);
 +  crypto_digest_add_bytes(d2, "ghijkl", 6);
 +  crypto_digest_get_digest(d2, d_out1, sizeof(d_out1));
 +  crypto_digest(d_out2, "abcdefghijkl", 12);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +  crypto_digest_assign(d2, d1);
 +  crypto_digest_add_bytes(d2, "mno", 3);
 +  crypto_digest_get_digest(d2, d_out1, sizeof(d_out1));
 +  crypto_digest(d_out2, "abcdefmno", 9);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +  crypto_digest_get_digest(d1, d_out1, sizeof(d_out1));
 +  crypto_digest(d_out2, "abcdef", 6);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +  crypto_free_digest_env(d1);
 +  crypto_free_digest_env(d2);
 +
 +  /* Incremental digest code with sha256 */
 +  d1 = crypto_new_digest256_env(DIGEST_SHA256);
 +  test_assert(d1);
 +  crypto_digest_add_bytes(d1, "abcdef", 6);
 +  d2 = crypto_digest_dup(d1);
 +  test_assert(d2);
 +  crypto_digest_add_bytes(d2, "ghijkl", 6);
 +  crypto_digest_get_digest(d2, d_out1, sizeof(d_out1));
 +  crypto_digest256(d_out2, "abcdefghijkl", 12, DIGEST_SHA256);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +  crypto_digest_assign(d2, d1);
 +  crypto_digest_add_bytes(d2, "mno", 3);
 +  crypto_digest_get_digest(d2, d_out1, sizeof(d_out1));
 +  crypto_digest256(d_out2, "abcdefmno", 9, DIGEST_SHA256);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +  crypto_digest_get_digest(d1, d_out1, sizeof(d_out1));
 +  crypto_digest256(d_out2, "abcdef", 6, DIGEST_SHA256);
 +  test_memeq(d_out1, d_out2, DIGEST_LEN);
 +
 + done:
 +  if (d1)
 +    crypto_free_digest_env(d1);
 +  if (d2)
 +    crypto_free_digest_env(d2);
 +  tor_free(mem_op_hex_tmp);
 +}
 +
 +/** Run unit tests for our public key crypto functions */
 +static void
 +test_crypto_pk(void)
 +{
 +  crypto_pk_env_t *pk1 = NULL, *pk2 = NULL;
 +  char *encoded = NULL;
 +  char data1[1024], data2[1024], data3[1024];
 +  size_t size;
 +  int i, j, p, len;
 +
 +  /* Public-key ciphers */
 +  pk1 = pk_generate(0);
 +  pk2 = crypto_new_pk_env();
 +  test_assert(pk1 && pk2);
 +  test_assert(! crypto_pk_write_public_key_to_string(pk1, &encoded, &size));
 +  test_assert(! crypto_pk_read_public_key_from_string(pk2, encoded, size));
 +  test_eq(0, crypto_pk_cmp_keys(pk1, pk2));
 +
 +  test_eq(128, crypto_pk_keysize(pk1));
 +  test_eq(128, crypto_pk_keysize(pk2));
 +
-   test_eq(128, crypto_pk_public_encrypt(pk2, data1, "Hello whirled.", 15,
++  test_eq(128, crypto_pk_public_encrypt(pk2, data1, sizeof(data1),
++                                        "Hello whirled.", 15,
 +                                        PK_PKCS1_OAEP_PADDING));
-   test_eq(128, crypto_pk_public_encrypt(pk1, data2, "Hello whirled.", 15,
++  test_eq(128, crypto_pk_public_encrypt(pk1, data2, sizeof(data1),
++                                        "Hello whirled.", 15,
 +                                        PK_PKCS1_OAEP_PADDING));
 +  /* oaep padding should make encryption not match */
 +  test_memneq(data1, data2, 128);
-   test_eq(15, crypto_pk_private_decrypt(pk1, data3, data1, 128,
++  test_eq(15, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data1, 128,
 +                                        PK_PKCS1_OAEP_PADDING,1));
 +  test_streq(data3, "Hello whirled.");
 +  memset(data3, 0, 1024);
-   test_eq(15, crypto_pk_private_decrypt(pk1, data3, data2, 128,
++  test_eq(15, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data2, 128,
 +                                        PK_PKCS1_OAEP_PADDING,1));
 +  test_streq(data3, "Hello whirled.");
 +  /* Can't decrypt with public key. */
-   test_eq(-1, crypto_pk_private_decrypt(pk2, data3, data2, 128,
++  test_eq(-1, crypto_pk_private_decrypt(pk2, data3, sizeof(data3), data2, 128,
 +                                        PK_PKCS1_OAEP_PADDING,1));
 +  /* Try again with bad padding */
 +  memcpy(data2+1, "XYZZY", 5);  /* This has fails ~ once-in-2^40 */
-   test_eq(-1, crypto_pk_private_decrypt(pk1, data3, data2, 128,
++  test_eq(-1, crypto_pk_private_decrypt(pk1, data3, sizeof(data3), data2, 128,
 +                                        PK_PKCS1_OAEP_PADDING,1));
 +
 +  /* File operations: save and load private key */
 +  test_assert(! crypto_pk_write_private_key_to_filename(pk1,
 +                                                        get_fname("pkey1")));
 +  /* failing case for read: can't read. */
 +  test_assert(crypto_pk_read_private_key_from_filename(pk2,
 +                                                   get_fname("xyzzy")) < 0);
 +  write_str_to_file(get_fname("xyzzy"), "foobar", 6);
 +  /* Failing case for read: no key. */
 +  test_assert(crypto_pk_read_private_key_from_filename(pk2,
 +                                                   get_fname("xyzzy")) < 0);
 +  test_assert(! crypto_pk_read_private_key_from_filename(pk2,
 +                                                         get_fname("pkey1")));
-   test_eq(15, crypto_pk_private_decrypt(pk2, data3, data1, 128,
++  test_eq(15, crypto_pk_private_decrypt(pk2, data3, sizeof(data3), data1, 128,
 +                                        PK_PKCS1_OAEP_PADDING,1));
 +
 +  /* Now try signing. */
 +  strlcpy(data1, "Ossifrage", 1024);
-   test_eq(128, crypto_pk_private_sign(pk1, data2, data1, 10));
-   test_eq(10, crypto_pk_public_checksig(pk1, data3, data2, 128));
++  test_eq(128, crypto_pk_private_sign(pk1, data2, sizeof(data2), data1, 10));
++  test_eq(10, crypto_pk_public_checksig(pk1, data3, sizeof(data3), data2, 128));
 +  test_streq(data3, "Ossifrage");
 +  /* Try signing digests. */
-   test_eq(128, crypto_pk_private_sign_digest(pk1, data2, data1, 10));
-   test_eq(20, crypto_pk_public_checksig(pk1, data3, data2, 128));
++  test_eq(128, crypto_pk_private_sign_digest(pk1, data2, sizeof(data2),
++                                             data1, 10));
++  test_eq(20, crypto_pk_public_checksig(pk1, data3, sizeof(data3), data2, 128));
 +  test_eq(0, crypto_pk_public_checksig_digest(pk1, data1, 10, data2, 128));
 +  test_eq(-1, crypto_pk_public_checksig_digest(pk1, data1, 11, data2, 128));
++
 +  /*XXXX test failed signing*/
 +
 +  /* Try encoding */
 +  crypto_free_pk_env(pk2);
 +  pk2 = NULL;
 +  i = crypto_pk_asn1_encode(pk1, data1, 1024);
 +  test_assert(i>0);
 +  pk2 = crypto_pk_asn1_decode(data1, i);
 +  test_assert(crypto_pk_cmp_keys(pk1,pk2) == 0);
 +
 +  /* Try with hybrid encryption wrappers. */
 +  crypto_rand(data1, 1024);
 +  for (i = 0; i < 3; ++i) {
 +    for (j = 85; j < 140; ++j) {
 +      memset(data2,0,1024);
 +      memset(data3,0,1024);
 +      if (i == 0 && j < 129)
 +        continue;
 +      p = (i==0)?PK_NO_PADDING:
 +        (i==1)?PK_PKCS1_PADDING:PK_PKCS1_OAEP_PADDING;
-       len = crypto_pk_public_hybrid_encrypt(pk1,data2,data1,j,p,0);
++      len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2),
++                                            data1,j,p,0);
 +      test_assert(len>=0);
-       len = crypto_pk_private_hybrid_decrypt(pk1,data3,data2,len,p,1);
++      len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3),
++                                             data2,len,p,1);
 +      test_eq(len,j);
 +      test_memeq(data1,data3,j);
 +    }
 +  }
 +
 +  /* Try copy_full */
 +  crypto_free_pk_env(pk2);
 +  pk2 = crypto_pk_copy_full(pk1);
 +  test_assert(pk2 != NULL);
 +  test_neq_ptr(pk1, pk2);
 +  test_assert(crypto_pk_cmp_keys(pk1,pk2) == 0);
 +
 + done:
 +  if (pk1)
 +    crypto_free_pk_env(pk1);
 +  if (pk2)
 +    crypto_free_pk_env(pk2);
 +  tor_free(encoded);
 +}
 +
 +/** Run unit tests for misc crypto formatting functionality (base64, base32,
 + * fingerprints, etc) */
 +static void
 +test_crypto_formats(void)
 +{
 +  char *data1 = NULL, *data2 = NULL, *data3 = NULL;
 +  int i, j, idx;
 +
 +  data1 = tor_malloc(1024);
 +  data2 = tor_malloc(1024);
 +  data3 = tor_malloc(1024);
 +  test_assert(data1 && data2 && data3);
 +
 +  /* Base64 tests */
 +  memset(data1, 6, 1024);
 +  for (idx = 0; idx < 10; ++idx) {
 +    i = base64_encode(data2, 1024, data1, idx);
 +    test_assert(i >= 0);
 +    j = base64_decode(data3, 1024, data2, i);
 +    test_eq(j,idx);
 +    test_memeq(data3, data1, idx);
 +  }
 +
 +  strlcpy(data1, "Test string that contains 35 chars.", 1024);
 +  strlcat(data1, " 2nd string that contains 35 chars.", 1024);
 +
 +  i = base64_encode(data2, 1024, data1, 71);
 +  test_assert(i >= 0);
 +  j = base64_decode(data3, 1024, data2, i);
 +  test_eq(j, 71);
 +  test_streq(data3, data1);
 +  test_assert(data2[i] == '\0');
 +
 +  crypto_rand(data1, DIGEST_LEN);
 +  memset(data2, 100, 1024);
 +  digest_to_base64(data2, data1);
 +  test_eq(BASE64_DIGEST_LEN, strlen(data2));
 +  test_eq(100, data2[BASE64_DIGEST_LEN+2]);
 +  memset(data3, 99, 1024);
 +  test_eq(digest_from_base64(data3, data2), 0);
 +  test_memeq(data1, data3, DIGEST_LEN);
 +  test_eq(99, data3[DIGEST_LEN+1]);
 +
 +  test_assert(digest_from_base64(data3, "###") < 0);
 +
 +  /* Encoding SHA256 */
 +  crypto_rand(data2, DIGEST256_LEN);
 +  memset(data2, 100, 1024);
 +  digest256_to_base64(data2, data1);
 +  test_eq(BASE64_DIGEST256_LEN, strlen(data2));
 +  test_eq(100, data2[BASE64_DIGEST256_LEN+2]);
 +  memset(data3, 99, 1024);
 +  test_eq(digest256_from_base64(data3, data2), 0);
 +  test_memeq(data1, data3, DIGEST256_LEN);
 +  test_eq(99, data3[DIGEST256_LEN+1]);
 +
 +  /* Base32 tests */
 +  strlcpy(data1, "5chrs", 1024);
 +  /* bit pattern is:  [35 63 68 72 73] ->
 +   *        [00110101 01100011 01101000 01110010 01110011]
 +   * By 5s: [00110 10101 10001 10110 10000 11100 10011 10011]
 +   */
 +  base32_encode(data2, 9, data1, 5);
 +  test_streq(data2, "gvrwq4tt");
 +
 +  strlcpy(data1, "\xFF\xF5\x6D\x44\xAE\x0D\x5C\xC9\x62\xC4", 1024);
 +  base32_encode(data2, 30, data1, 10);
 +  test_streq(data2, "772w2rfobvomsywe");
 +
 +  /* Base16 tests */
 +  strlcpy(data1, "6chrs\xff", 1024);
 +  base16_encode(data2, 13, data1, 6);
 +  test_streq(data2, "3663687273FF");
 +
 +  strlcpy(data1, "f0d678affc000100", 1024);
 +  i = base16_decode(data2, 8, data1, 16);
 +  test_eq(i,0);
 +  test_memeq(data2, "\xf0\xd6\x78\xaf\xfc\x00\x01\x00",8);
 +
 +  /* now try some failing base16 decodes */
 +  test_eq(-1, base16_decode(data2, 8, data1, 15)); /* odd input len */
 +  test_eq(-1, base16_decode(data2, 7, data1, 16)); /* dest too short */
 +  strlcpy(data1, "f0dz!8affc000100", 1024);
 +  test_eq(-1, base16_decode(data2, 8, data1, 16));
 +
 +  tor_free(data1);
 +  tor_free(data2);
 +  tor_free(data3);
 +
 +  /* Add spaces to fingerprint */
 +  {
 +    data1 = tor_strdup("ABCD1234ABCD56780000ABCD1234ABCD56780000");
 +    test_eq(strlen(data1), 40);
 +    data2 = tor_malloc(FINGERPRINT_LEN+1);
 +    add_spaces_to_fp(data2, FINGERPRINT_LEN+1, data1);
 +    test_streq(data2, "ABCD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 0000");
 +    tor_free(data1);
 +    tor_free(data2);
 +  }
 +
 +  /* Check fingerprint */
 +  {
 +    test_assert(crypto_pk_check_fingerprint_syntax(
 +                "ABCD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 0000"));
 +    test_assert(!crypto_pk_check_fingerprint_syntax(
 +                "ABCD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 000"));
 +    test_assert(!crypto_pk_check_fingerprint_syntax(
 +                "ABCD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 00000"));
 +    test_assert(!crypto_pk_check_fingerprint_syntax(
 +                "ABCD 1234 ABCD 5678 0000 ABCD1234 ABCD 5678 0000"));
 +    test_assert(!crypto_pk_check_fingerprint_syntax(
 +                "ABCD 1234 ABCD 5678 0000 ABCD1234 ABCD 5678 00000"));
 +    test_assert(!crypto_pk_check_fingerprint_syntax(
 +                "ACD 1234 ABCD 5678 0000 ABCD 1234 ABCD 5678 00000"));
 +  }
 +
 + done:
 +  tor_free(data1);
 +  tor_free(data2);
 +  tor_free(data3);
 +}
 +
 +/** Run unit tests for our secret-to-key passphrase hashing functionality. */
 +static void
 +test_crypto_s2k(void)
 +{
 +  char buf[29];
 +  char buf2[29];
 +  char *buf3 = NULL;
 +  int i;
 +
 +  memset(buf, 0, sizeof(buf));
 +  memset(buf2, 0, sizeof(buf2));
 +  buf3 = tor_malloc(65536);
 +  memset(buf3, 0, 65536);
 +
 +  secret_to_key(buf+9, 20, "", 0, buf);
 +  crypto_digest(buf2+9, buf3, 1024);
 +  test_memeq(buf, buf2, 29);
 +
 +  memcpy(buf,"vrbacrda",8);
 +  memcpy(buf2,"vrbacrda",8);
 +  buf[8] = 96;
 +  buf2[8] = 96;
 +  secret_to_key(buf+9, 20, "12345678", 8, buf);
 +  for (i = 0; i < 65536; i += 16) {
 +    memcpy(buf3+i, "vrbacrda12345678", 16);
 +  }
 +  crypto_digest(buf2+9, buf3, 65536);
 +  test_memeq(buf, buf2, 29);
 +
 + done:
 +  tor_free(buf3);
 +}
 +
 +/** Test AES-CTR encryption and decryption with IV. */
 +static void
 +test_crypto_aes_iv(void)
 +{
 +  crypto_cipher_env_t *cipher;
 +  char *plain, *encrypted1, *encrypted2, *decrypted1, *decrypted2;
 +  char plain_1[1], plain_15[15], plain_16[16], plain_17[17];
 +  char key1[16], key2[16];
 +  ssize_t encrypted_size, decrypted_size;
 +
 +  plain = tor_malloc(4095);
 +  encrypted1 = tor_malloc(4095 + 1 + 16);
 +  encrypted2 = tor_malloc(4095 + 1 + 16);
 +  decrypted1 = tor_malloc(4095 + 1);
 +  decrypted2 = tor_malloc(4095 + 1);
 +
 +  crypto_rand(plain, 4095);
 +  crypto_rand(key1, 16);
 +  crypto_rand(key2, 16);
 +  crypto_rand(plain_1, 1);
 +  crypto_rand(plain_15, 15);
 +  crypto_rand(plain_16, 16);
 +  crypto_rand(plain_17, 17);
 +  key1[0] = key2[0] + 128; /* Make sure that contents are different. */
 +  /* Encrypt and decrypt with the same key. */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted1, 16 + 4095,
 +                                                 plain, 4095);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 4095);
 +  tor_assert(encrypted_size > 0); /* This is obviously true, since 4111 is
 +                                   * greater than 0, but its truth is not
 +                                   * obvious to all analysis tools. */
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 4095,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(decrypted_size, 4095);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain, decrypted1, 4095);
 +  /* Encrypt a second time (with a new random initialization vector). */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted2, 16 + 4095,
 +                                             plain, 4095);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 4095);
 +  tor_assert(encrypted_size > 0);
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted2, 4095,
 +                                             encrypted2, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(decrypted_size, 4095);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain, decrypted2, 4095);
 +  test_memneq(encrypted1, encrypted2, encrypted_size);
 +  /* Decrypt with the wrong key. */
 +  cipher = crypto_create_init_cipher(key2, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted2, 4095,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_memneq(plain, decrypted2, encrypted_size);
 +  /* Alter the initialization vector. */
 +  encrypted1[0] += 42;
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 4095,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_memneq(plain, decrypted2, 4095);
 +  /* Special length case: 1. */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted1, 16 + 1,
 +                                             plain_1, 1);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 1);
 +  tor_assert(encrypted_size > 0);
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 1,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(decrypted_size, 1);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain_1, decrypted1, 1);
 +  /* Special length case: 15. */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted1, 16 + 15,
 +                                             plain_15, 15);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 15);
 +  tor_assert(encrypted_size > 0);
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 15,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(decrypted_size, 15);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain_15, decrypted1, 15);
 +  /* Special length case: 16. */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted1, 16 + 16,
 +                                             plain_16, 16);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 16);
 +  tor_assert(encrypted_size > 0);
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 16,
 +                                             encrypted1, encrypted_size);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(decrypted_size, 16);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain_16, decrypted1, 16);
 +  /* Special length case: 17. */
 +  cipher = crypto_create_init_cipher(key1, 1);
 +  encrypted_size = crypto_cipher_encrypt_with_iv(cipher, encrypted1, 16 + 17,
 +                                             plain_17, 17);
 +  crypto_free_cipher_env(cipher);
 +  cipher = NULL;
 +  test_eq(encrypted_size, 16 + 17);
 +  tor_assert(encrypted_size > 0);
 +  cipher = crypto_create_init_cipher(key1, 0);
 +  decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 17,
 +                                             encrypted1, encrypted_size);
 +  test_eq(decrypted_size, 17);
 +  tor_assert(decrypted_size > 0);
 +  test_memeq(plain_17, decrypted1, 17);
 +
 + done:
 +  /* Free memory. */
 +  tor_free(plain);
 +  tor_free(encrypted1);
 +  tor_free(encrypted2);
 +  tor_free(decrypted1);
 +  tor_free(decrypted2);
 +  if (cipher)
 +    crypto_free_cipher_env(cipher);
 +}
 +
 +/** Test base32 decoding. */
 +static void
 +test_crypto_base32_decode(void)
 +{
 +  char plain[60], encoded[96 + 1], decoded[60];
 +  int res;
 +  crypto_rand(plain, 60);
 +  /* Encode and decode a random string. */
 +  base32_encode(encoded, 96 + 1, plain, 60);
 +  res = base32_decode(decoded, 60, encoded, 96);
 +  test_eq(res, 0);
 +  test_memeq(plain, decoded, 60);
 +  /* Encode, uppercase, and decode a random string. */
 +  base32_encode(encoded, 96 + 1, plain, 60);
 +  tor_strupper(encoded);
 +  res = base32_decode(decoded, 60, encoded, 96);
 +  test_eq(res, 0);
 +  test_memeq(plain, decoded, 60);
 +  /* Change encoded string and decode. */
 +  if (encoded[0] == 'A' || encoded[0] == 'a')
 +    encoded[0] = 'B';
 +  else
 +    encoded[0] = 'A';
 +  res = base32_decode(decoded, 60, encoded, 96);
 +  test_eq(res, 0);
 +  test_memneq(plain, decoded, 60);
 +  /* Bad encodings. */
 +  encoded[0] = '!';
 +  res = base32_decode(decoded, 60, encoded, 96);
 +  test_assert(res < 0);
 +
 + done:
 +  ;
 +}
 +
 +#define CRYPTO_LEGACY(name)                                            \
 +  { #name, legacy_test_helper, 0, &legacy_setup, test_crypto_ ## name }
 +
 +struct testcase_t crypto_tests[] = {
 +  CRYPTO_LEGACY(formats),
 +  CRYPTO_LEGACY(rng),
 +  CRYPTO_LEGACY(aes),
 +  CRYPTO_LEGACY(sha),
 +  CRYPTO_LEGACY(pk),
 +  CRYPTO_LEGACY(dh),
 +  CRYPTO_LEGACY(s2k),
 +  CRYPTO_LEGACY(aes_iv),
 +  CRYPTO_LEGACY(base32_decode),
 +  END_OF_TESTCASES
 +};
 +