LCOV - code coverage report
Current view: top level - feature/dircache - dircache.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 547 774 70.7 %
Date: 2021-11-24 03:28:48 Functions: 28 33 84.8 %

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

Generated by: LCOV version 1.14