tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

dircache.c (62357B)


      1 /* Copyright (c) 2001-2004, Roger Dingledine.
      2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      3 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      4 /* See LICENSE for licensing information */
      5 
      6 /**
      7 * @file dircache.c
      8 * @brief Cache directories and serve them to clients.
      9 **/
     10 
     11 #define DIRCACHE_PRIVATE
     12 
     13 #include "core/or/or.h"
     14 
     15 #include "app/config/config.h"
     16 #include "app/config/resolve_addr.h"
     17 #include "core/mainloop/connection.h"
     18 #include "core/or/relay.h"
     19 #include "feature/dirauth/dirvote.h"
     20 #include "feature/dirauth/authmode.h"
     21 #include "feature/dirauth/process_descs.h"
     22 #include "feature/dircache/conscache.h"
     23 #include "feature/dircache/consdiffmgr.h"
     24 #include "feature/dircache/dircache.h"
     25 #include "feature/dircache/dirserv.h"
     26 #include "feature/dircommon/directory.h"
     27 #include "feature/dircommon/fp_pair.h"
     28 #include "feature/hs/hs_cache.h"
     29 #include "feature/hs/hs_ident.h"
     30 #include "feature/nodelist/authcert.h"
     31 #include "feature/nodelist/networkstatus.h"
     32 #include "feature/nodelist/routerlist.h"
     33 #include "feature/relay/relay_config.h"
     34 #include "feature/relay/routermode.h"
     35 #include "feature/stats/geoip_stats.h"
     36 #include "feature/stats/rephist.h"
     37 #include "lib/compress/compress.h"
     38 #include "lib/crypt_ops/crypto_format.h"
     39 
     40 #include "feature/dircache/cached_dir_st.h"
     41 #include "feature/dircommon/dir_connection_st.h"
     42 #include "feature/nodelist/authority_cert_st.h"
     43 #include "feature/nodelist/networkstatus_st.h"
     44 #include "feature/nodelist/routerinfo_st.h"
     45 
     46 /** Maximum size, in bytes, for any directory object that we're accepting
     47 * as an upload. */
     48 #define MAX_DIR_UL_SIZE ((1<<24)-1) /* 16MB-1 */
     49 
     50 /** HTTP cache control: how long do we tell proxies they can cache each
     51 * kind of document we serve? */
     52 #define FULL_DIR_CACHE_LIFETIME (60*60)
     53 #define RUNNINGROUTERS_CACHE_LIFETIME (20*60)
     54 #define DIRPORTFRONTPAGE_CACHE_LIFETIME (20*60)
     55 #define NETWORKSTATUS_CACHE_LIFETIME (5*60)
     56 #define ROUTERDESC_CACHE_LIFETIME (30*60)
     57 #define ROUTERDESC_BY_DIGEST_CACHE_LIFETIME (48*60*60)
     58 #define ROBOTS_CACHE_LIFETIME (24*60*60)
     59 #define MICRODESC_CACHE_LIFETIME (48*60*60)
     60 /* Bandwidth files change every hour. */
     61 #define BANDWIDTH_CACHE_LIFETIME (30*60)
     62 /** Parse an HTTP request string <b>headers</b> of the form
     63 * \verbatim
     64 * "\%s [http[s]://]\%s HTTP/1..."
     65 * \endverbatim
     66 * If it's well-formed, strdup the second \%s into *<b>url</b>, and
     67 * nul-terminate it. If the url doesn't start with "/tor/", rewrite it
     68 * so it does. Return 0.
     69 * Otherwise, return -1.
     70 */
     71 STATIC int
     72 parse_http_url(const char *headers, char **url)
     73 {
     74  char *command = NULL;
     75  if (parse_http_command(headers, &command, url) < 0) {
     76    return -1;
     77  }
     78  if (strcmpstart(*url, "/tor/")) {
     79    char *new_url = NULL;
     80    tor_asprintf(&new_url, "/tor%s%s",
     81                 *url[0] == '/' ? "" : "/",
     82                 *url);
     83    tor_free(*url);
     84    *url = new_url;
     85  }
     86  tor_free(command);
     87  return 0;
     88 }
     89 
     90 /** Create an http response for the client <b>conn</b> out of
     91 * <b>status</b> and <b>reason_phrase</b>. Write it to <b>conn</b>.
     92 */
     93 static void
     94 write_short_http_response(dir_connection_t *conn, int status,
     95                       const char *reason_phrase)
     96 {
     97  char *buf = NULL;
     98  char *datestring = NULL;
     99 
    100  IF_BUG_ONCE(!reason_phrase) { /* bullet-proofing */
    101    reason_phrase = "unspecified";
    102  }
    103 
    104  if (server_mode(get_options())) {
    105    /* include the Date: header, but only if we're a relay or bridge */
    106    char datebuf[RFC1123_TIME_LEN+1];
    107    format_rfc1123_time(datebuf, time(NULL));
    108    tor_asprintf(&datestring, "Date: %s\r\n", datebuf);
    109  }
    110 
    111  tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n%s\r\n",
    112               status, reason_phrase, datestring?datestring:"");
    113 
    114  log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase);
    115  connection_buf_add(buf, strlen(buf), TO_CONN(conn));
    116 
    117  tor_free(datestring);
    118  tor_free(buf);
    119 }
    120 
    121 /** Write the header for an HTTP/1.0 response onto <b>conn</b>-\>outbuf,
    122 * with <b>type</b> as the Content-Type.
    123 *
    124 * If <b>length</b> is nonnegative, it is the Content-Length.
    125 * If <b>encoding</b> is provided, it is the Content-Encoding.
    126 * If <b>cache_lifetime</b> is greater than 0, the content may be cached for
    127 * up to cache_lifetime seconds.  Otherwise, the content may not be cached. */
    128 static void
    129 write_http_response_header_impl(dir_connection_t *conn, ssize_t length,
    130                           const char *type, const char *encoding,
    131                           const char *extra_headers,
    132                           long cache_lifetime)
    133 {
    134  char date[RFC1123_TIME_LEN+1];
    135  time_t now = approx_time();
    136  buf_t *buf = buf_new_with_capacity(1024);
    137 
    138  tor_assert(conn);
    139 
    140  format_rfc1123_time(date, now);
    141 
    142  buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date);
    143  if (type) {
    144    buf_add_printf(buf, "Content-Type: %s\r\n", type);
    145  }
    146  if (!is_local_to_resolve_addr(&conn->base_.addr)) {
    147    /* Don't report the source address for a nearby/private connection.
    148     * Otherwise we tend to mis-report in cases where incoming ports are
    149     * being forwarded to a Tor server running behind the firewall. */
    150    buf_add_printf(buf, X_ADDRESS_HEADER "%s\r\n", conn->base_.address);
    151  }
    152  if (encoding) {
    153    buf_add_printf(buf, "Content-Encoding: %s\r\n", encoding);
    154  }
    155  if (length >= 0) {
    156    buf_add_printf(buf, "Content-Length: %ld\r\n", (long)length);
    157  }
    158  if (cache_lifetime > 0) {
    159    char expbuf[RFC1123_TIME_LEN+1];
    160    format_rfc1123_time(expbuf, (time_t)(now + cache_lifetime));
    161    /* We could say 'Cache-control: max-age=%d' here if we start doing
    162     * http/1.1 */
    163    buf_add_printf(buf, "Expires: %s\r\n", expbuf);
    164  } else if (cache_lifetime == 0) {
    165    /* We could say 'Cache-control: no-cache' here if we start doing
    166     * http/1.1 */
    167    buf_add_string(buf, "Pragma: no-cache\r\n");
    168  }
    169  if (extra_headers) {
    170    buf_add_string(buf, extra_headers);
    171  }
    172  buf_add_string(buf, "\r\n");
    173 
    174  connection_buf_add_buf(TO_CONN(conn), buf);
    175  buf_free(buf);
    176 }
    177 
    178 /** As write_http_response_header_impl, but translates method into
    179 * encoding */
    180 static void
    181 write_http_response_headers(dir_connection_t *conn, ssize_t length,
    182                            compress_method_t method,
    183                            const char *extra_headers, long cache_lifetime)
    184 {
    185  write_http_response_header_impl(conn, length,
    186                                  "text/plain",
    187                                  compression_method_get_name(method),
    188                                  extra_headers,
    189                                  cache_lifetime);
    190 }
    191 
    192 /** As write_http_response_headers, but assumes extra_headers is NULL */
    193 static void
    194 write_http_response_header(dir_connection_t *conn, ssize_t length,
    195                           compress_method_t method,
    196                           long cache_lifetime)
    197 {
    198  write_http_response_headers(conn, length, method, NULL, cache_lifetime);
    199 }
    200 
    201 /** Array of compression methods to use (if supported) for serving
    202 * precompressed data, ordered from best to worst. */
    203 static compress_method_t srv_meth_pref_precompressed[] = {
    204  LZMA_METHOD,
    205  ZSTD_METHOD,
    206  ZLIB_METHOD,
    207  GZIP_METHOD,
    208  NO_METHOD
    209 };
    210 
    211 /** Array of compression methods to use (if supported) for serving
    212 * streamed data, ordered from best to worst. */
    213 static compress_method_t srv_meth_pref_streaming_compression[] = {
    214  ZSTD_METHOD,
    215  ZLIB_METHOD,
    216  GZIP_METHOD,
    217  NO_METHOD
    218 };
    219 
    220 /** Parse the compression methods listed in an Accept-Encoding header <b>h</b>,
    221 * and convert them to a bitfield where compression method x is supported if
    222 * and only if 1 &lt;&lt; x is set in the bitfield. */
    223 STATIC unsigned
    224 parse_accept_encoding_header(const char *h)
    225 {
    226  unsigned result = (1u << NO_METHOD);
    227  smartlist_t *methods = smartlist_new();
    228  smartlist_split_string(methods, h, ",",
    229             SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE|SPLIT_IGNORE_BLANK, 0);
    230 
    231  SMARTLIST_FOREACH_BEGIN(methods, const char *, m) {
    232    compress_method_t method = compression_method_get_by_name(m);
    233    if (method != UNKNOWN_METHOD) {
    234      tor_assert(((unsigned)method) < 8*sizeof(unsigned));
    235      result |= (1u << method);
    236    }
    237  } SMARTLIST_FOREACH_END(m);
    238  SMARTLIST_FOREACH_BEGIN(methods, char *, m) {
    239    tor_free(m);
    240  } SMARTLIST_FOREACH_END(m);
    241  smartlist_free(methods);
    242  return result;
    243 }
    244 
    245 /** Decide whether a client would accept the consensus we have.
    246 *
    247 * Clients can say they only want a consensus if it's signed by more
    248 * than half the authorities in a list.  They pass this list in
    249 * the url as "...consensus/<b>fpr</b>+<b>fpr</b>+<b>fpr</b>".
    250 *
    251 * <b>fpr</b> may be an abbreviated fingerprint, i.e. only a left substring
    252 * of the full authority identity digest. (Only strings of even length,
    253 * i.e. encodings of full bytes, are handled correctly.  In the case
    254 * of an odd number of hex digits the last one is silently ignored.)
    255 *
    256 * Returns 1 if more than half of the requested authorities signed the
    257 * consensus, 0 otherwise.
    258 */
    259 static int
    260 client_likes_consensus(const struct consensus_cache_entry_t *ent,
    261                       const char *want_url)
    262 {
    263  smartlist_t *voters = smartlist_new();
    264  int need_at_least;
    265  int have = 0;
    266 
    267  if (consensus_cache_entry_get_voter_id_digests(ent, voters) != 0) {
    268    smartlist_free(voters);
    269    return 1; // We don't know the voters; assume the client won't mind. */
    270  }
    271 
    272  smartlist_t *want_authorities = smartlist_new();
    273  dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0);
    274  need_at_least = smartlist_len(want_authorities)/2+1;
    275 
    276  SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, want_digest) {
    277 
    278    SMARTLIST_FOREACH_BEGIN(voters, const char *, digest) {
    279      if (!strcasecmpstart(digest, want_digest)) {
    280        have++;
    281        break;
    282      };
    283    } SMARTLIST_FOREACH_END(digest);
    284 
    285    /* early exit, if we already have enough */
    286    if (have >= need_at_least)
    287      break;
    288  } SMARTLIST_FOREACH_END(want_digest);
    289 
    290  SMARTLIST_FOREACH(want_authorities, char *, d, tor_free(d));
    291  smartlist_free(want_authorities);
    292  SMARTLIST_FOREACH(voters, char *, cp, tor_free(cp));
    293  smartlist_free(voters);
    294  return (have >= need_at_least);
    295 }
    296 
    297 /** Return the compression level we should use for sending a compressed
    298 * response of size <b>n_bytes</b>. */
    299 STATIC compression_level_t
    300 choose_compression_level(void)
    301 {
    302  /* This is the compression level choice for a stream.
    303   *
    304   * We always return LOW because this compression is done in the main thread
    305   * thus we save CPU time as much as possible, and it is also done more than
    306   * background compression for document we serve pre-compressed.
    307   *
    308   * GZip highest compression level (9) gives us a ratio of 49.72%
    309   * Zstd lowest compression level (1) gives us a ratio of 47.38%
    310   *
    311   * Thus, as the network moves more and more to use Zstd when requesting
    312   * directory documents that are not pre-cached, even at the
    313   * lowest level, we still gain over GZip and thus help with load and CPU
    314   * time on the network. */
    315  return LOW_COMPRESSION;
    316 }
    317 
    318 /** Information passed to handle a GET request. */
    319 typedef struct get_handler_args_t {
    320  /** Bitmask of compression methods that the client said (or implied) it
    321   * supported. */
    322  unsigned compression_supported;
    323  /** If nonzero, the time included an if-modified-since header with this
    324   * value. */
    325  time_t if_modified_since;
    326  /** String containing the requested URL or resource. */
    327  const char *url;
    328  /** String containing the HTTP headers */
    329  const char *headers;
    330 } get_handler_args_t;
    331 
    332 /** Entry for handling an HTTP GET request.
    333 *
    334 * This entry matches a request if "string" is equal to the requested
    335 * resource, or if "is_prefix" is true and "string" is a prefix of the
    336 * requested resource.
    337 *
    338 * The 'handler' function is called to handle the request.  It receives
    339 * an arguments structure, and must return 0 on success or -1 if we should
    340 * close the connection.
    341 **/
    342 typedef struct url_table_ent_t {
    343  const char *string;
    344  int is_prefix;
    345  int (*handler)(dir_connection_t *conn, const get_handler_args_t *args);
    346 } url_table_ent_t;
    347 
    348 static int handle_get_frontpage(dir_connection_t *conn,
    349                                const get_handler_args_t *args);
    350 static int handle_get_current_consensus(dir_connection_t *conn,
    351                                const get_handler_args_t *args);
    352 static int handle_get_status_vote(dir_connection_t *conn,
    353                                const get_handler_args_t *args);
    354 static int handle_get_microdesc(dir_connection_t *conn,
    355                                const get_handler_args_t *args);
    356 static int handle_get_descriptor(dir_connection_t *conn,
    357                                const get_handler_args_t *args);
    358 static int handle_get_keys(dir_connection_t *conn,
    359                                const get_handler_args_t *args);
    360 static int handle_get_robots(dir_connection_t *conn,
    361                                const get_handler_args_t *args);
    362 static int handle_get_networkstatus_bridges(dir_connection_t *conn,
    363                                const get_handler_args_t *args);
    364 static int handle_get_next_bandwidth(dir_connection_t *conn,
    365                                     const get_handler_args_t *args);
    366 
    367 /** Table for handling GET requests. */
    368 static const url_table_ent_t url_table[] = {
    369  { "/tor/", 0, handle_get_frontpage },
    370  { "/tor/status-vote/current/consensus", 1, handle_get_current_consensus },
    371  { "/tor/status-vote/current/", 1, handle_get_status_vote },
    372  { "/tor/status-vote/next/bandwidth", 0, handle_get_next_bandwidth },
    373  { "/tor/status-vote/next/", 1, handle_get_status_vote },
    374  { "/tor/micro/d/", 1, handle_get_microdesc },
    375  { "/tor/server/", 1, handle_get_descriptor },
    376  { "/tor/extra/", 1, handle_get_descriptor },
    377  { "/tor/keys/", 1, handle_get_keys },
    378  { "/tor/hs/3/", 1, handle_get_hs_descriptor_v3 },
    379  { "/tor/robots.txt", 0, handle_get_robots },
    380  { "/tor/networkstatus-bridges", 0, handle_get_networkstatus_bridges },
    381  { NULL, 0, NULL },
    382 };
    383 
    384 /** Helper function: called when a dirserver gets a complete HTTP GET
    385 * request.  Look for a request for a directory or for a rendezvous
    386 * service descriptor.  On finding one, write a response into
    387 * conn-\>outbuf.  If the request is unrecognized, send a 404.
    388 * Return 0 if we handled this successfully, or -1 if we need to close
    389 * the connection. */
    390 MOCK_IMPL(STATIC int,
    391 directory_handle_command_get,(dir_connection_t *conn, const char *headers,
    392                              const char *req_body, size_t req_body_len))
    393 {
    394  char *url, *url_mem, *header;
    395  time_t if_modified_since = 0;
    396  int zlib_compressed_in_url;
    397  unsigned compression_methods_supported;
    398 
    399  /* We ignore the body of a GET request. */
    400  (void)req_body;
    401  (void)req_body_len;
    402 
    403  log_debug(LD_DIRSERV,"Received GET command.");
    404 
    405  conn->base_.state = DIR_CONN_STATE_SERVER_WRITING;
    406 
    407  if (parse_http_url(headers, &url) < 0) {
    408    write_short_http_response(conn, 400, "Bad request");
    409    return 0;
    410  }
    411  if ((header = http_get_header(headers, "If-Modified-Since: "))) {
    412    struct tm tm;
    413    if (parse_http_time(header, &tm) == 0) {
    414      if (tor_timegm(&tm, &if_modified_since)<0) {
    415        if_modified_since = 0;
    416      } else {
    417        log_debug(LD_DIRSERV, "If-Modified-Since is '%s'.", escaped(header));
    418      }
    419    }
    420    /* The correct behavior on a malformed If-Modified-Since header is to
    421     * act as if no If-Modified-Since header had been given. */
    422    tor_free(header);
    423  }
    424  log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url));
    425 
    426  url_mem = url;
    427  {
    428    size_t url_len = strlen(url);
    429 
    430    zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z");
    431    if (zlib_compressed_in_url) {
    432      url[url_len-2] = '\0';
    433    }
    434  }
    435 
    436  if ((header = http_get_header(headers, "Accept-Encoding: "))) {
    437    compression_methods_supported = parse_accept_encoding_header(header);
    438    tor_free(header);
    439  } else {
    440    compression_methods_supported = (1u << NO_METHOD);
    441  }
    442  if (zlib_compressed_in_url) {
    443    compression_methods_supported |= (1u << ZLIB_METHOD);
    444  }
    445 
    446  /* Remove all methods that we don't both support. */
    447  compression_methods_supported &= tor_compress_get_supported_method_bitmask();
    448 
    449  get_handler_args_t args;
    450  args.url = url;
    451  args.headers = headers;
    452  args.if_modified_since = if_modified_since;
    453  args.compression_supported = compression_methods_supported;
    454 
    455  int i, result = -1;
    456  for (i = 0; url_table[i].string; ++i) {
    457    int match;
    458    if (url_table[i].is_prefix) {
    459      match = !strcmpstart(url, url_table[i].string);
    460    } else {
    461      match = !strcmp(url, url_table[i].string);
    462    }
    463    if (match) {
    464      result = url_table[i].handler(conn, &args);
    465      goto done;
    466    }
    467  }
    468 
    469  /* we didn't recognize the url */
    470  write_short_http_response(conn, 404, "Not found");
    471  result = 0;
    472 
    473 done:
    474  tor_free(url_mem);
    475  return result;
    476 }
    477 
    478 /** Helper function for GET / or GET /tor/
    479 */
    480 static int
    481 handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args)
    482 {
    483  (void) args; /* unused */
    484  const char *frontpage = relay_get_dirportfrontpage();
    485 
    486  if (frontpage) {
    487    size_t dlen;
    488    dlen = strlen(frontpage);
    489    /* Let's return a disclaimer page (users shouldn't use V1 anymore,
    490       and caches don't fetch '/', so this is safe). */
    491 
    492    /* [We don't check for write_bucket_low here, since we want to serve
    493     *  this page no matter what.] */
    494    write_http_response_header_impl(conn, dlen, "text/html", "identity",
    495                                    NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME);
    496    connection_buf_add(frontpage, dlen, TO_CONN(conn));
    497  } else {
    498    write_short_http_response(conn, 404, "Not found");
    499  }
    500  return 0;
    501 }
    502 
    503 /** Warn that the cached consensus <b>consensus</b> of type
    504 * <b>flavor</b> too new or too old, based on <b>is_too_new</b>,
    505 * and will not be served to clients. Rate-limit the warning to avoid logging
    506 * an entry on every request.
    507 */
    508 static void
    509 warn_consensus_is_not_reasonably_live(
    510                          const struct consensus_cache_entry_t *consensus,
    511                          const char *flavor, time_t now, bool is_too_new)
    512 {
    513 #define NOT_REASONABLY_LIVE_WARNING_INTERVAL (60*60)
    514  static ratelim_t warned[2] = { RATELIM_INIT(
    515                                      NOT_REASONABLY_LIVE_WARNING_INTERVAL),
    516                                RATELIM_INIT(
    517                                      NOT_REASONABLY_LIVE_WARNING_INTERVAL) };
    518  char timestamp[ISO_TIME_LEN+1];
    519  /* valid_after if is_too_new, valid_until if !is_too_new */
    520  time_t valid_time = 0;
    521  char *dupes = NULL;
    522 
    523  if (is_too_new) {
    524    if (consensus_cache_entry_get_valid_after(consensus, &valid_time))
    525      return;
    526    dupes = rate_limit_log(&warned[1], now);
    527  } else {
    528    if (consensus_cache_entry_get_valid_until(consensus, &valid_time))
    529      return;
    530    dupes = rate_limit_log(&warned[0], now);
    531  }
    532 
    533  if (dupes) {
    534    format_local_iso_time(timestamp, valid_time);
    535    log_warn(LD_DIRSERV, "Our %s%sconsensus is too %s, so we will not "
    536             "serve it to clients. It was valid %s %s local time and we "
    537             "continued to serve it for up to 24 hours %s.%s",
    538             flavor ? flavor : "",
    539             flavor ? " " : "",
    540             is_too_new ? "new" : "old",
    541             is_too_new ? "after" : "until",
    542             timestamp,
    543             is_too_new ? "before it was valid" : "after it expired",
    544             dupes);
    545    tor_free(dupes);
    546  }
    547 }
    548 
    549 /**
    550 * Parse a single hex-encoded sha3-256 digest from <b>hex</b> into
    551 * <b>digest</b>. Return 0 on success.  On failure, report that the hash came
    552 * from <b>location</b>, report that we are taking <b>action</b> with it, and
    553 * return -1.
    554 */
    555 static int
    556 parse_one_diff_hash(uint8_t *digest, const char *hex, const char *location,
    557                    const char *action)
    558 {
    559  if (base16_decode((char*)digest, DIGEST256_LEN, hex, strlen(hex)) ==
    560      DIGEST256_LEN) {
    561    return 0;
    562  } else {
    563    log_fn(LOG_PROTOCOL_WARN, LD_DIR,
    564           "%s contained bogus digest %s; %s.",
    565           location, escaped(hex), action);
    566    return -1;
    567  }
    568 }
    569 
    570 /** If there is an X-Or-Diff-From-Consensus header included in <b>headers</b>,
    571 * set <b>digest_out</b> to a new smartlist containing every 256-bit
    572 * hex-encoded digest listed in that header and return 0.  Otherwise return
    573 * -1.  */
    574 static int
    575 parse_or_diff_from_header(smartlist_t **digests_out, const char *headers)
    576 {
    577  char *hdr = http_get_header(headers, X_OR_DIFF_FROM_CONSENSUS_HEADER);
    578  if (hdr == NULL) {
    579    return -1;
    580  }
    581  smartlist_t *hex_digests = smartlist_new();
    582  *digests_out = smartlist_new();
    583  smartlist_split_string(hex_digests, hdr, " ",
    584                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
    585  SMARTLIST_FOREACH_BEGIN(hex_digests, const char *, hex) {
    586    uint8_t digest[DIGEST256_LEN];
    587    if (!parse_one_diff_hash(digest, hex, "X-Or-Diff-From-Consensus header",
    588                             "ignoring")) {
    589      smartlist_add(*digests_out, tor_memdup(digest, sizeof(digest)));
    590    }
    591  } SMARTLIST_FOREACH_END(hex);
    592  SMARTLIST_FOREACH(hex_digests, char *, cp, tor_free(cp));
    593  smartlist_free(hex_digests);
    594  tor_free(hdr);
    595  return 0;
    596 }
    597 
    598 /** Fallback compression method.  The fallback compression method is used in
    599 * case a client requests a non-compressed document. We only store compressed
    600 * documents, so we use this compression method to fetch the document and let
    601 * the spooling system do the streaming decompression.
    602 */
    603 #define FALLBACK_COMPRESS_METHOD ZLIB_METHOD
    604 
    605 /**
    606 * Try to find the best consensus diff possible in order to serve a client
    607 * request for a diff from one of the consensuses in <b>digests</b> to the
    608 * current consensus of flavor <b>flav</b>.  The client supports the
    609 * compression methods listed in the <b>compression_methods</b> bitfield:
    610 * place the method chosen (if any) into <b>compression_used_out</b>.
    611 */
    612 static struct consensus_cache_entry_t *
    613 find_best_diff(const smartlist_t *digests, int flav,
    614               unsigned compression_methods,
    615               compress_method_t *compression_used_out)
    616 {
    617  struct consensus_cache_entry_t *result = NULL;
    618 
    619  SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) {
    620    unsigned u;
    621    for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) {
    622      compress_method_t method = srv_meth_pref_precompressed[u];
    623      if (0 == (compression_methods & (1u<<method)))
    624        continue; // client doesn't like this one, or we don't have it.
    625      if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256,
    626                                     diff_from, DIGEST256_LEN,
    627                                     method) == CONSDIFF_AVAILABLE) {
    628        tor_assert_nonfatal(result);
    629        *compression_used_out = method;
    630        return result;
    631      }
    632    }
    633  } SMARTLIST_FOREACH_END(diff_from);
    634 
    635  SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) {
    636    if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256, diff_from,
    637          DIGEST256_LEN, FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) {
    638      tor_assert_nonfatal(result);
    639      *compression_used_out = FALLBACK_COMPRESS_METHOD;
    640      return result;
    641    }
    642  } SMARTLIST_FOREACH_END(diff_from);
    643 
    644  return NULL;
    645 }
    646 
    647 /** Lookup the cached consensus document by the flavor found in <b>flav</b>.
    648 * The preferred set of compression methods should be listed in the
    649 * <b>compression_methods</b> bitfield. The compression method chosen (if any)
    650 * is stored in <b>compression_used_out</b>. */
    651 static struct consensus_cache_entry_t *
    652 find_best_consensus(int flav,
    653                    unsigned compression_methods,
    654                    compress_method_t *compression_used_out)
    655 {
    656  struct consensus_cache_entry_t *result = NULL;
    657  unsigned u;
    658 
    659  for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) {
    660    compress_method_t method = srv_meth_pref_precompressed[u];
    661 
    662    if (0 == (compression_methods & (1u<<method)))
    663      continue;
    664 
    665    if (consdiffmgr_find_consensus(&result, flav,
    666                                   method) == CONSDIFF_AVAILABLE) {
    667      tor_assert_nonfatal(result);
    668      *compression_used_out = method;
    669      return result;
    670    }
    671  }
    672 
    673  if (consdiffmgr_find_consensus(&result, flav,
    674        FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) {
    675    tor_assert_nonfatal(result);
    676    *compression_used_out = FALLBACK_COMPRESS_METHOD;
    677    return result;
    678  }
    679 
    680  return NULL;
    681 }
    682 
    683 /** Try to find the best supported compression method possible from a given
    684 * <b>compression_methods</b>. Return NO_METHOD if no mutually supported
    685 * compression method could be found. */
    686 static compress_method_t
    687 find_best_compression_method(unsigned compression_methods, int stream)
    688 {
    689  unsigned u;
    690  compress_method_t *methods;
    691  size_t length;
    692 
    693  if (stream) {
    694    methods = srv_meth_pref_streaming_compression;
    695    length = ARRAY_LENGTH(srv_meth_pref_streaming_compression);
    696  } else {
    697    methods = srv_meth_pref_precompressed;
    698    length = ARRAY_LENGTH(srv_meth_pref_precompressed);
    699  }
    700 
    701  for (u = 0; u < length; ++u) {
    702    compress_method_t method = methods[u];
    703    if (compression_methods & (1u<<method))
    704      return method;
    705  }
    706 
    707  return NO_METHOD;
    708 }
    709 
    710 /** Check if any of the digests in <b>digests</b> matches the latest consensus
    711 *  flavor (given in <b>flavor</b>) that we have available. */
    712 static int
    713 digest_list_contains_best_consensus(consensus_flavor_t flavor,
    714                                    const smartlist_t *digests)
    715 {
    716  const networkstatus_t *ns = NULL;
    717 
    718  if (digests == NULL)
    719    return 0;
    720 
    721  ns = networkstatus_get_latest_consensus_by_flavor(flavor);
    722 
    723  if (ns == NULL)
    724    return 0;
    725 
    726  SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, digest) {
    727    if (tor_memeq(ns->digest_sha3_as_signed, digest, DIGEST256_LEN))
    728      return 1;
    729  } SMARTLIST_FOREACH_END(digest);
    730 
    731  return 0;
    732 }
    733 
    734 /** Encodes the results of parsing a consensus request to figure out what
    735 * consensus, and possibly what diffs, the user asked for. */
    736 typedef struct {
    737  /** name of the flavor to retrieve. */
    738  char *flavor;
    739  /** flavor to retrieve, as enum. */
    740  consensus_flavor_t flav;
    741  /** plus-separated list of authority fingerprints; see
    742   * client_likes_consensus(). Aliases the URL in the request passed to
    743   * parse_consensus_request(). */
    744  const char *want_fps;
    745  /** Optionally, a smartlist of sha3 digests-as-signed of the consensuses
    746   * to return a diff from. */
    747  smartlist_t *diff_from_digests;
    748  /** If true, never send a full consensus. If there is no diff, send
    749   * a 404 instead. */
    750  int diff_only;
    751 } parsed_consensus_request_t;
    752 
    753 /** Remove all data held in <b>req</b>. Do not free <b>req</b> itself, since
    754 * it is stack-allocated. */
    755 static void
    756 parsed_consensus_request_clear(parsed_consensus_request_t *req)
    757 {
    758  if (!req)
    759    return;
    760  tor_free(req->flavor);
    761  if (req->diff_from_digests) {
    762    SMARTLIST_FOREACH(req->diff_from_digests, uint8_t *, d, tor_free(d));
    763    smartlist_free(req->diff_from_digests);
    764  }
    765  memset(req, 0, sizeof(parsed_consensus_request_t));
    766 }
    767 
    768 /**
    769 * Parse the URL and relevant headers of <b>args</b> for a current-consensus
    770 * request to learn what flavor of consensus we want, what keys it must be
    771 * signed with, and what diffs we would accept (or demand) instead. Return 0
    772 * on success and -1 on failure.
    773 */
    774 static int
    775 parse_consensus_request(parsed_consensus_request_t *out,
    776                        const get_handler_args_t *args)
    777 {
    778  const char *url = args->url;
    779  memset(out, 0, sizeof(parsed_consensus_request_t));
    780  out->flav = FLAV_NS;
    781 
    782  const char CONSENSUS_URL_PREFIX[] = "/tor/status-vote/current/consensus/";
    783  const char CONSENSUS_FLAVORED_PREFIX[] =
    784    "/tor/status-vote/current/consensus-";
    785 
    786  /* figure out the flavor if any, and who we wanted to sign the thing */
    787  const char *after_flavor = NULL;
    788 
    789  if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
    790    const char *f, *cp;
    791    f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
    792    cp = strchr(f, '/');
    793    if (cp) {
    794      after_flavor = cp+1;
    795      out->flavor = tor_strndup(f, cp-f);
    796    } else {
    797      out->flavor = tor_strdup(f);
    798    }
    799    int flav = networkstatus_parse_flavor_name(out->flavor);
    800    if (flav < 0)
    801      flav = FLAV_NS;
    802    out->flav = flav;
    803  } else {
    804    if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
    805      after_flavor = url+strlen(CONSENSUS_URL_PREFIX);
    806  }
    807 
    808  /* see whether we've been asked explicitly for a diff from an older
    809   * consensus. (The user might also have said that a diff would be okay,
    810   * via X-Or-Diff-From-Consensus */
    811  const char DIFF_COMPONENT[] = "diff/";
    812  char *diff_hash_in_url = NULL;
    813  if (after_flavor && !strcmpstart(after_flavor, DIFF_COMPONENT)) {
    814    after_flavor += strlen(DIFF_COMPONENT);
    815    const char *cp = strchr(after_flavor, '/');
    816    if (cp) {
    817      diff_hash_in_url = tor_strndup(after_flavor, cp-after_flavor);
    818      out->want_fps = cp+1;
    819    } else {
    820      diff_hash_in_url = tor_strdup(after_flavor);
    821      out->want_fps = NULL;
    822    }
    823  } else {
    824    out->want_fps = after_flavor;
    825  }
    826 
    827  if (diff_hash_in_url) {
    828    uint8_t diff_from[DIGEST256_LEN];
    829    out->diff_from_digests = smartlist_new();
    830    out->diff_only = 1;
    831    int ok = !parse_one_diff_hash(diff_from, diff_hash_in_url, "URL",
    832                                  "rejecting");
    833    tor_free(diff_hash_in_url);
    834    if (ok) {
    835      smartlist_add(out->diff_from_digests,
    836                    tor_memdup(diff_from, DIGEST256_LEN));
    837    } else {
    838      return -1;
    839    }
    840  } else {
    841    parse_or_diff_from_header(&out->diff_from_digests, args->headers);
    842  }
    843 
    844  return 0;
    845 }
    846 
    847 /** Helper function for GET /tor/status-vote/current/consensus
    848 */
    849 static int
    850 handle_get_current_consensus(dir_connection_t *conn,
    851                             const get_handler_args_t *args)
    852 {
    853  const compress_method_t compress_method =
    854    find_best_compression_method(args->compression_supported, 0);
    855  const time_t if_modified_since = args->if_modified_since;
    856  int clear_spool = 0;
    857 
    858  /* v3 network status fetch. */
    859  long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
    860 
    861  time_t now = time(NULL);
    862  parsed_consensus_request_t req;
    863 
    864  if (parse_consensus_request(&req, args) < 0) {
    865    write_short_http_response(conn, 404, "Couldn't parse request");
    866    goto done;
    867  }
    868 
    869  if (digest_list_contains_best_consensus(req.flav,
    870                                          req.diff_from_digests)) {
    871    write_short_http_response(conn, 304, "Not modified");
    872    geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED);
    873    goto done;
    874  }
    875 
    876  struct consensus_cache_entry_t *cached_consensus = NULL;
    877 
    878  compress_method_t compression_used = NO_METHOD;
    879  if (req.diff_from_digests) {
    880    cached_consensus = find_best_diff(req.diff_from_digests, req.flav,
    881                                      args->compression_supported,
    882                                      &compression_used);
    883  }
    884 
    885  if (req.diff_only && !cached_consensus) {
    886    write_short_http_response(conn, 404, "No such diff available");
    887    geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
    888    goto done;
    889  }
    890 
    891  if (! cached_consensus) {
    892    cached_consensus = find_best_consensus(req.flav,
    893                                           args->compression_supported,
    894                                           &compression_used);
    895  }
    896 
    897  time_t valid_after, fresh_until, valid_until;
    898  int have_valid_after = 0, have_fresh_until = 0, have_valid_until = 0;
    899  if (cached_consensus) {
    900    have_valid_after =
    901      !consensus_cache_entry_get_valid_after(cached_consensus, &valid_after);
    902    have_fresh_until =
    903      !consensus_cache_entry_get_fresh_until(cached_consensus, &fresh_until);
    904    have_valid_until =
    905      !consensus_cache_entry_get_valid_until(cached_consensus, &valid_until);
    906  }
    907 
    908  if (cached_consensus && have_valid_after &&
    909      !networkstatus_valid_after_is_reasonably_live(valid_after, now)) {
    910    write_short_http_response(conn, 404, "Consensus is too new");
    911    warn_consensus_is_not_reasonably_live(cached_consensus, req.flavor, now,
    912                                          1);
    913    geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
    914    goto done;
    915  } else if (
    916      cached_consensus && have_valid_until &&
    917      !networkstatus_valid_until_is_reasonably_live(valid_until, now)) {
    918    write_short_http_response(conn, 404, "Consensus is too old");
    919    warn_consensus_is_not_reasonably_live(cached_consensus, req.flavor, now,
    920                                          0);
    921    geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
    922    goto done;
    923  }
    924 
    925  if (cached_consensus && req.want_fps &&
    926      !client_likes_consensus(cached_consensus, req.want_fps)) {
    927    write_short_http_response(conn, 404, "Consensus not signed by sufficient "
    928                           "number of requested authorities");
    929    geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS);
    930    goto done;
    931  }
    932 
    933  conn->spool = smartlist_new();
    934  clear_spool = 1;
    935  {
    936    spooled_resource_t *spooled;
    937    if (cached_consensus) {
    938      spooled = spooled_resource_new_from_cache_entry(cached_consensus);
    939      smartlist_add(conn->spool, spooled);
    940    }
    941  }
    942 
    943  lifetime = (have_fresh_until && fresh_until > now) ? fresh_until - now : 0;
    944 
    945  size_t size_guess = 0;
    946  int n_expired = 0;
    947  dirserv_spool_remove_missing_and_guess_size(conn, if_modified_since,
    948                                              compress_method != NO_METHOD,
    949                                              &size_guess,
    950                                              &n_expired);
    951 
    952  if (!smartlist_len(conn->spool) && !n_expired) {
    953    write_short_http_response(conn, 404, "Not found");
    954    geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
    955    goto done;
    956  } else if (!smartlist_len(conn->spool)) {
    957    write_short_http_response(conn, 304, "Not modified");
    958    geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED);
    959    goto done;
    960  }
    961 
    962  if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) {
    963    log_debug(LD_DIRSERV,
    964              "Client asked for network status lists, but we've been "
    965              "writing too many bytes lately. Sending 503 Dir busy.");
    966    write_short_http_response(conn, 503, "Directory busy, try again later");
    967    geoip_note_ns_response(GEOIP_REJECT_BUSY);
    968    goto done;
    969  }
    970 
    971  /* Success: we are going to try serving it. */
    972  geoip_note_ns_response(GEOIP_SERVED);
    973  conn->should_count_geoip_when_finished = 1;
    974 
    975  tor_addr_t addr;
    976  if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
    977    /* Note that a request for a network status has started, so that we
    978     * can measure the download time later on. */
    979    if (conn->dirreq_id)
    980      geoip_start_dirreq(conn->dirreq_id, size_guess, DIRREQ_TUNNELED);
    981    else
    982      geoip_start_dirreq(TO_CONN(conn)->global_identifier, size_guess,
    983                         DIRREQ_DIRECT);
    984  }
    985 
    986  /* Use this header to tell caches that the response depends on the
    987   * X-Or-Diff-From-Consensus header (or lack thereof). */
    988  const char vary_header[] = "Vary: X-Or-Diff-From-Consensus\r\n";
    989 
    990  clear_spool = 0;
    991 
    992  // The compress_method might have been NO_METHOD, but we store the data
    993  // compressed. Decompress them using `compression_used`. See fallback code in
    994  // find_best_consensus() and find_best_diff().
    995  write_http_response_headers(conn, -1,
    996                             compress_method == NO_METHOD ?
    997                               NO_METHOD : compression_used,
    998                             vary_header,
    999                             smartlist_len(conn->spool) == 1 ? lifetime : 0);
   1000 
   1001  if (compress_method == NO_METHOD && smartlist_len(conn->spool))
   1002    conn->compress_state = tor_compress_new(0, compression_used,
   1003                                            HIGH_COMPRESSION);
   1004 
   1005  /* Prime the connection with some data. */
   1006  const int initial_flush_result = connection_dirserv_flushed_some(conn);
   1007  tor_assert_nonfatal(initial_flush_result == 0);
   1008  goto done;
   1009 
   1010 done:
   1011  parsed_consensus_request_clear(&req);
   1012  if (clear_spool) {
   1013    dir_conn_clear_spool(conn);
   1014  }
   1015  return 0;
   1016 }
   1017 
   1018 /** Helper function for GET /tor/status-vote/{current,next}/...
   1019 */
   1020 static int
   1021 handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args)
   1022 {
   1023  const char *url = args->url;
   1024  {
   1025    ssize_t body_len = 0;
   1026    ssize_t estimated_len = 0;
   1027    int lifetime = 60; /* XXXX?? should actually use vote intervals. */
   1028    /* This smartlist holds strings that we can compress on the fly. */
   1029    smartlist_t *items = smartlist_new();
   1030    /* This smartlist holds cached_dir_t objects that have a precompressed
   1031     * deflated version. */
   1032    smartlist_t *dir_items = smartlist_new();
   1033    dirvote_dirreq_get_status_vote(url, items, dir_items);
   1034    if (!smartlist_len(dir_items) && !smartlist_len(items)) {
   1035      write_short_http_response(conn, 404, "Not found");
   1036      goto vote_done;
   1037    }
   1038 
   1039    /* We're sending items from at most one kind of source */
   1040    tor_assert_nonfatal(smartlist_len(items) == 0 ||
   1041                        smartlist_len(dir_items) == 0);
   1042 
   1043    int streaming;
   1044    unsigned mask;
   1045    if (smartlist_len(items)) {
   1046      /* We're taking strings and compressing them on the fly. */
   1047      streaming = 1;
   1048      mask = ~0u;
   1049    } else {
   1050      /* We're taking cached_dir_t objects. We only have them uncompressed
   1051       * or deflated. */
   1052      streaming = 0;
   1053      mask = (1u<<NO_METHOD) | (1u<<ZLIB_METHOD);
   1054    }
   1055    const compress_method_t compress_method = find_best_compression_method(
   1056                              args->compression_supported&mask, streaming);
   1057 
   1058    SMARTLIST_FOREACH(dir_items, cached_dir_t *, d,
   1059                      body_len += compress_method != NO_METHOD ?
   1060                        d->dir_compressed_len : d->dir_len);
   1061    estimated_len += body_len;
   1062    SMARTLIST_FOREACH(items, const char *, item, {
   1063        size_t ln = strlen(item);
   1064        if (compress_method != NO_METHOD) {
   1065          estimated_len += ln/2;
   1066        } else {
   1067          body_len += ln; estimated_len += ln;
   1068        }
   1069      });
   1070 
   1071    if (connection_dir_is_global_write_low(TO_CONN(conn), estimated_len)) {
   1072      write_short_http_response(conn, 503, "Directory busy, try again later");
   1073      goto vote_done;
   1074    }
   1075    write_http_response_header(conn, body_len ? body_len : -1,
   1076                 compress_method,
   1077                 lifetime);
   1078 
   1079    if (smartlist_len(items)) {
   1080      if (compress_method != NO_METHOD) {
   1081        conn->compress_state = tor_compress_new(1, compress_method,
   1082                           choose_compression_level());
   1083      }
   1084 
   1085      SMARTLIST_FOREACH(items, const char *, c,
   1086                        connection_dir_buf_add(c, strlen(c), conn,
   1087                                               c_sl_idx == c_sl_len - 1));
   1088    } else {
   1089      SMARTLIST_FOREACH(dir_items, cached_dir_t *, d,
   1090          connection_buf_add(compress_method != NO_METHOD ?
   1091                                    d->dir_compressed : d->dir,
   1092                                  compress_method != NO_METHOD ?
   1093                                    d->dir_compressed_len : d->dir_len,
   1094                                  TO_CONN(conn)));
   1095    }
   1096  vote_done:
   1097    smartlist_free(items);
   1098    smartlist_free(dir_items);
   1099    goto done;
   1100  }
   1101 done:
   1102  return 0;
   1103 }
   1104 
   1105 /** Helper function for GET /tor/micro/d/...
   1106 */
   1107 static int
   1108 handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args)
   1109 {
   1110  const char *url = args->url;
   1111  const compress_method_t compress_method =
   1112    find_best_compression_method(args->compression_supported, 1);
   1113  int clear_spool = 1;
   1114  {
   1115    conn->spool = smartlist_new();
   1116 
   1117    dir_split_resource_into_spoolable(url+strlen("/tor/micro/d/"),
   1118                                      DIR_SPOOL_MICRODESC,
   1119                                      conn->spool, NULL,
   1120                                      DSR_DIGEST256|DSR_BASE64|DSR_SORT_UNIQ);
   1121 
   1122    size_t size_guess = 0;
   1123    dirserv_spool_remove_missing_and_guess_size(conn, 0,
   1124                                                compress_method != NO_METHOD,
   1125                                                &size_guess, NULL);
   1126    if (smartlist_len(conn->spool) == 0) {
   1127      write_short_http_response(conn, 404, "Not found");
   1128      goto done;
   1129    }
   1130    if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) {
   1131      log_info(LD_DIRSERV,
   1132               "Client asked for server descriptors, but we've been "
   1133               "writing too many bytes lately. Sending 503 Dir busy.");
   1134      write_short_http_response(conn, 503, "Directory busy, try again later");
   1135      goto done;
   1136    }
   1137 
   1138    clear_spool = 0;
   1139    write_http_response_header(conn, -1,
   1140                               compress_method,
   1141                               MICRODESC_CACHE_LIFETIME);
   1142 
   1143    if (compress_method != NO_METHOD)
   1144      conn->compress_state = tor_compress_new(1, compress_method,
   1145                                      choose_compression_level());
   1146 
   1147    const int initial_flush_result = connection_dirserv_flushed_some(conn);
   1148    tor_assert_nonfatal(initial_flush_result == 0);
   1149    goto done;
   1150  }
   1151 
   1152 done:
   1153  if (clear_spool) {
   1154    dir_conn_clear_spool(conn);
   1155  }
   1156  return 0;
   1157 }
   1158 
   1159 /** Helper function for GET /tor/{server,extra}/...
   1160 */
   1161 static int
   1162 handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args)
   1163 {
   1164  const char *url = args->url;
   1165  const compress_method_t compress_method =
   1166    find_best_compression_method(args->compression_supported, 1);
   1167  const or_options_t *options = get_options();
   1168  int clear_spool = 1;
   1169  if (!strcmpstart(url,"/tor/server/") ||
   1170      (!options->BridgeAuthoritativeDir &&
   1171       !options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
   1172    int res;
   1173    const char *msg = NULL;
   1174    int cache_lifetime = 0;
   1175    int is_extra = !strcmpstart(url,"/tor/extra/");
   1176    url += is_extra ? strlen("/tor/extra/") : strlen("/tor/server/");
   1177    dir_spool_source_t source;
   1178    time_t publish_cutoff = 0;
   1179    if (!strcmpstart(url, "d/")) {
   1180      source =
   1181        is_extra ? DIR_SPOOL_EXTRA_BY_DIGEST : DIR_SPOOL_SERVER_BY_DIGEST;
   1182    } else {
   1183      source =
   1184        is_extra ? DIR_SPOOL_EXTRA_BY_FP : DIR_SPOOL_SERVER_BY_FP;
   1185      /* We only want to apply a publish cutoff when we're requesting
   1186       * resources by fingerprint. */
   1187      publish_cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH;
   1188    }
   1189 
   1190    conn->spool = smartlist_new();
   1191    res = dirserv_get_routerdesc_spool(conn->spool, url,
   1192                                       source,
   1193                                       connection_dir_is_encrypted(conn),
   1194                                       &msg);
   1195 
   1196    if (!strcmpstart(url, "all")) {
   1197      cache_lifetime = FULL_DIR_CACHE_LIFETIME;
   1198    } else if (smartlist_len(conn->spool) == 1) {
   1199      cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME;
   1200    }
   1201 
   1202    size_t size_guess = 0;
   1203    int n_expired = 0;
   1204    dirserv_spool_remove_missing_and_guess_size(conn, publish_cutoff,
   1205                                                compress_method != NO_METHOD,
   1206                                                &size_guess, &n_expired);
   1207 
   1208    /* If we are the bridge authority and the descriptor is a bridge
   1209     * descriptor, remember that we served this descriptor for desc stats. */
   1210    /* XXXX it's a bit of a kludge to have this here. */
   1211    if (get_options()->BridgeAuthoritativeDir &&
   1212        source == DIR_SPOOL_SERVER_BY_FP) {
   1213      SMARTLIST_FOREACH_BEGIN(conn->spool, spooled_resource_t *, spooled) {
   1214        const routerinfo_t *router =
   1215          router_get_by_id_digest((const char *)spooled->digest);
   1216        /* router can be NULL here when the bridge auth is asked for its own
   1217         * descriptor. */
   1218        if (router && router->purpose == ROUTER_PURPOSE_BRIDGE)
   1219          rep_hist_note_desc_served(router->cache_info.identity_digest);
   1220      } SMARTLIST_FOREACH_END(spooled);
   1221    }
   1222 
   1223    if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) {
   1224      if (msg == NULL)
   1225        msg = "Not found";
   1226      write_short_http_response(conn, 404, msg);
   1227    } else {
   1228      if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) {
   1229        log_info(LD_DIRSERV,
   1230                 "Client asked for server descriptors, but we've been "
   1231                 "writing too many bytes lately. Sending 503 Dir busy.");
   1232        write_short_http_response(conn, 503,
   1233                                  "Directory busy, try again later");
   1234        dir_conn_clear_spool(conn);
   1235        goto done;
   1236      }
   1237      write_http_response_header(conn, -1, compress_method, cache_lifetime);
   1238      if (compress_method != NO_METHOD)
   1239        conn->compress_state = tor_compress_new(1, compress_method,
   1240                                        choose_compression_level());
   1241      clear_spool = 0;
   1242      /* Prime the connection with some data. */
   1243      int initial_flush_result = connection_dirserv_flushed_some(conn);
   1244      tor_assert_nonfatal(initial_flush_result == 0);
   1245    }
   1246    goto done;
   1247  }
   1248 done:
   1249  if (clear_spool)
   1250    dir_conn_clear_spool(conn);
   1251  return 0;
   1252 }
   1253 
   1254 /** Helper function for GET /tor/keys/...
   1255 */
   1256 static int
   1257 handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args)
   1258 {
   1259  const char *url = args->url;
   1260  const compress_method_t compress_method =
   1261    find_best_compression_method(args->compression_supported, 1);
   1262  const time_t if_modified_since = args->if_modified_since;
   1263  {
   1264    smartlist_t *certs = smartlist_new();
   1265    ssize_t len = -1;
   1266    if (!strcmp(url, "/tor/keys/all")) {
   1267      authority_cert_get_all(certs);
   1268    } else if (!strcmp(url, "/tor/keys/authority")) {
   1269      authority_cert_t *cert = get_my_v3_authority_cert();
   1270      if (cert)
   1271        smartlist_add(certs, cert);
   1272    } else if (!strcmpstart(url, "/tor/keys/fp/")) {
   1273      smartlist_t *fps = smartlist_new();
   1274      dir_split_resource_into_fingerprints(url+strlen("/tor/keys/fp/"),
   1275                                           fps, NULL,
   1276                                           DSR_HEX|DSR_SORT_UNIQ);
   1277      SMARTLIST_FOREACH(fps, char *, d, {
   1278          authority_cert_t *c = authority_cert_get_newest_by_id(d);
   1279          if (c) smartlist_add(certs, c);
   1280          tor_free(d);
   1281      });
   1282      smartlist_free(fps);
   1283    } else if (!strcmpstart(url, "/tor/keys/sk/")) {
   1284      smartlist_t *fps = smartlist_new();
   1285      dir_split_resource_into_fingerprints(url+strlen("/tor/keys/sk/"),
   1286                                           fps, NULL,
   1287                                           DSR_HEX|DSR_SORT_UNIQ);
   1288      SMARTLIST_FOREACH(fps, char *, d, {
   1289          authority_cert_t *c = authority_cert_get_by_sk_digest(d);
   1290          if (c) smartlist_add(certs, c);
   1291          tor_free(d);
   1292      });
   1293      smartlist_free(fps);
   1294    } else if (!strcmpstart(url, "/tor/keys/fp-sk/")) {
   1295      smartlist_t *fp_sks = smartlist_new();
   1296      dir_split_resource_into_fingerprint_pairs(url+strlen("/tor/keys/fp-sk/"),
   1297                                                fp_sks);
   1298      SMARTLIST_FOREACH(fp_sks, fp_pair_t *, pair, {
   1299          authority_cert_t *c = authority_cert_get_by_digests(pair->first,
   1300                                                              pair->second);
   1301          if (c) smartlist_add(certs, c);
   1302          tor_free(pair);
   1303      });
   1304      smartlist_free(fp_sks);
   1305    } else {
   1306      write_short_http_response(conn, 400, "Bad request");
   1307      goto keys_done;
   1308    }
   1309    if (!smartlist_len(certs)) {
   1310      write_short_http_response(conn, 404, "Not found");
   1311      goto keys_done;
   1312    }
   1313    SMARTLIST_FOREACH(certs, authority_cert_t *, c,
   1314      if (c->cache_info.published_on < if_modified_since)
   1315        SMARTLIST_DEL_CURRENT(certs, c));
   1316    if (!smartlist_len(certs)) {
   1317      write_short_http_response(conn, 304, "Not modified");
   1318      goto keys_done;
   1319    }
   1320    len = 0;
   1321    SMARTLIST_FOREACH(certs, authority_cert_t *, c,
   1322                      len += c->cache_info.signed_descriptor_len);
   1323 
   1324    if (connection_dir_is_global_write_low(TO_CONN(conn),
   1325                                compress_method != NO_METHOD ? len/2 : len)) {
   1326      write_short_http_response(conn, 503, "Directory busy, try again later");
   1327      goto keys_done;
   1328    }
   1329 
   1330    write_http_response_header(conn,
   1331                               compress_method != NO_METHOD ? -1 : len,
   1332                               compress_method,
   1333                               60*60);
   1334    if (compress_method != NO_METHOD) {
   1335      conn->compress_state = tor_compress_new(1, compress_method,
   1336                                              choose_compression_level());
   1337    }
   1338 
   1339    SMARTLIST_FOREACH(certs, authority_cert_t *, c,
   1340          connection_dir_buf_add(c->cache_info.signed_descriptor_body,
   1341                                 c->cache_info.signed_descriptor_len,
   1342                                 conn, c_sl_idx == c_sl_len - 1));
   1343 keys_done:
   1344    smartlist_free(certs);
   1345    goto done;
   1346  }
   1347 done:
   1348  return 0;
   1349 }
   1350 
   1351 /** Helper function for GET `/tor/hs/3/...`. Only for version 3.
   1352 */
   1353 STATIC int
   1354 handle_get_hs_descriptor_v3(dir_connection_t *conn,
   1355                            const get_handler_args_t *args)
   1356 {
   1357  int retval;
   1358  const char *desc_str = NULL;
   1359  const char *pubkey_str = NULL;
   1360  const char *url = args->url;
   1361 
   1362  /* Reject non anonymous dir connections (which also tests if encrypted). We
   1363   * do not allow single hop clients to query an HSDir. */
   1364  if (!connection_dir_is_anonymous(conn)) {
   1365    write_short_http_response(conn, 503,
   1366                              "Rejecting single hop HS v3 descriptor request");
   1367    goto done;
   1368  }
   1369 
   1370  /* After the path prefix follows the base64 encoded blinded pubkey which we
   1371   * use to get the descriptor from the cache. Skip the prefix and get the
   1372   * pubkey. */
   1373  tor_assert(!strcmpstart(url, "/tor/hs/3/"));
   1374  pubkey_str = url + strlen("/tor/hs/3/");
   1375  retval = hs_cache_lookup_as_dir(HS_VERSION_THREE,
   1376                                  pubkey_str, &desc_str);
   1377  if (retval <= 0 || desc_str == NULL) {
   1378    write_short_http_response(conn, 404, "Not found");
   1379    goto done;
   1380  }
   1381 
   1382  /* Found requested descriptor! Pass it to this nice client. */
   1383  write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0);
   1384  connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn));
   1385 
   1386  /* We have successfully written the descriptor on the connection outbuf so
   1387   * save this query identifier into the dir_connection_t in order
   1388   * to associate it to the descriptor when closing. */
   1389  {
   1390    /* Decode blinded key. This is certain to work else
   1391     * hs_cache_lookup_as_dir() would have failed. */
   1392    ed25519_public_key_t blinded_key;
   1393    ed25519_public_from_base64(&blinded_key, pubkey_str);
   1394    conn->hs_ident = hs_ident_server_dir_conn_new(&blinded_key);
   1395  }
   1396 
   1397 done:
   1398  return 0;
   1399 }
   1400 
   1401 /** Helper function for GET /tor/networkstatus-bridges
   1402 */
   1403 static int
   1404 handle_get_networkstatus_bridges(dir_connection_t *conn,
   1405                                 const get_handler_args_t *args)
   1406 {
   1407  const char *headers = args->headers;
   1408 
   1409  const or_options_t *options = get_options();
   1410  if (options->BridgeAuthoritativeDir &&
   1411      options->BridgePassword_AuthDigest_ &&
   1412      connection_dir_is_encrypted(conn)) {
   1413    char *status;
   1414    char digest[DIGEST256_LEN];
   1415 
   1416    char *header = http_get_header(headers, "Authorization: Basic ");
   1417    if (header)
   1418      crypto_digest256(digest, header, strlen(header), DIGEST_SHA256);
   1419 
   1420    /* now make sure the password is there and right */
   1421    if (!header ||
   1422        tor_memneq(digest,
   1423                   options->BridgePassword_AuthDigest_, DIGEST256_LEN)) {
   1424      write_short_http_response(conn, 404, "Not found");
   1425      tor_free(header);
   1426      goto done;
   1427    }
   1428    tor_free(header);
   1429 
   1430    /* all happy now. send an answer. */
   1431    status = networkstatus_getinfo_by_purpose("bridge", time(NULL));
   1432    size_t dlen = strlen(status);
   1433    write_http_response_header(conn, dlen, NO_METHOD, 0);
   1434    connection_buf_add(status, dlen, TO_CONN(conn));
   1435    tor_free(status);
   1436    goto done;
   1437  }
   1438 done:
   1439  return 0;
   1440 }
   1441 
   1442 /** Helper function for GET the bandwidth file used for the next vote */
   1443 static int
   1444 handle_get_next_bandwidth(dir_connection_t *conn,
   1445                          const get_handler_args_t *args)
   1446 {
   1447  log_debug(LD_DIR, "Getting next bandwidth.");
   1448  const or_options_t *options = get_options();
   1449  const compress_method_t compress_method =
   1450    find_best_compression_method(args->compression_supported, 1);
   1451 
   1452  if (options->V3BandwidthsFile) {
   1453    char *bandwidth = read_file_to_str(options->V3BandwidthsFile,
   1454                                       RFTS_IGNORE_MISSING, NULL);
   1455    if (bandwidth != NULL) {
   1456      ssize_t len = strlen(bandwidth);
   1457      write_http_response_header(conn, compress_method != NO_METHOD ? -1 : len,
   1458                                 compress_method, BANDWIDTH_CACHE_LIFETIME);
   1459      if (compress_method != NO_METHOD) {
   1460        conn->compress_state = tor_compress_new(1, compress_method,
   1461                                        choose_compression_level());
   1462        log_debug(LD_DIR, "Compressing bandwidth file.");
   1463      } else {
   1464        log_debug(LD_DIR, "Not compressing bandwidth file.");
   1465      }
   1466      connection_dir_buf_add((const char*)bandwidth, len, conn, 1);
   1467      tor_free(bandwidth);
   1468      return 0;
   1469    }
   1470  }
   1471  write_short_http_response(conn, 404, "Not found");
   1472  return 0;
   1473 }
   1474 
   1475 /** Helper function for GET robots.txt or /tor/robots.txt */
   1476 static int
   1477 handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args)
   1478 {
   1479  (void)args;
   1480  {
   1481    const char robots[] = "User-agent: *\r\nDisallow: /\r\n";
   1482    size_t len = strlen(robots);
   1483    write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME);
   1484    connection_buf_add(robots, len, TO_CONN(conn));
   1485  }
   1486  return 0;
   1487 }
   1488 
   1489 /* Given the <b>url</b> from a POST request, try to extract the version number
   1490 * using the provided <b>prefix</b>. The version should be after the prefix and
   1491 * ending with the separator "/". For instance:
   1492 *      /tor/hs/3/publish
   1493 *
   1494 * On success, <b>end_pos</b> points to the position right after the version
   1495 * was found. On error, it is set to NULL.
   1496 *
   1497 * Return version on success else negative value. */
   1498 STATIC int
   1499 parse_hs_version_from_post(const char *url, const char *prefix,
   1500                           const char **end_pos)
   1501 {
   1502  int ok;
   1503  unsigned long version;
   1504  const char *start;
   1505  char *end = NULL;
   1506 
   1507  tor_assert(url);
   1508  tor_assert(prefix);
   1509  tor_assert(end_pos);
   1510 
   1511  /* Check if the prefix does start the url. */
   1512  if (strcmpstart(url, prefix)) {
   1513    goto err;
   1514  }
   1515  /* Move pointer to the end of the prefix string. */
   1516  start = url + strlen(prefix);
   1517  /* Try this to be the HS version and if we are still at the separator, next
   1518   * will be move to the right value. */
   1519  version = tor_parse_long(start, 10, 0, INT_MAX, &ok, &end);
   1520  if (!ok) {
   1521    goto err;
   1522  }
   1523 
   1524  *end_pos = end;
   1525  return (int) version;
   1526 err:
   1527  *end_pos = NULL;
   1528  return -1;
   1529 }
   1530 
   1531 /* Handle the POST request for a hidden service descripror. The request is in
   1532 * <b>url</b>, the body of the request is in <b>body</b>. Return 200 on success
   1533 * else return 400 indicating a bad request. */
   1534 STATIC int
   1535 handle_post_hs_descriptor(const char *url, const char *body)
   1536 {
   1537  int version;
   1538  const char *end_pos;
   1539 
   1540  tor_assert(url);
   1541  tor_assert(body);
   1542 
   1543  version = parse_hs_version_from_post(url, "/tor/hs/", &end_pos);
   1544  if (version < 0) {
   1545    goto err;
   1546  }
   1547 
   1548  /* We have a valid version number, now make sure it's a publish request. Use
   1549   * the end position just after the version and check for the command. */
   1550  if (strcmpstart(end_pos, "/publish")) {
   1551    goto err;
   1552  }
   1553 
   1554  switch (version) {
   1555  case HS_VERSION_THREE:
   1556    if (hs_cache_store_as_dir(body) < 0) {
   1557      goto err;
   1558    }
   1559    log_info(LD_REND, "Publish request for HS descriptor handled "
   1560                      "successfully.");
   1561    break;
   1562  default:
   1563    /* Unsupported version, return a bad request. */
   1564    goto err;
   1565  }
   1566 
   1567  return 200;
   1568 err:
   1569  /* Bad request. */
   1570  return 400;
   1571 }
   1572 
   1573 /** Helper function: called when a dirserver gets a complete HTTP POST
   1574 * request.  Look for an uploaded server descriptor or rendezvous
   1575 * service descriptor.  On finding one, process it and write a
   1576 * response into conn-\>outbuf.  If the request is unrecognized, send a
   1577 * 400.  Always return 0. */
   1578 MOCK_IMPL(STATIC int,
   1579 directory_handle_command_post,(dir_connection_t *conn, const char *headers,
   1580                               const char *body, size_t body_len))
   1581 {
   1582  char *url = NULL;
   1583  const or_options_t *options = get_options();
   1584 
   1585  (void) body_len;
   1586 
   1587  log_debug(LD_DIRSERV,"Received POST command.");
   1588 
   1589  conn->base_.state = DIR_CONN_STATE_SERVER_WRITING;
   1590 
   1591  if (!public_server_mode(options)) {
   1592    log_info(LD_DIR, "Rejected dir post request from %s "
   1593             "since we're not a public relay.",
   1594             connection_describe_peer(TO_CONN(conn)));
   1595    write_short_http_response(conn, 503, "Not acting as a public relay");
   1596    goto done;
   1597  }
   1598 
   1599  if (parse_http_url(headers, &url) < 0) {
   1600    write_short_http_response(conn, 400, "Bad request");
   1601    return 0;
   1602  }
   1603  log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url));
   1604 
   1605  /* Handle HS descriptor publish request. We force an anonymous connection
   1606   * (which also tests for encrypted). We do not allow single-hop client to
   1607   * post a descriptor onto an HSDir. */
   1608  if (!strcmpstart(url, "/tor/hs/")) {
   1609    if (!connection_dir_is_anonymous(conn)) {
   1610      write_short_http_response(conn, 503,
   1611                                "Rejecting single hop HS descriptor post");
   1612      goto done;
   1613    }
   1614    const char *msg = "HS descriptor stored successfully.";
   1615 
   1616    /* We most probably have a publish request for an HS descriptor. */
   1617    int code = handle_post_hs_descriptor(url, body);
   1618    if (code != 200) {
   1619      msg = "Invalid HS descriptor. Rejected.";
   1620    }
   1621    write_short_http_response(conn, code, msg);
   1622    goto done;
   1623  }
   1624 
   1625  if (!authdir_mode(options)) {
   1626    /* we just provide cached directories; we don't want to
   1627     * receive anything. */
   1628    write_short_http_response(conn, 400, "Nonauthoritative directory does not "
   1629                           "accept posted server descriptors");
   1630    goto done;
   1631  }
   1632 
   1633  if (authdir_mode(options) &&
   1634      !strcmp(url,"/tor/")) { /* server descriptor post */
   1635    const char *msg = "[None]";
   1636    uint8_t purpose = authdir_mode_bridge(options) ?
   1637                      ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL;
   1638 
   1639    {
   1640      char *genreason = http_get_header(headers, "X-Desc-Gen-Reason: ");
   1641      log_info(LD_DIRSERV,
   1642               "New descriptor post, because: %s",
   1643               genreason ? genreason : "not specified");
   1644      tor_free(genreason);
   1645    }
   1646 
   1647    was_router_added_t r = dirserv_add_multiple_descriptors(body, body_len,
   1648                                           purpose, conn->base_.address, &msg);
   1649    tor_assert(msg);
   1650 
   1651    if (r == ROUTER_ADDED_SUCCESSFULLY) {
   1652      write_short_http_response(conn, 200, msg);
   1653    } else if (WRA_WAS_OUTDATED(r)) {
   1654      write_http_response_header_impl(conn, -1, NULL, NULL,
   1655                                      "X-Descriptor-Not-New: Yes\r\n", -1);
   1656    } else {
   1657      log_info(LD_DIRSERV,
   1658               "Rejected router descriptor or extra-info from %s "
   1659               "(\"%s\").",
   1660               connection_describe_peer(TO_CONN(conn)),
   1661               msg);
   1662      write_short_http_response(conn, 400, msg);
   1663    }
   1664    goto done;
   1665  }
   1666 
   1667  if (authdir_mode_v3(options) &&
   1668      !strcmp(url,"/tor/post/vote")) { /* v3 networkstatus vote */
   1669    const char *msg = "OK";
   1670    int status;
   1671    if (dirvote_add_vote(body, approx_time(), TO_CONN(conn)->address,
   1672                         &msg, &status)) {
   1673      write_short_http_response(conn, status, "Vote stored");
   1674    } else {
   1675      tor_assert(msg);
   1676      log_warn(LD_DIRSERV, "Rejected vote from %s (\"%s\").",
   1677               connection_describe_peer(TO_CONN(conn)),
   1678               msg);
   1679      write_short_http_response(conn, status, msg);
   1680    }
   1681    goto done;
   1682  }
   1683 
   1684  if (authdir_mode_v3(options) &&
   1685      !strcmp(url,"/tor/post/consensus-signature")) { /* sigs on consensus. */
   1686    const char *msg = NULL;
   1687    if (dirvote_add_signatures(body, conn->base_.address, &msg)>=0) {
   1688      write_short_http_response(conn, 200, msg?msg:"Signatures stored");
   1689    } else {
   1690      log_warn(LD_DIR, "Unable to store signatures posted by %s: %s",
   1691               connection_describe_peer(TO_CONN(conn)),
   1692               msg?msg:"???");
   1693      write_short_http_response(conn, 400,
   1694                                msg?msg:"Unable to store signatures");
   1695    }
   1696    goto done;
   1697  }
   1698 
   1699  /* we didn't recognize the url */
   1700  write_short_http_response(conn, 404, "Not found");
   1701 
   1702 done:
   1703  tor_free(url);
   1704  return 0;
   1705 }
   1706 
   1707 /** If <b>headers</b> indicates that a proxy was involved, then rewrite
   1708 * <b>conn</b>-\>address to describe our best guess of the address that
   1709 * originated this HTTP request. */
   1710 static void
   1711 http_set_address_origin(const char *headers, connection_t *conn)
   1712 {
   1713  char *fwd;
   1714 
   1715  fwd = http_get_header(headers, "Forwarded-For: ");
   1716  if (!fwd)
   1717    fwd = http_get_header(headers, "X-Forwarded-For: ");
   1718  if (fwd) {
   1719    tor_addr_t toraddr;
   1720    if (tor_addr_parse(&toraddr,fwd) == -1 ||
   1721        tor_addr_is_internal(&toraddr,0)) {
   1722      log_debug(LD_DIR, "Ignoring local/internal IP %s", escaped(fwd));
   1723      tor_free(fwd);
   1724      return;
   1725    }
   1726 
   1727    tor_free(conn->address);
   1728    conn->address = tor_strdup(fwd);
   1729    tor_free(fwd);
   1730  }
   1731 }
   1732 
   1733 /** Called when a dirserver receives data on a directory connection;
   1734 * looks for an HTTP request.  If the request is complete, remove it
   1735 * from the inbuf, try to process it; otherwise, leave it on the
   1736 * buffer.  Return a 0 on success, or -1 on error.
   1737 */
   1738 int
   1739 directory_handle_command(dir_connection_t *conn)
   1740 {
   1741  char *headers=NULL, *body=NULL;
   1742  size_t body_len=0;
   1743  int r;
   1744 
   1745  tor_assert(conn);
   1746  tor_assert(conn->base_.type == CONN_TYPE_DIR);
   1747 
   1748  switch (connection_fetch_from_buf_http(TO_CONN(conn),
   1749                              &headers, MAX_HEADERS_SIZE,
   1750                              &body, &body_len, MAX_DIR_UL_SIZE, 0)) {
   1751    case -1: /* overflow */
   1752      log_warn(LD_DIRSERV,
   1753               "Request too large from %s to DirPort. Closing.",
   1754               connection_describe_peer(TO_CONN(conn)));
   1755      return -1;
   1756    case 0:
   1757      log_debug(LD_DIRSERV,"command not all here yet.");
   1758      return 0;
   1759    /* case 1, fall through */
   1760  }
   1761 
   1762  http_set_address_origin(headers, TO_CONN(conn));
   1763  // we should escape headers here as well,
   1764  // but we can't call escaped() twice, as it uses the same buffer
   1765  //log_debug(LD_DIRSERV,"headers %s, body %s.", headers, escaped(body));
   1766 
   1767  if (!strncasecmp(headers,"GET",3))
   1768    r = directory_handle_command_get(conn, headers, body, body_len);
   1769  else if (!strncasecmp(headers,"POST",4))
   1770    r = directory_handle_command_post(conn, headers, body, body_len);
   1771  else {
   1772    log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
   1773           "Got headers %s with unknown command. Closing.",
   1774           escaped(headers));
   1775    r = -1;
   1776  }
   1777 
   1778  tor_free(headers); tor_free(body);
   1779  return r;
   1780 }