diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-07-05 16:31:38 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-07-05 17:15:50 -0400 |
commit | 63b4ea22af8e8314dd718f02046de5f4b91edf9d (patch) | |
tree | af52b6fba37f22c86447fd5267dd5eb557807c8b /src/feature/relay | |
parent | ce84200542f48a92e8b56a8d032401ecd153e90c (diff) | |
download | tor-63b4ea22af8e8314dd718f02046de5f4b91edf9d.tar.gz tor-63b4ea22af8e8314dd718f02046de5f4b91edf9d.zip |
Move literally everything out of src/or
This commit won't build yet -- it just puts everything in a slightly
more logical place.
The reasoning here is that "src/core" will hold the stuff that every (or
nearly every) tor instance will need in order to do onion routing.
Other features (including some necessary ones) will live in
"src/feature". The "src/app" directory will hold the stuff needed
to have Tor be an application you can actually run.
This commit DOES NOT refactor the former contents of src/or into a
logical set of acyclic libraries, or change any code at all. That
will have to come in the future.
We will continue to move things around and split them in the future,
but I hope this lays a reasonable groundwork for doing so.
Diffstat (limited to 'src/feature/relay')
-rw-r--r-- | src/feature/relay/dns.c | 2143 | ||||
-rw-r--r-- | src/feature/relay/dns.h | 70 | ||||
-rw-r--r-- | src/feature/relay/dns_structs.h | 102 | ||||
-rw-r--r-- | src/feature/relay/ext_orport.c | 662 | ||||
-rw-r--r-- | src/feature/relay/ext_orport.h | 64 | ||||
-rw-r--r-- | src/feature/relay/router.c | 3832 | ||||
-rw-r--r-- | src/feature/relay/router.h | 161 | ||||
-rw-r--r-- | src/feature/relay/routerkeys.c | 1413 | ||||
-rw-r--r-- | src/feature/relay/routerkeys.h | 85 |
9 files changed, 8532 insertions, 0 deletions
diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c new file mode 100644 index 0000000000..4ac58552f4 --- /dev/null +++ b/src/feature/relay/dns.c @@ -0,0 +1,2143 @@ +/* Copyright (c) 2003-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dns.c + * \brief Implements a local cache for DNS results for Tor servers. + * This is implemented as a wrapper around Adam Langley's eventdns.c code. + * (We can't just use gethostbyname() and friends because we really need to + * be nonblocking.) + * + * There are three main cases when a Tor relay uses dns.c to launch a DNS + * request: + * <ol> + * <li>To check whether the DNS server is working more or less correctly. + * This happens via dns_launch_correctness_checks(). The answer is + * reported in the return value from later calls to + * dns_seems_to_be_broken(). + * <li>When a client has asked the relay, in a RELAY_BEGIN cell, to connect + * to a given server by hostname. This happens via dns_resolve(). + * <li>When a client has asked the relay, in a RELAY_RESOLVE cell, to look + * up a given server's IP address(es) by hostname. This also happens via + * dns_resolve(). + * </ol> + * + * Each of these gets handled a little differently. + * + * To check for correctness, we look up some hostname we expect to exist and + * have real entries, some hostnames which we expect to definitely not exist, + * and some hostnames that we expect to probably not exist. If too many of + * the hostnames that shouldn't exist do exist, that's a DNS hijacking + * attempt. If too many of the hostnames that should exist have the same + * addresses as the ones that shouldn't exist, that's a very bad DNS hijacking + * attempt, or a very naughty captive portal. And if the hostnames that + * should exist simply don't exist, we probably have a broken nameserver. + * + * To handle client requests, we first check our cache for answers. If there + * isn't something up-to-date, we've got to launch A or AAAA requests as + * appropriate. How we handle responses to those in particular is a bit + * complex; see dns_lookup() and set_exitconn_info_from_resolve(). + * + * When a lookup is finally complete, the inform_pending_connections() + * function will tell all of the streams that have been waiting for the + * resolve, by calling connection_exit_connect() if the client sent a + * RELAY_BEGIN cell, and by calling send_resolved_cell() or + * send_hostname_cell() if the client sent a RELAY_RESOLVE cell. + **/ + +#define DNS_PRIVATE + +#include "or/or.h" +#include "or/circuitlist.h" +#include "or/circuituse.h" +#include "or/config.h" +#include "or/connection.h" +#include "or/connection_edge.h" +#include "or/control.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "or/dns.h" +#include "or/main.h" +#include "or/policies.h" +#include "or/relay.h" +#include "or/router.h" +#include "ht.h" +#include "lib/sandbox/sandbox.h" +#include "lib/evloop/compat_libevent.h" + +#include "or/edge_connection_st.h" +#include "or/or_circuit_st.h" + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#include <event2/event.h> +#include <event2/dns.h> + +/** How long will we wait for an answer from the resolver before we decide + * that the resolver is wedged? */ +#define RESOLVE_MAX_TIMEOUT 300 + +/** Our evdns_base; this structure handles all our name lookups. */ +static struct evdns_base *the_evdns_base = NULL; + +/** Have we currently configured nameservers with eventdns? */ +static int nameservers_configured = 0; +/** Did our most recent attempt to configure nameservers with eventdns fail? */ +static int nameserver_config_failed = 0; +/** What was the resolv_conf fname we last used when configuring the + * nameservers? Used to check whether we need to reconfigure. */ +static char *resolv_conf_fname = NULL; +/** What was the mtime on the resolv.conf file we last used when configuring + * the nameservers? Used to check whether we need to reconfigure. */ +static time_t resolv_conf_mtime = 0; + +static void purge_expired_resolves(time_t now); +static void dns_found_answer(const char *address, uint8_t query_type, + int dns_answer, + const tor_addr_t *addr, + const char *hostname, + uint32_t ttl); +static void add_wildcarded_test_address(const char *address); +static int configure_nameservers(int force); +static int answer_is_wildcarded(const char *ip); +static int evdns_err_is_transient(int err); +static void inform_pending_connections(cached_resolve_t *resolve); +static void make_pending_resolve_cached(cached_resolve_t *cached); + +#ifdef DEBUG_DNS_CACHE +static void assert_cache_ok_(void); +#define assert_cache_ok() assert_cache_ok_() +#else +#define assert_cache_ok() STMT_NIL +#endif /* defined(DEBUG_DNS_CACHE) */ +static void assert_resolve_ok(cached_resolve_t *resolve); + +/** Hash table of cached_resolve objects. */ +static HT_HEAD(cache_map, cached_resolve_t) cache_root; + +/** Global: how many IPv6 requests have we made in all? */ +static uint64_t n_ipv6_requests_made = 0; +/** Global: how many IPv6 requests have timed out? */ +static uint64_t n_ipv6_timeouts = 0; +/** Global: Do we think that IPv6 DNS is broken? */ +static int dns_is_broken_for_ipv6 = 0; + +/** Function to compare hashed resolves on their addresses; used to + * implement hash tables. */ +static inline int +cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b) +{ + /* make this smarter one day? */ + assert_resolve_ok(a); // Not b; b may be just a search. + return !strncmp(a->address, b->address, MAX_ADDRESSLEN); +} + +/** Hash function for cached_resolve objects */ +static inline unsigned int +cached_resolve_hash(cached_resolve_t *a) +{ + return (unsigned) siphash24g((const uint8_t*)a->address, strlen(a->address)); +} + +HT_PROTOTYPE(cache_map, cached_resolve_t, node, cached_resolve_hash, + cached_resolves_eq) +HT_GENERATE2(cache_map, cached_resolve_t, node, cached_resolve_hash, + cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_) + +/** Initialize the DNS cache. */ +static void +init_cache_map(void) +{ + HT_INIT(cache_map, &cache_root); +} + +/** Helper: called by eventdns when eventdns wants to log something. */ +static void +evdns_log_cb(int warn, const char *msg) +{ + const char *cp; + static int all_down = 0; + int severity = warn ? LOG_WARN : LOG_INFO; + if (!strcmpstart(msg, "Resolve requested for") && + get_options()->SafeLogging) { + log_info(LD_EXIT, "eventdns: Resolve requested."); + return; + } else if (!strcmpstart(msg, "Search: ")) { + return; + } + if (!strcmpstart(msg, "Nameserver ") && (cp=strstr(msg, " has failed: "))) { + char *ns = tor_strndup(msg+11, cp-(msg+11)); + const char *colon = strchr(cp, ':'); + tor_assert(colon); + const char *err = colon+2; + /* Don't warn about a single failed nameserver; we'll warn with 'all + * nameservers have failed' if we're completely out of nameservers; + * otherwise, the situation is tolerable. */ + severity = LOG_INFO; + control_event_server_status(LOG_NOTICE, + "NAMESERVER_STATUS NS=%s STATUS=DOWN ERR=%s", + ns, escaped(err)); + tor_free(ns); + } else if (!strcmpstart(msg, "Nameserver ") && + (cp=strstr(msg, " is back up"))) { + char *ns = tor_strndup(msg+11, cp-(msg+11)); + severity = (all_down && warn) ? LOG_NOTICE : LOG_INFO; + all_down = 0; + control_event_server_status(LOG_NOTICE, + "NAMESERVER_STATUS NS=%s STATUS=UP", ns); + tor_free(ns); + } else if (!strcmp(msg, "All nameservers have failed")) { + control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN"); + all_down = 1; + } else if (!strcmpstart(msg, "Address mismatch on received DNS")) { + static ratelim_t mismatch_limit = RATELIM_INIT(3600); + const char *src = strstr(msg, " Apparent source"); + if (!src || get_options()->SafeLogging) { + src = ""; + } + log_fn_ratelim(&mismatch_limit, severity, LD_EXIT, + "eventdns: Received a DNS packet from " + "an IP address to which we did not send a request. This " + "could be a DNS spoofing attempt, or some kind of " + "misconfiguration.%s", src); + return; + } + tor_log(severity, LD_EXIT, "eventdns: %s", msg); +} + +/** Helper: passed to eventdns.c as a callback so it can generate random + * numbers for transaction IDs and 0x20-hack coding. */ +static void +dns_randfn_(char *b, size_t n) +{ + crypto_rand(b,n); +} + +/** Initialize the DNS subsystem; called by the OR process. */ +int +dns_init(void) +{ + init_cache_map(); + evdns_set_random_bytes_fn(dns_randfn_); + if (server_mode(get_options())) { + int r = configure_nameservers(1); + return r; + } + return 0; +} + +/** Called when DNS-related options change (or may have changed). Returns -1 + * on failure, 0 on success. */ +int +dns_reset(void) +{ + const or_options_t *options = get_options(); + if (! server_mode(options)) { + + if (!the_evdns_base) { + if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) { + log_err(LD_BUG, "Couldn't create an evdns_base"); + return -1; + } + } + + evdns_base_clear_nameservers_and_suspend(the_evdns_base); + evdns_base_search_clear(the_evdns_base); + nameservers_configured = 0; + tor_free(resolv_conf_fname); + resolv_conf_mtime = 0; + } else { + if (configure_nameservers(0) < 0) { + return -1; + } + } + return 0; +} + +/** Return true iff the most recent attempt to initialize the DNS subsystem + * failed. */ +int +has_dns_init_failed(void) +{ + return nameserver_config_failed; +} + +/** Helper: Given a TTL from a DNS response, determine what TTL to give the + * OP that asked us to resolve it, and how long to cache that record + * ourselves. */ +uint32_t +dns_clip_ttl(uint32_t ttl) +{ + /* This logic is a defense against "DefectTor" DNS-based traffic + * confirmation attacks, as in https://nymity.ch/tor-dns/tor-dns.pdf . + * We only give two values: a "low" value and a "high" value. + */ + if (ttl < MIN_DNS_TTL_AT_EXIT) + return MIN_DNS_TTL_AT_EXIT; + else + return MAX_DNS_TTL_AT_EXIT; +} + +/** Helper: free storage held by an entry in the DNS cache. */ +static void +free_cached_resolve_(cached_resolve_t *r) +{ + if (!r) + return; + while (r->pending_connections) { + pending_connection_t *victim = r->pending_connections; + r->pending_connections = victim->next; + tor_free(victim); + } + if (r->res_status_hostname == RES_STATUS_DONE_OK) + tor_free(r->result_ptr.hostname); + r->magic = 0xFF00FF00; + tor_free(r); +} + +/** Compare two cached_resolve_t pointers by expiry time, and return + * less-than-zero, zero, or greater-than-zero as appropriate. Used for + * the priority queue implementation. */ +static int +compare_cached_resolves_by_expiry_(const void *_a, const void *_b) +{ + const cached_resolve_t *a = _a, *b = _b; + if (a->expire < b->expire) + return -1; + else if (a->expire == b->expire) + return 0; + else + return 1; +} + +/** Priority queue of cached_resolve_t objects to let us know when they + * will expire. */ +static smartlist_t *cached_resolve_pqueue = NULL; + +static void +cached_resolve_add_answer(cached_resolve_t *resolve, + int query_type, + int dns_result, + const tor_addr_t *answer_addr, + const char *answer_hostname, + uint32_t ttl) +{ + if (query_type == DNS_PTR) { + if (resolve->res_status_hostname != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_hostname) { + resolve->result_ptr.hostname = tor_strdup(answer_hostname); + resolve->res_status_hostname = RES_STATUS_DONE_OK; + } else { + resolve->result_ptr.err_hostname = dns_result; + resolve->res_status_hostname = RES_STATUS_DONE_ERR; + } + resolve->ttl_hostname = ttl; + } else if (query_type == DNS_IPv4_A) { + if (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_addr && + tor_addr_family(answer_addr) == AF_INET) { + resolve->result_ipv4.addr_ipv4 = tor_addr_to_ipv4h(answer_addr); + resolve->res_status_ipv4 = RES_STATUS_DONE_OK; + } else { + resolve->result_ipv4.err_ipv4 = dns_result; + resolve->res_status_ipv4 = RES_STATUS_DONE_ERR; + } + resolve->ttl_ipv4 = ttl; + } else if (query_type == DNS_IPv6_AAAA) { + if (resolve->res_status_ipv6 != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_addr && + tor_addr_family(answer_addr) == AF_INET6) { + memcpy(&resolve->result_ipv6.addr_ipv6, + tor_addr_to_in6(answer_addr), + sizeof(struct in6_addr)); + resolve->res_status_ipv6 = RES_STATUS_DONE_OK; + } else { + resolve->result_ipv6.err_ipv6 = dns_result; + resolve->res_status_ipv6 = RES_STATUS_DONE_ERR; + } + resolve->ttl_ipv6 = ttl; + } +} + +/** Return true iff there are no in-flight requests for <b>resolve</b>. */ +static int +cached_resolve_have_all_answers(const cached_resolve_t *resolve) +{ + return (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT && + resolve->res_status_ipv6 != RES_STATUS_INFLIGHT && + resolve->res_status_hostname != RES_STATUS_INFLIGHT); +} + +/** Set an expiry time for a cached_resolve_t, and add it to the expiry + * priority queue */ +static void +set_expiry(cached_resolve_t *resolve, time_t expires) +{ + tor_assert(resolve && resolve->expire == 0); + if (!cached_resolve_pqueue) + cached_resolve_pqueue = smartlist_new(); + resolve->expire = expires; + smartlist_pqueue_add(cached_resolve_pqueue, + compare_cached_resolves_by_expiry_, + offsetof(cached_resolve_t, minheap_idx), + resolve); +} + +/** Free all storage held in the DNS cache and related structures. */ +void +dns_free_all(void) +{ + cached_resolve_t **ptr, **next, *item; + assert_cache_ok(); + if (cached_resolve_pqueue) { + SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, + { + if (res->state == CACHE_STATE_DONE) + free_cached_resolve_(res); + }); + } + for (ptr = HT_START(cache_map, &cache_root); ptr != NULL; ptr = next) { + item = *ptr; + next = HT_NEXT_RMV(cache_map, &cache_root, ptr); + free_cached_resolve_(item); + } + HT_CLEAR(cache_map, &cache_root); + smartlist_free(cached_resolve_pqueue); + cached_resolve_pqueue = NULL; + tor_free(resolv_conf_fname); +} + +/** Remove every cached_resolve whose <b>expire</b> time is before or + * equal to <b>now</b> from the cache. */ +static void +purge_expired_resolves(time_t now) +{ + cached_resolve_t *resolve, *removed; + pending_connection_t *pend; + edge_connection_t *pendconn; + + assert_cache_ok(); + if (!cached_resolve_pqueue) + return; + + while (smartlist_len(cached_resolve_pqueue)) { + resolve = smartlist_get(cached_resolve_pqueue, 0); + if (resolve->expire > now) + break; + smartlist_pqueue_pop(cached_resolve_pqueue, + compare_cached_resolves_by_expiry_, + offsetof(cached_resolve_t, minheap_idx)); + + if (resolve->state == CACHE_STATE_PENDING) { + log_debug(LD_EXIT, + "Expiring a dns resolve %s that's still pending. Forgot to " + "cull it? DNS resolve didn't tell us about the timeout?", + escaped_safe_str(resolve->address)); + } else if (resolve->state == CACHE_STATE_CACHED) { + log_debug(LD_EXIT, + "Forgetting old cached resolve (address %s, expires %lu)", + escaped_safe_str(resolve->address), + (unsigned long)resolve->expire); + tor_assert(!resolve->pending_connections); + } else { + tor_assert(resolve->state == CACHE_STATE_DONE); + tor_assert(!resolve->pending_connections); + } + + if (resolve->pending_connections) { + log_debug(LD_EXIT, + "Closing pending connections on timed-out DNS resolve!"); + while (resolve->pending_connections) { + pend = resolve->pending_connections; + resolve->pending_connections = pend->next; + /* Connections should only be pending if they have no socket. */ + tor_assert(!SOCKET_OK(pend->conn->base_.s)); + pendconn = pend->conn; + /* Prevent double-remove */ + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + if (!pendconn->base_.marked_for_close) { + connection_edge_end(pendconn, END_STREAM_REASON_TIMEOUT); + circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); + connection_free_(TO_CONN(pendconn)); + } + tor_free(pend); + } + } + + if (resolve->state == CACHE_STATE_CACHED || + resolve->state == CACHE_STATE_PENDING) { + removed = HT_REMOVE(cache_map, &cache_root, resolve); + if (removed != resolve) { + log_err(LD_BUG, "The expired resolve we purged didn't match any in" + " the cache. Tried to purge %s (%p); instead got %s (%p).", + resolve->address, (void*)resolve, + removed ? removed->address : "NULL", (void*)removed); + } + tor_assert(removed == resolve); + } else { + /* This should be in state DONE. Make sure it's not in the cache. */ + cached_resolve_t *tmp = HT_FIND(cache_map, &cache_root, resolve); + tor_assert(tmp != resolve); + } + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) + tor_free(resolve->result_ptr.hostname); + resolve->magic = 0xF0BBF0BB; + tor_free(resolve); + } + + assert_cache_ok(); +} + +/* argument for send_resolved_cell only, meaning "let the answer type be ipv4 + * or ipv6 depending on the connection's address". */ +#define RESOLVED_TYPE_AUTO 0xff + +/** Send a response to the RESOLVE request of a connection. + * <b>answer_type</b> must be one of + * RESOLVED_TYPE_(AUTO|ERROR|ERROR_TRANSIENT|). + * + * If <b>circ</b> is provided, and we have a cached answer, send the + * answer back along circ; otherwise, send the answer back along + * <b>conn</b>'s attached circuit. + */ +MOCK_IMPL(STATIC void, +send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type, + const cached_resolve_t *resolved)) +{ + char buf[RELAY_PAYLOAD_SIZE], *cp = buf; + size_t buflen = 0; + uint32_t ttl; + + buf[0] = answer_type; + ttl = dns_clip_ttl(conn->address_ttl); + + switch (answer_type) + { + case RESOLVED_TYPE_AUTO: + if (resolved && resolved->res_status_ipv4 == RES_STATUS_DONE_OK) { + cp[0] = RESOLVED_TYPE_IPV4; + cp[1] = 4; + set_uint32(cp+2, htonl(resolved->result_ipv4.addr_ipv4)); + set_uint32(cp+6, htonl(ttl)); + cp += 10; + } + if (resolved && resolved->res_status_ipv6 == RES_STATUS_DONE_OK) { + const uint8_t *bytes = resolved->result_ipv6.addr_ipv6.s6_addr; + cp[0] = RESOLVED_TYPE_IPV6; + cp[1] = 16; + memcpy(cp+2, bytes, 16); + set_uint32(cp+18, htonl(ttl)); + cp += 22; + } + if (cp != buf) { + buflen = cp - buf; + break; + } else { + answer_type = RESOLVED_TYPE_ERROR; + /* fall through. */ + } + /* Falls through. */ + case RESOLVED_TYPE_ERROR_TRANSIENT: + case RESOLVED_TYPE_ERROR: + { + const char *errmsg = "Error resolving hostname"; + size_t msglen = strlen(errmsg); + + buf[0] = answer_type; + buf[1] = msglen; + strlcpy(buf+2, errmsg, sizeof(buf)-2); + set_uint32(buf+2+msglen, htonl(ttl)); + buflen = 6+msglen; + break; + } + default: + tor_assert(0); + return; + } + // log_notice(LD_EXIT, "Sending a regular RESOLVED reply: "); + + connection_edge_send_command(conn, RELAY_COMMAND_RESOLVED, buf, buflen); +} + +/** Send a response to the RESOLVE request of a connection for an in-addr.arpa + * address on connection <b>conn</b> which yielded the result <b>hostname</b>. + * The answer type will be RESOLVED_HOSTNAME. + * + * If <b>circ</b> is provided, and we have a cached answer, send the + * answer back along circ; otherwise, send the answer back along + * <b>conn</b>'s attached circuit. + */ +MOCK_IMPL(STATIC void, +send_resolved_hostname_cell,(edge_connection_t *conn, + const char *hostname)) +{ + char buf[RELAY_PAYLOAD_SIZE]; + size_t buflen; + uint32_t ttl; + size_t namelen = strlen(hostname); + tor_assert(hostname); + + tor_assert(namelen < 256); + ttl = dns_clip_ttl(conn->address_ttl); + + buf[0] = RESOLVED_TYPE_HOSTNAME; + buf[1] = (uint8_t)namelen; + memcpy(buf+2, hostname, namelen); + set_uint32(buf+2+namelen, htonl(ttl)); + buflen = 2+namelen+4; + + // log_notice(LD_EXIT, "Sending a reply RESOLVED reply: %s", hostname); + connection_edge_send_command(conn, RELAY_COMMAND_RESOLVED, buf, buflen); + // log_notice(LD_EXIT, "Sent"); +} + +/** See if we have a cache entry for <b>exitconn</b>-\>address. If so, + * if resolve valid, put it into <b>exitconn</b>-\>addr and return 1. + * If resolve failed, free exitconn and return -1. + * + * (For EXIT_PURPOSE_RESOLVE connections, send back a RESOLVED error cell + * on returning -1. For EXIT_PURPOSE_CONNECT connections, there's no + * need to send back an END cell, since connection_exit_begin_conn will + * do that for us.) + * + * If we have a cached answer, send the answer back along <b>exitconn</b>'s + * circuit. + * + * Else, if seen before and pending, add conn to the pending list, + * and return 0. + * + * Else, if not seen before, add conn to pending list, hand to + * dns farm, and return 0. + * + * Exitconn's on_circuit field must be set, but exitconn should not + * yet be linked onto the n_streams/resolving_streams list of that circuit. + * On success, link the connection to n_streams if it's an exit connection. + * On "pending", link the connection to resolving streams. Otherwise, + * clear its on_circuit field. + */ +int +dns_resolve(edge_connection_t *exitconn) +{ + or_circuit_t *oncirc = TO_OR_CIRCUIT(exitconn->on_circuit); + int is_resolve, r; + int made_connection_pending = 0; + char *hostname = NULL; + cached_resolve_t *resolve = NULL; + is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE; + + r = dns_resolve_impl(exitconn, is_resolve, oncirc, &hostname, + &made_connection_pending, &resolve); + + switch (r) { + case 1: + /* We got an answer without a lookup -- either the answer was + * cached, or it was obvious (like an IP address). */ + if (is_resolve) { + /* Send the answer back right now, and detach. */ + if (hostname) + send_resolved_hostname_cell(exitconn, hostname); + else + send_resolved_cell(exitconn, RESOLVED_TYPE_AUTO, resolve); + exitconn->on_circuit = NULL; + } else { + /* Add to the n_streams list; the calling function will send back a + * connected cell. */ + exitconn->next_stream = oncirc->n_streams; + oncirc->n_streams = exitconn; + } + break; + case 0: + /* The request is pending: add the connection into the linked list of + * resolving_streams on this circuit. */ + exitconn->base_.state = EXIT_CONN_STATE_RESOLVING; + exitconn->next_stream = oncirc->resolving_streams; + oncirc->resolving_streams = exitconn; + break; + case -2: + case -1: + /* The request failed before it could start: cancel this connection, + * and stop everybody waiting for the same connection. */ + if (is_resolve) { + send_resolved_cell(exitconn, + (r == -1) ? RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT, + NULL); + } + + exitconn->on_circuit = NULL; + + dns_cancel_pending_resolve(exitconn->base_.address); + + if (!made_connection_pending && !exitconn->base_.marked_for_close) { + /* If we made the connection pending, then we freed it already in + * dns_cancel_pending_resolve(). If we marked it for close, it'll + * get freed from the main loop. Otherwise, can free it now. */ + connection_free_(TO_CONN(exitconn)); + } + break; + default: + tor_assert(0); + } + + tor_free(hostname); + return r; +} + +/** Helper function for dns_resolve: same functionality, but does not handle: + * - marking connections on error and clearing their on_circuit + * - linking connections to n_streams/resolving_streams, + * - sending resolved cells if we have an answer/error right away, + * + * Return -2 on a transient error. If it's a reverse resolve and it's + * successful, sets *<b>hostname_out</b> to a newly allocated string + * holding the cached reverse DNS value. + * + * Set *<b>made_connection_pending_out</b> to true if we have placed + * <b>exitconn</b> on the list of pending connections for some resolve; set it + * to false otherwise. + * + * Set *<b>resolve_out</b> to a cached resolve, if we found one. + */ +MOCK_IMPL(STATIC int, +dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve, + or_circuit_t *oncirc, char **hostname_out, + int *made_connection_pending_out, + cached_resolve_t **resolve_out)) +{ + cached_resolve_t *resolve; + cached_resolve_t search; + pending_connection_t *pending_connection; + int is_reverse = 0; + tor_addr_t addr; + time_t now = time(NULL); + int r; + assert_connection_ok(TO_CONN(exitconn), 0); + tor_assert(!SOCKET_OK(exitconn->base_.s)); + assert_cache_ok(); + tor_assert(oncirc); + *made_connection_pending_out = 0; + + /* first check if exitconn->base_.address is an IP. If so, we already + * know the answer. */ + if (tor_addr_parse(&addr, exitconn->base_.address) >= 0) { + if (tor_addr_family(&addr) == AF_INET || + tor_addr_family(&addr) == AF_INET6) { + tor_addr_copy(&exitconn->base_.addr, &addr); + exitconn->address_ttl = DEFAULT_DNS_TTL; + return 1; + } else { + /* XXXX unspec? Bogus? */ + return -1; + } + } + + /* If we're a non-exit, don't even do DNS lookups. */ + if (router_my_exit_policy_is_reject_star()) + return -1; + + if (address_is_invalid_destination(exitconn->base_.address, 0)) { + tor_log(LOG_PROTOCOL_WARN, LD_EXIT, + "Rejecting invalid destination address %s", + escaped_safe_str(exitconn->base_.address)); + return -1; + } + + /* then take this opportunity to see if there are any expired + * resolves in the hash table. */ + purge_expired_resolves(now); + + /* lower-case exitconn->base_.address, so it's in canonical form */ + tor_strlower(exitconn->base_.address); + + /* Check whether this is a reverse lookup. If it's malformed, or it's a + * .in-addr.arpa address but this isn't a resolve request, kill the + * connection. + */ + if ((r = tor_addr_parse_PTR_name(&addr, exitconn->base_.address, + AF_UNSPEC, 0)) != 0) { + if (r == 1) { + is_reverse = 1; + if (tor_addr_is_internal(&addr, 0)) /* internal address? */ + return -1; + } + + if (!is_reverse || !is_resolve) { + if (!is_reverse) + log_info(LD_EXIT, "Bad .in-addr.arpa address \"%s\"; sending error.", + escaped_safe_str(exitconn->base_.address)); + else if (!is_resolve) + log_info(LD_EXIT, + "Attempt to connect to a .in-addr.arpa address \"%s\"; " + "sending error.", + escaped_safe_str(exitconn->base_.address)); + + return -1; + } + //log_notice(LD_EXIT, "Looks like an address %s", + //exitconn->base_.address); + } + exitconn->is_reverse_dns_lookup = is_reverse; + + /* now check the hash table to see if 'address' is already there. */ + strlcpy(search.address, exitconn->base_.address, sizeof(search.address)); + resolve = HT_FIND(cache_map, &cache_root, &search); + if (resolve && resolve->expire > now) { /* already there */ + switch (resolve->state) { + case CACHE_STATE_PENDING: + /* add us to the pending list */ + pending_connection = tor_malloc_zero( + sizeof(pending_connection_t)); + pending_connection->conn = exitconn; + pending_connection->next = resolve->pending_connections; + resolve->pending_connections = pending_connection; + *made_connection_pending_out = 1; + log_debug(LD_EXIT,"Connection (fd "TOR_SOCKET_T_FORMAT") waiting " + "for pending DNS resolve of %s", exitconn->base_.s, + escaped_safe_str(exitconn->base_.address)); + return 0; + case CACHE_STATE_CACHED: + log_debug(LD_EXIT,"Connection (fd "TOR_SOCKET_T_FORMAT") found " + "cached answer for %s", + exitconn->base_.s, + escaped_safe_str(resolve->address)); + + *resolve_out = resolve; + + return set_exitconn_info_from_resolve(exitconn, resolve, hostname_out); + case CACHE_STATE_DONE: + log_err(LD_BUG, "Found a 'DONE' dns resolve still in the cache."); + tor_fragile_assert(); + } + tor_assert(0); + } + tor_assert(!resolve); + /* not there, need to add it */ + resolve = tor_malloc_zero(sizeof(cached_resolve_t)); + resolve->magic = CACHED_RESOLVE_MAGIC; + resolve->state = CACHE_STATE_PENDING; + resolve->minheap_idx = -1; + strlcpy(resolve->address, exitconn->base_.address, sizeof(resolve->address)); + + /* add this connection to the pending list */ + pending_connection = tor_malloc_zero(sizeof(pending_connection_t)); + pending_connection->conn = exitconn; + resolve->pending_connections = pending_connection; + *made_connection_pending_out = 1; + + /* Add this resolve to the cache and priority queue. */ + HT_INSERT(cache_map, &cache_root, resolve); + set_expiry(resolve, now + RESOLVE_MAX_TIMEOUT); + + log_debug(LD_EXIT,"Launching %s.", + escaped_safe_str(exitconn->base_.address)); + assert_cache_ok(); + + return launch_resolve(resolve); +} + +/** Given an exit connection <b>exitconn</b>, and a cached_resolve_t + * <b>resolve</b> whose DNS lookups have all either succeeded or failed, + * update the appropriate fields (address_ttl and addr) of <b>exitconn</b>. + * + * The logic can be complicated here, since we might have launched both + * an A lookup and an AAAA lookup, and since either of those might have + * succeeded or failed, and since we want to answer a RESOLVE cell with + * a full answer but answer a BEGIN cell with whatever answer the client + * would accept <i>and</i> we could still connect to. + * + * If this is a reverse lookup, set *<b>hostname_out</b> to a newly allocated + * copy of the name resulting hostname. + * + * Return -2 on a transient error, -1 on a permenent error, and 1 on + * a successful lookup. + */ +MOCK_IMPL(STATIC int, +set_exitconn_info_from_resolve,(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out)) +{ + int ipv4_ok, ipv6_ok, answer_with_ipv4, r; + uint32_t begincell_flags; + const int is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE; + tor_assert(exitconn); + tor_assert(resolve); + + if (exitconn->is_reverse_dns_lookup) { + exitconn->address_ttl = resolve->ttl_hostname; + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) { + *hostname_out = tor_strdup(resolve->result_ptr.hostname); + return 1; + } else { + return -1; + } + } + + /* If we're here then the connection wants one or either of ipv4, ipv6, and + * we can give it one or both. */ + if (is_resolve) { + begincell_flags = BEGIN_FLAG_IPV6_OK; + } else { + begincell_flags = exitconn->begincell_flags; + } + + ipv4_ok = (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) && + ! (begincell_flags & BEGIN_FLAG_IPV4_NOT_OK); + ipv6_ok = (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) && + (begincell_flags & BEGIN_FLAG_IPV6_OK) && + get_options()->IPv6Exit; + + /* Now decide which one to actually give. */ + if (ipv4_ok && ipv6_ok && is_resolve) { + answer_with_ipv4 = 1; + } else if (ipv4_ok && ipv6_ok) { + /* If we have both, see if our exit policy has an opinion. */ + const uint16_t port = exitconn->base_.port; + int ipv4_allowed, ipv6_allowed; + tor_addr_t a4, a6; + tor_addr_from_ipv4h(&a4, resolve->result_ipv4.addr_ipv4); + tor_addr_from_in6(&a6, &resolve->result_ipv6.addr_ipv6); + ipv4_allowed = !router_compare_to_my_exit_policy(&a4, port); + ipv6_allowed = !router_compare_to_my_exit_policy(&a6, port); + if (ipv4_allowed && !ipv6_allowed) { + answer_with_ipv4 = 1; + } else if (ipv6_allowed && !ipv4_allowed) { + answer_with_ipv4 = 0; + } else { + /* Our exit policy would permit both. Answer with whichever the user + * prefers */ + answer_with_ipv4 = !(begincell_flags & + BEGIN_FLAG_IPV6_PREFERRED); + } + } else { + /* Otherwise if one is okay, send it back. */ + if (ipv4_ok) { + answer_with_ipv4 = 1; + } else if (ipv6_ok) { + answer_with_ipv4 = 0; + } else { + /* Neither one was okay. Choose based on user preference. */ + answer_with_ipv4 = !(begincell_flags & + BEGIN_FLAG_IPV6_PREFERRED); + } + } + + /* Finally, we write the answer back. */ + r = 1; + if (answer_with_ipv4) { + if (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) { + tor_addr_from_ipv4h(&exitconn->base_.addr, + resolve->result_ipv4.addr_ipv4); + } else { + r = evdns_err_is_transient(resolve->result_ipv4.err_ipv4) ? -2 : -1; + } + + exitconn->address_ttl = resolve->ttl_ipv4; + } else { + if (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) { + tor_addr_from_in6(&exitconn->base_.addr, + &resolve->result_ipv6.addr_ipv6); + } else { + r = evdns_err_is_transient(resolve->result_ipv6.err_ipv6) ? -2 : -1; + } + + exitconn->address_ttl = resolve->ttl_ipv6; + } + + return r; +} + +/** Log an error and abort if conn is waiting for a DNS resolve. + */ +void +assert_connection_edge_not_dns_pending(edge_connection_t *conn) +{ + pending_connection_t *pend; + cached_resolve_t search; + +#if 1 + cached_resolve_t *resolve; + strlcpy(search.address, conn->base_.address, sizeof(search.address)); + resolve = HT_FIND(cache_map, &cache_root, &search); + if (!resolve) + return; + for (pend = resolve->pending_connections; pend; pend = pend->next) { + tor_assert(pend->conn != conn); + } +#else /* !(1) */ + cached_resolve_t **resolve; + HT_FOREACH(resolve, cache_map, &cache_root) { + for (pend = (*resolve)->pending_connections; pend; pend = pend->next) { + tor_assert(pend->conn != conn); + } + } +#endif /* 1 */ +} + +/** Log an error and abort if any connection waiting for a DNS resolve is + * corrupted. */ +void +assert_all_pending_dns_resolves_ok(void) +{ + pending_connection_t *pend; + cached_resolve_t **resolve; + + HT_FOREACH(resolve, cache_map, &cache_root) { + for (pend = (*resolve)->pending_connections; + pend; + pend = pend->next) { + assert_connection_ok(TO_CONN(pend->conn), 0); + tor_assert(!SOCKET_OK(pend->conn->base_.s)); + tor_assert(!connection_in_array(TO_CONN(pend->conn))); + } + } +} + +/** Remove <b>conn</b> from the list of connections waiting for conn-\>address. + */ +void +connection_dns_remove(edge_connection_t *conn) +{ + pending_connection_t *pend, *victim; + cached_resolve_t search; + cached_resolve_t *resolve; + + tor_assert(conn->base_.type == CONN_TYPE_EXIT); + tor_assert(conn->base_.state == EXIT_CONN_STATE_RESOLVING); + + strlcpy(search.address, conn->base_.address, sizeof(search.address)); + + resolve = HT_FIND(cache_map, &cache_root, &search); + if (!resolve) { + log_notice(LD_BUG, "Address %s is not pending. Dropping.", + escaped_safe_str(conn->base_.address)); + return; + } + + tor_assert(resolve->pending_connections); + assert_connection_ok(TO_CONN(conn),0); + + pend = resolve->pending_connections; + + if (pend->conn == conn) { + resolve->pending_connections = pend->next; + tor_free(pend); + log_debug(LD_EXIT, "First connection (fd "TOR_SOCKET_T_FORMAT") no " + "longer waiting for resolve of %s", + conn->base_.s, + escaped_safe_str(conn->base_.address)); + return; + } else { + for ( ; pend->next; pend = pend->next) { + if (pend->next->conn == conn) { + victim = pend->next; + pend->next = victim->next; + tor_free(victim); + log_debug(LD_EXIT, + "Connection (fd "TOR_SOCKET_T_FORMAT") no longer waiting " + "for resolve of %s", + conn->base_.s, escaped_safe_str(conn->base_.address)); + return; /* more are pending */ + } + } + log_warn(LD_BUG, "Connection (fd "TOR_SOCKET_T_FORMAT") was not waiting " + "for a resolve of %s, but we tried to remove it.", + conn->base_.s, escaped_safe_str(conn->base_.address)); + } +} + +/** Mark all connections waiting for <b>address</b> for close. Then cancel + * the resolve for <b>address</b> itself, and remove any cached results for + * <b>address</b> from the cache. + */ +MOCK_IMPL(void, +dns_cancel_pending_resolve,(const char *address)) +{ + pending_connection_t *pend; + cached_resolve_t search; + cached_resolve_t *resolve, *tmp; + edge_connection_t *pendconn; + circuit_t *circ; + + strlcpy(search.address, address, sizeof(search.address)); + + resolve = HT_FIND(cache_map, &cache_root, &search); + if (!resolve) + return; + + if (resolve->state != CACHE_STATE_PENDING) { + /* We can get into this state if we never actually created the pending + * resolve, due to finding an earlier cached error or something. Just + * ignore it. */ + if (resolve->pending_connections) { + log_warn(LD_BUG, + "Address %s is not pending but has pending connections!", + escaped_safe_str(address)); + tor_fragile_assert(); + } + return; + } + + if (!resolve->pending_connections) { + log_warn(LD_BUG, + "Address %s is pending but has no pending connections!", + escaped_safe_str(address)); + tor_fragile_assert(); + return; + } + tor_assert(resolve->pending_connections); + + /* mark all pending connections to fail */ + log_debug(LD_EXIT, + "Failing all connections waiting on DNS resolve of %s", + escaped_safe_str(address)); + while (resolve->pending_connections) { + pend = resolve->pending_connections; + pend->conn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + pendconn = pend->conn; + assert_connection_ok(TO_CONN(pendconn), 0); + tor_assert(!SOCKET_OK(pendconn->base_.s)); + if (!pendconn->base_.marked_for_close) { + connection_edge_end(pendconn, END_STREAM_REASON_RESOLVEFAILED); + } + circ = circuit_get_by_edge_conn(pendconn); + if (circ) + circuit_detach_stream(circ, pendconn); + if (!pendconn->base_.marked_for_close) + connection_free_(TO_CONN(pendconn)); + resolve->pending_connections = pend->next; + tor_free(pend); + } + + tmp = HT_REMOVE(cache_map, &cache_root, resolve); + if (tmp != resolve) { + log_err(LD_BUG, "The cancelled resolve we purged didn't match any in" + " the cache. Tried to purge %s (%p); instead got %s (%p).", + resolve->address, (void*)resolve, + tmp ? tmp->address : "NULL", (void*)tmp); + } + tor_assert(tmp == resolve); + + resolve->state = CACHE_STATE_DONE; +} + +/** Return true iff <b>address</b> is one of the addresses we use to verify + * that well-known sites aren't being hijacked by our DNS servers. */ +static inline int +is_test_address(const char *address) +{ + const or_options_t *options = get_options(); + return options->ServerDNSTestAddresses && + smartlist_contains_string_case(options->ServerDNSTestAddresses, address); +} + +/** Called on the OR side when the eventdns library tells us the outcome of a + * single DNS resolve: remember the answer, and tell all pending connections + * about the result of the lookup if the lookup is now done. (<b>address</b> + * is a NUL-terminated string containing the address to look up; + * <b>query_type</b> is one of DNS_{IPv4_A,IPv6_AAAA,PTR}; <b>dns_answer</b> + * is DNS_OK or one of DNS_ERR_*, <b>addr</b> is an IPv4 or IPv6 address if we + * got one; <b>hostname</b> is a hostname fora PTR request if we got one, and + * <b>ttl</b> is the time-to-live of this answer, in seconds.) + */ +static void +dns_found_answer(const char *address, uint8_t query_type, + int dns_answer, + const tor_addr_t *addr, + const char *hostname, uint32_t ttl) +{ + cached_resolve_t search; + cached_resolve_t *resolve; + + assert_cache_ok(); + + strlcpy(search.address, address, sizeof(search.address)); + + resolve = HT_FIND(cache_map, &cache_root, &search); + if (!resolve) { + int is_test_addr = is_test_address(address); + if (!is_test_addr) + log_info(LD_EXIT,"Resolved unasked address %s; ignoring.", + escaped_safe_str(address)); + return; + } + assert_resolve_ok(resolve); + + if (resolve->state != CACHE_STATE_PENDING) { + /* XXXX Maybe update addr? or check addr for consistency? Or let + * VALID replace FAILED? */ + int is_test_addr = is_test_address(address); + if (!is_test_addr) + log_notice(LD_EXIT, + "Resolved %s which was already resolved; ignoring", + escaped_safe_str(address)); + tor_assert(resolve->pending_connections == NULL); + return; + } + + cached_resolve_add_answer(resolve, query_type, dns_answer, + addr, hostname, ttl); + + if (cached_resolve_have_all_answers(resolve)) { + inform_pending_connections(resolve); + + make_pending_resolve_cached(resolve); + } +} + +/** Given a pending cached_resolve_t that we just finished resolving, + * inform every connection that was waiting for the outcome of that + * resolution. + * + * Do this by sending a RELAY_RESOLVED cell (if the pending stream had sent us + * RELAY_RESOLVE cell), or by launching an exit connection (if the pending + * stream had send us a RELAY_BEGIN cell). + */ +static void +inform_pending_connections(cached_resolve_t *resolve) +{ + pending_connection_t *pend; + edge_connection_t *pendconn; + int r; + + while (resolve->pending_connections) { + char *hostname = NULL; + pend = resolve->pending_connections; + pendconn = pend->conn; /* don't pass complex things to the + connection_mark_for_close macro */ + assert_connection_ok(TO_CONN(pendconn),time(NULL)); + + if (pendconn->base_.marked_for_close) { + /* prevent double-remove. */ + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + resolve->pending_connections = pend->next; + tor_free(pend); + continue; + } + + r = set_exitconn_info_from_resolve(pendconn, + resolve, + &hostname); + + if (r < 0) { + /* prevent double-remove. */ + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { + connection_edge_end(pendconn, END_STREAM_REASON_RESOLVEFAILED); + /* This detach must happen after we send the end cell. */ + circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); + } else { + send_resolved_cell(pendconn, r == -1 ? + RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT, + NULL); + /* This detach must happen after we send the resolved cell. */ + circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); + } + connection_free_(TO_CONN(pendconn)); + } else { + circuit_t *circ; + if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { + /* prevent double-remove. */ + pend->conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + circ = circuit_get_by_edge_conn(pend->conn); + tor_assert(circ); + tor_assert(!CIRCUIT_IS_ORIGIN(circ)); + /* unlink pend->conn from resolving_streams, */ + circuit_detach_stream(circ, pend->conn); + /* and link it to n_streams */ + pend->conn->next_stream = TO_OR_CIRCUIT(circ)->n_streams; + pend->conn->on_circuit = circ; + TO_OR_CIRCUIT(circ)->n_streams = pend->conn; + + connection_exit_connect(pend->conn); + } else { + /* prevent double-remove. This isn't really an accurate state, + * but it does the right thing. */ + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + if (pendconn->is_reverse_dns_lookup) + send_resolved_hostname_cell(pendconn, hostname); + else + send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO, resolve); + circ = circuit_get_by_edge_conn(pendconn); + tor_assert(circ); + circuit_detach_stream(circ, pendconn); + connection_free_(TO_CONN(pendconn)); + } + } + resolve->pending_connections = pend->next; + tor_free(pend); + tor_free(hostname); + } +} + +/** Remove a pending cached_resolve_t from the hashtable, and add a + * corresponding cached cached_resolve_t. + * + * This function is only necessary because of the perversity of our + * cache timeout code; see inline comment for ideas on eliminating it. + **/ +static void +make_pending_resolve_cached(cached_resolve_t *resolve) +{ + cached_resolve_t *removed; + + resolve->state = CACHE_STATE_DONE; + removed = HT_REMOVE(cache_map, &cache_root, resolve); + if (removed != resolve) { + log_err(LD_BUG, "The pending resolve we found wasn't removable from" + " the cache. Tried to purge %s (%p); instead got %s (%p).", + resolve->address, (void*)resolve, + removed ? removed->address : "NULL", (void*)removed); + } + assert_resolve_ok(resolve); + assert_cache_ok(); + /* The resolve will eventually just hit the time-out in the expiry queue and + * expire. See fd0bafb0dedc7e2 for a brief explanation of how this got that + * way. XXXXX we could do better!*/ + + { + cached_resolve_t *new_resolve = tor_memdup(resolve, + sizeof(cached_resolve_t)); + uint32_t ttl = UINT32_MAX; + new_resolve->expire = 0; /* So that set_expiry won't croak. */ + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) + new_resolve->result_ptr.hostname = + tor_strdup(resolve->result_ptr.hostname); + + new_resolve->state = CACHE_STATE_CACHED; + + assert_resolve_ok(new_resolve); + HT_INSERT(cache_map, &cache_root, new_resolve); + + if ((resolve->res_status_ipv4 == RES_STATUS_DONE_OK || + resolve->res_status_ipv4 == RES_STATUS_DONE_ERR) && + resolve->ttl_ipv4 < ttl) + ttl = resolve->ttl_ipv4; + + if ((resolve->res_status_ipv6 == RES_STATUS_DONE_OK || + resolve->res_status_ipv6 == RES_STATUS_DONE_ERR) && + resolve->ttl_ipv6 < ttl) + ttl = resolve->ttl_ipv6; + + if ((resolve->res_status_hostname == RES_STATUS_DONE_OK || + resolve->res_status_hostname == RES_STATUS_DONE_ERR) && + resolve->ttl_hostname < ttl) + ttl = resolve->ttl_hostname; + + set_expiry(new_resolve, time(NULL) + dns_clip_ttl(ttl)); + } + + assert_cache_ok(); +} + +/** Eventdns helper: return true iff the eventdns result <b>err</b> is + * a transient failure. */ +static int +evdns_err_is_transient(int err) +{ + switch (err) + { + case DNS_ERR_SERVERFAILED: + case DNS_ERR_TRUNCATED: + case DNS_ERR_TIMEOUT: + return 1; + default: + return 0; + } +} + +/** Configure eventdns nameservers if force is true, or if the configuration + * has changed since the last time we called this function, or if we failed on + * our last attempt. On Unix, this reads from /etc/resolv.conf or + * options->ServerDNSResolvConfFile; on Windows, this reads from + * options->ServerDNSResolvConfFile or the registry. Return 0 on success or + * -1 on failure. */ +static int +configure_nameservers(int force) +{ + const or_options_t *options; + const char *conf_fname; + struct stat st; + int r, flags; + options = get_options(); + conf_fname = options->ServerDNSResolvConfFile; +#ifndef _WIN32 + if (!conf_fname) + conf_fname = "/etc/resolv.conf"; +#endif + flags = DNS_OPTIONS_ALL; + + if (!the_evdns_base) { + if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) { + log_err(LD_BUG, "Couldn't create an evdns_base"); + return -1; + } + } + + evdns_set_log_fn(evdns_log_cb); + if (conf_fname) { + log_debug(LD_FS, "stat()ing %s", conf_fname); + if (stat(sandbox_intern_string(conf_fname), &st)) { + log_warn(LD_EXIT, "Unable to stat resolver configuration in '%s': %s", + conf_fname, strerror(errno)); + goto err; + } + if (!force && resolv_conf_fname && !strcmp(conf_fname,resolv_conf_fname) + && st.st_mtime == resolv_conf_mtime) { + log_info(LD_EXIT, "No change to '%s'", conf_fname); + return 0; + } + if (nameservers_configured) { + evdns_base_search_clear(the_evdns_base); + evdns_base_clear_nameservers_and_suspend(the_evdns_base); + } +#if defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) + if (flags & DNS_OPTION_HOSTSFILE) { + flags ^= DNS_OPTION_HOSTSFILE; + log_debug(LD_FS, "Loading /etc/hosts"); + evdns_base_load_hosts(the_evdns_base, + sandbox_intern_string("/etc/hosts")); + } +#endif /* defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) */ + log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); + if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, + sandbox_intern_string(conf_fname)))) { + log_warn(LD_EXIT, "Unable to parse '%s', or no nameservers in '%s' (%d)", + conf_fname, conf_fname, r); + goto err; + } + if (evdns_base_count_nameservers(the_evdns_base) == 0) { + log_warn(LD_EXIT, "Unable to find any nameservers in '%s'.", conf_fname); + goto err; + } + tor_free(resolv_conf_fname); + resolv_conf_fname = tor_strdup(conf_fname); + resolv_conf_mtime = st.st_mtime; + if (nameservers_configured) + evdns_base_resume(the_evdns_base); + } +#ifdef _WIN32 + else { + if (nameservers_configured) { + evdns_base_search_clear(the_evdns_base); + evdns_base_clear_nameservers_and_suspend(the_evdns_base); + } + if (evdns_base_config_windows_nameservers(the_evdns_base)) { + log_warn(LD_EXIT,"Could not config nameservers."); + goto err; + } + if (evdns_base_count_nameservers(the_evdns_base) == 0) { + log_warn(LD_EXIT, "Unable to find any platform nameservers in " + "your Windows configuration."); + goto err; + } + if (nameservers_configured) + evdns_base_resume(the_evdns_base); + tor_free(resolv_conf_fname); + resolv_conf_mtime = 0; + } +#endif /* defined(_WIN32) */ + +#define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v)) + + // If we only have one nameserver, it does not make sense to back off + // from it for a timeout. Unfortunately, the value for max-timeouts is + // currently clamped by libevent to 255, but it does not hurt to set + // it higher in case libevent gets a patch for this. Higher-than- + // default maximum of 3 with multiple nameservers to avoid spuriously + // marking one down on bursts of timeouts resulting from scans/attacks + // against non-responding authoritative DNS servers. + if (evdns_base_count_nameservers(the_evdns_base) == 1) { + SET("max-timeouts:", "1000000"); + } else { + SET("max-timeouts:", "10"); + } + + // Elongate the queue of maximum inflight dns requests, so if a bunch + // remain pending at the resolver (happens commonly with Unbound) we won't + // stall every other DNS request. This potentially means some wasted + // CPU as there's a walk over a linear queue involved, but this is a + // much better tradeoff compared to just failing DNS requests because + // of a full queue. + SET("max-inflight:", "8192"); + + // Two retries at 5 and 10 seconds for bind9/named which relies on + // clients to handle retries. Second retry for retried circuits with + // extended 15 second timeout. Superfluous with local-system Unbound + // instance--has its own elaborate retry scheme. + SET("timeout:", "5"); + SET("attempts:","3"); + + if (options->ServerDNSRandomizeCase) + SET("randomize-case:", "1"); + else + SET("randomize-case:", "0"); + +#undef SET + + dns_servers_relaunch_checks(); + + nameservers_configured = 1; + if (nameserver_config_failed) { + nameserver_config_failed = 0; + /* XXX the three calls to republish the descriptor might be producing + * descriptors that are only cosmetically different, especially on + * non-exit relays! -RD */ + mark_my_descriptor_dirty("dns resolvers back"); + } + return 0; + err: + nameservers_configured = 0; + if (! nameserver_config_failed) { + nameserver_config_failed = 1; + mark_my_descriptor_dirty("dns resolvers failed"); + } + return -1; +} + +/** For eventdns: Called when we get an answer for a request we launched. + * See eventdns.h for arguments; 'arg' holds the address we tried to resolve. + */ +static void +evdns_callback(int result, char type, int count, int ttl, void *addresses, + void *arg) +{ + char *arg_ = arg; + uint8_t orig_query_type = arg_[0]; + char *string_address = arg_ + 1; + tor_addr_t addr; + const char *hostname = NULL; + int was_wildcarded = 0; + + tor_addr_make_unspec(&addr); + + /* Keep track of whether IPv6 is working */ + if (type == DNS_IPv6_AAAA) { + if (result == DNS_ERR_TIMEOUT) { + ++n_ipv6_timeouts; + } + + if (n_ipv6_timeouts > 10 && + n_ipv6_timeouts > n_ipv6_requests_made / 2) { + if (! dns_is_broken_for_ipv6) { + log_notice(LD_EXIT, "More than half of our IPv6 requests seem to " + "have timed out. I'm going to assume I can't get AAAA " + "responses."); + dns_is_broken_for_ipv6 = 1; + } + } + } + + if (result == DNS_ERR_NONE) { + if (type == DNS_IPv4_A && count) { + char answer_buf[INET_NTOA_BUF_LEN+1]; + char *escaped_address; + uint32_t *addrs = addresses; + tor_addr_from_ipv4n(&addr, addrs[0]); + + tor_addr_to_str(answer_buf, &addr, sizeof(answer_buf), 0); + escaped_address = esc_for_log(string_address); + + if (answer_is_wildcarded(answer_buf)) { + log_debug(LD_EXIT, "eventdns said that %s resolves to ISP-hijacked " + "address %s; treating as a failure.", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + was_wildcarded = 1; + tor_addr_make_unspec(&addr); + result = DNS_ERR_NOTEXIST; + } else { + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + } + tor_free(escaped_address); + } else if (type == DNS_IPv6_AAAA && count) { + char answer_buf[TOR_ADDR_BUF_LEN]; + char *escaped_address; + struct in6_addr *addrs = addresses; + tor_addr_from_in6(&addr, &addrs[0]); + tor_inet_ntop(AF_INET6, &addrs[0], answer_buf, sizeof(answer_buf)); + escaped_address = esc_for_log(string_address); + + if (answer_is_wildcarded(answer_buf)) { + log_debug(LD_EXIT, "eventdns said that %s resolves to ISP-hijacked " + "address %s; treating as a failure.", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + was_wildcarded = 1; + tor_addr_make_unspec(&addr); + result = DNS_ERR_NOTEXIST; + } else { + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + } + tor_free(escaped_address); + } else if (type == DNS_PTR && count) { + char *escaped_address; + hostname = ((char**)addresses)[0]; + escaped_address = esc_for_log(string_address); + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + safe_str(escaped_address), + escaped_safe_str(hostname)); + tor_free(escaped_address); + } else if (count) { + log_info(LD_EXIT, "eventdns returned only unrecognized answer types " + " for %s.", + escaped_safe_str(string_address)); + } else { + log_info(LD_EXIT, "eventdns returned no addresses or error for %s.", + escaped_safe_str(string_address)); + } + } + if (was_wildcarded) { + if (is_test_address(string_address)) { + /* Ick. We're getting redirected on known-good addresses. Our DNS + * server must really hate us. */ + add_wildcarded_test_address(string_address); + } + } + + if (orig_query_type && type && orig_query_type != type) { + log_warn(LD_BUG, "Weird; orig_query_type == %d but type == %d", + (int)orig_query_type, (int)type); + } + if (result != DNS_ERR_SHUTDOWN) + dns_found_answer(string_address, orig_query_type, + result, &addr, hostname, ttl); + + tor_free(arg_); +} + +/** Start a single DNS resolve for <b>address</b> (if <b>query_type</b> is + * DNS_IPv4_A or DNS_IPv6_AAAA) <b>ptr_address</b> (if <b>query_type</b> is + * DNS_PTR). Return 0 if we launched the request, -1 otherwise. */ +static int +launch_one_resolve(const char *address, uint8_t query_type, + const tor_addr_t *ptr_address) +{ + const int options = get_options()->ServerDNSSearchDomains ? 0 + : DNS_QUERY_NO_SEARCH; + const size_t addr_len = strlen(address); + struct evdns_request *req = 0; + char *addr = tor_malloc(addr_len + 2); + addr[0] = (char) query_type; + memcpy(addr+1, address, addr_len + 1); + + switch (query_type) { + case DNS_IPv4_A: + req = evdns_base_resolve_ipv4(the_evdns_base, + address, options, evdns_callback, addr); + break; + case DNS_IPv6_AAAA: + req = evdns_base_resolve_ipv6(the_evdns_base, + address, options, evdns_callback, addr); + ++n_ipv6_requests_made; + break; + case DNS_PTR: + if (tor_addr_family(ptr_address) == AF_INET) + req = evdns_base_resolve_reverse(the_evdns_base, + tor_addr_to_in(ptr_address), + DNS_QUERY_NO_SEARCH, + evdns_callback, addr); + else if (tor_addr_family(ptr_address) == AF_INET6) + req = evdns_base_resolve_reverse_ipv6(the_evdns_base, + tor_addr_to_in6(ptr_address), + DNS_QUERY_NO_SEARCH, + evdns_callback, addr); + else + log_warn(LD_BUG, "Called with PTR query and unexpected address family"); + break; + default: + log_warn(LD_BUG, "Called with unexpectd query type %d", (int)query_type); + break; + } + + if (req) { + return 0; + } else { + tor_free(addr); + return -1; + } +} + +/** For eventdns: start resolving as necessary to find the target for + * <b>exitconn</b>. Returns -1 on error, -2 on transient error, + * 0 on "resolve launched." */ +MOCK_IMPL(STATIC int, +launch_resolve,(cached_resolve_t *resolve)) +{ + tor_addr_t a; + int r; + + if (net_is_disabled()) + return -1; + + /* What? Nameservers not configured? Sounds like a bug. */ + if (!nameservers_configured) { + log_warn(LD_EXIT, "(Harmless.) Nameservers not configured, but resolve " + "launched. Configuring."); + if (configure_nameservers(1) < 0) { + return -1; + } + } + + r = tor_addr_parse_PTR_name( + &a, resolve->address, AF_UNSPEC, 0); + + tor_assert(the_evdns_base); + if (r == 0) { + log_info(LD_EXIT, "Launching eventdns request for %s", + escaped_safe_str(resolve->address)); + resolve->res_status_ipv4 = RES_STATUS_INFLIGHT; + if (get_options()->IPv6Exit) + resolve->res_status_ipv6 = RES_STATUS_INFLIGHT; + + if (launch_one_resolve(resolve->address, DNS_IPv4_A, NULL) < 0) { + resolve->res_status_ipv4 = 0; + r = -1; + } + + if (r==0 && get_options()->IPv6Exit) { + /* We ask for an IPv6 address for *everything*. */ + if (launch_one_resolve(resolve->address, DNS_IPv6_AAAA, NULL) < 0) { + resolve->res_status_ipv6 = 0; + r = -1; + } + } + } else if (r == 1) { + r = 0; + log_info(LD_EXIT, "Launching eventdns reverse request for %s", + escaped_safe_str(resolve->address)); + resolve->res_status_hostname = RES_STATUS_INFLIGHT; + if (launch_one_resolve(resolve->address, DNS_PTR, &a) < 0) { + resolve->res_status_hostname = 0; + r = -1; + } + } else if (r == -1) { + log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here."); + } + + if (r < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_EXIT, "eventdns rejected address %s.", + escaped_safe_str(resolve->address)); + } + return r; +} + +/** How many requests for bogus addresses have we launched so far? */ +static int n_wildcard_requests = 0; + +/** Map from dotted-quad IP address in response to an int holding how many + * times we've seen it for a randomly generated (hopefully bogus) address. It + * would be easier to use definitely-invalid addresses (as specified by + * RFC2606), but see comment in dns_launch_wildcard_checks(). */ +static strmap_t *dns_wildcard_response_count = NULL; + +/** If present, a list of dotted-quad IP addresses that we are pretty sure our + * nameserver wants to return in response to requests for nonexistent domains. + */ +static smartlist_t *dns_wildcard_list = NULL; +/** True iff we've logged about a single address getting wildcarded. + * Subsequent warnings will be less severe. */ +static int dns_wildcard_one_notice_given = 0; +/** True iff we've warned that our DNS server is wildcarding too many failures. + */ +static int dns_wildcard_notice_given = 0; + +/** List of supposedly good addresses that are getting wildcarded to the + * same addresses as nonexistent addresses. */ +static smartlist_t *dns_wildcarded_test_address_list = NULL; +/** True iff we've warned about a test address getting wildcarded */ +static int dns_wildcarded_test_address_notice_given = 0; +/** True iff all addresses seem to be getting wildcarded. */ +static int dns_is_completely_invalid = 0; + +/** Called when we see <b>id</b> (a dotted quad or IPv6 address) in response + * to a request for a hopefully bogus address. */ +static void +wildcard_increment_answer(const char *id) +{ + int *ip; + if (!dns_wildcard_response_count) + dns_wildcard_response_count = strmap_new(); + + ip = strmap_get(dns_wildcard_response_count, id); // may be null (0) + if (!ip) { + ip = tor_malloc_zero(sizeof(int)); + strmap_set(dns_wildcard_response_count, id, ip); + } + ++*ip; + + if (*ip > 5 && n_wildcard_requests > 10) { + if (!dns_wildcard_list) dns_wildcard_list = smartlist_new(); + if (!smartlist_contains_string(dns_wildcard_list, id)) { + tor_log(dns_wildcard_notice_given ? LOG_INFO : LOG_NOTICE, LD_EXIT, + "Your DNS provider has given \"%s\" as an answer for %d different " + "invalid addresses. Apparently they are hijacking DNS failures. " + "I'll try to correct for this by treating future occurrences of " + "\"%s\" as 'not found'.", id, *ip, id); + smartlist_add_strdup(dns_wildcard_list, id); + } + if (!dns_wildcard_notice_given) + control_event_server_status(LOG_NOTICE, "DNS_HIJACKED"); + dns_wildcard_notice_given = 1; + } +} + +/** Note that a single test address (one believed to be good) seems to be + * getting redirected to the same IP as failures are. */ +static void +add_wildcarded_test_address(const char *address) +{ + int n, n_test_addrs; + if (!dns_wildcarded_test_address_list) + dns_wildcarded_test_address_list = smartlist_new(); + + if (smartlist_contains_string_case(dns_wildcarded_test_address_list, + address)) + return; + + n_test_addrs = get_options()->ServerDNSTestAddresses ? + smartlist_len(get_options()->ServerDNSTestAddresses) : 0; + + smartlist_add_strdup(dns_wildcarded_test_address_list, address); + n = smartlist_len(dns_wildcarded_test_address_list); + if (n > n_test_addrs/2) { + tor_log(dns_wildcarded_test_address_notice_given ? LOG_INFO : LOG_NOTICE, + LD_EXIT, "Your DNS provider tried to redirect \"%s\" to a junk " + "address. It has done this with %d test addresses so far. I'm " + "going to stop being an exit node for now, since our DNS seems so " + "broken.", address, n); + if (!dns_is_completely_invalid) { + dns_is_completely_invalid = 1; + mark_my_descriptor_dirty("dns hijacking confirmed"); + } + if (!dns_wildcarded_test_address_notice_given) + control_event_server_status(LOG_WARN, "DNS_USELESS"); + dns_wildcarded_test_address_notice_given = 1; + } +} + +/** Callback function when we get an answer (possibly failing) for a request + * for a (hopefully) nonexistent domain. */ +static void +evdns_wildcard_check_callback(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + (void)ttl; + ++n_wildcard_requests; + if (result == DNS_ERR_NONE && count) { + char *string_address = arg; + int i; + if (type == DNS_IPv4_A) { + const uint32_t *addrs = addresses; + for (i = 0; i < count; ++i) { + char answer_buf[INET_NTOA_BUF_LEN+1]; + struct in_addr in; + in.s_addr = addrs[i]; + tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); + wildcard_increment_answer(answer_buf); + } + } else if (type == DNS_IPv6_AAAA) { + const struct in6_addr *addrs = addresses; + for (i = 0; i < count; ++i) { + char answer_buf[TOR_ADDR_BUF_LEN+1]; + tor_inet_ntop(AF_INET6, &addrs[i], answer_buf, sizeof(answer_buf)); + wildcard_increment_answer(answer_buf); + } + } + + tor_log(dns_wildcard_one_notice_given ? LOG_INFO : LOG_NOTICE, LD_EXIT, + "Your DNS provider gave an answer for \"%s\", which " + "is not supposed to exist. Apparently they are hijacking " + "DNS failures. Trying to correct for this. We've noticed %d " + "possibly bad address%s so far.", + string_address, strmap_size(dns_wildcard_response_count), + (strmap_size(dns_wildcard_response_count) == 1) ? "" : "es"); + dns_wildcard_one_notice_given = 1; + } + tor_free(arg); +} + +/** Launch a single request for a nonexistent hostname consisting of between + * <b>min_len</b> and <b>max_len</b> random (plausible) characters followed by + * <b>suffix</b> */ +static void +launch_wildcard_check(int min_len, int max_len, int is_ipv6, + const char *suffix) +{ + char *addr; + struct evdns_request *req; + + addr = crypto_random_hostname(min_len, max_len, "", suffix); + log_info(LD_EXIT, "Testing whether our DNS server is hijacking nonexistent " + "domains with request for bogus hostname \"%s\"", addr); + + tor_assert(the_evdns_base); + if (is_ipv6) + req = evdns_base_resolve_ipv6( + the_evdns_base, + /* This "addr" tells us which address to resolve */ + addr, + DNS_QUERY_NO_SEARCH, evdns_wildcard_check_callback, + /* This "addr" is an argument to the callback*/ addr); + else + req = evdns_base_resolve_ipv4( + the_evdns_base, + /* This "addr" tells us which address to resolve */ + addr, + DNS_QUERY_NO_SEARCH, evdns_wildcard_check_callback, + /* This "addr" is an argument to the callback*/ addr); + if (!req) { + /* There is no evdns request in progress; stop addr from getting leaked */ + tor_free(addr); + } +} + +/** Launch attempts to resolve a bunch of known-good addresses (configured in + * ServerDNSTestAddresses). [Callback for a libevent timer] */ +static void +launch_test_addresses(evutil_socket_t fd, short event, void *args) +{ + const or_options_t *options = get_options(); + (void)fd; + (void)event; + (void)args; + + if (net_is_disabled()) + return; + + log_info(LD_EXIT, "Launching checks to see whether our nameservers like to " + "hijack *everything*."); + /* This situation is worse than the failure-hijacking situation. When this + * happens, we're no good for DNS requests at all, and we shouldn't really + * be an exit server.*/ + if (options->ServerDNSTestAddresses) { + + tor_assert(the_evdns_base); + SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses, + const char *, address) { + if (launch_one_resolve(address, DNS_IPv4_A, NULL) < 0) { + log_info(LD_EXIT, "eventdns rejected test address %s", + escaped_safe_str(address)); + } + + if (launch_one_resolve(address, DNS_IPv6_AAAA, NULL) < 0) { + log_info(LD_EXIT, "eventdns rejected test address %s", + escaped_safe_str(address)); + } + } SMARTLIST_FOREACH_END(address); + } +} + +#define N_WILDCARD_CHECKS 2 + +/** Launch DNS requests for a few nonexistent hostnames and a few well-known + * hostnames, and see if we can catch our nameserver trying to hijack them and + * map them to a stupid "I couldn't find ggoogle.com but maybe you'd like to + * buy these lovely encyclopedias" page. */ +static void +dns_launch_wildcard_checks(void) +{ + int i, ipv6; + log_info(LD_EXIT, "Launching checks to see whether our nameservers like " + "to hijack DNS failures."); + for (ipv6 = 0; ipv6 <= 1; ++ipv6) { + for (i = 0; i < N_WILDCARD_CHECKS; ++i) { + /* RFC2606 reserves these. Sadly, some DNS hijackers, in a silly + * attempt to 'comply' with rfc2606, refrain from giving A records for + * these. This is the standards-compliance equivalent of making sure + * that your crackhouse's elevator inspection certificate is up to date. + */ + launch_wildcard_check(2, 16, ipv6, ".invalid"); + launch_wildcard_check(2, 16, ipv6, ".test"); + + /* These will break specs if there are ever any number of + * 8+-character top-level domains. */ + launch_wildcard_check(8, 16, ipv6, ""); + + /* Try some random .com/org/net domains. This will work fine so long as + * not too many resolve to the same place. */ + launch_wildcard_check(8, 16, ipv6, ".com"); + launch_wildcard_check(8, 16, ipv6, ".org"); + launch_wildcard_check(8, 16, ipv6, ".net"); + } + } +} + +/** If appropriate, start testing whether our DNS servers tend to lie to + * us. */ +void +dns_launch_correctness_checks(void) +{ + static struct event *launch_event = NULL; + struct timeval timeout; + if (!get_options()->ServerDNSDetectHijacking) + return; + dns_launch_wildcard_checks(); + + /* Wait a while before launching requests for test addresses, so we can + * get the results from checking for wildcarding. */ + if (! launch_event) + launch_event = tor_evtimer_new(tor_libevent_get_base(), + launch_test_addresses, NULL); + timeout.tv_sec = 30; + timeout.tv_usec = 0; + if (evtimer_add(launch_event, &timeout)<0) { + log_warn(LD_BUG, "Couldn't add timer for checking for dns hijacking"); + } +} + +/** Return true iff our DNS servers lie to us too much to be trusted. */ +int +dns_seems_to_be_broken(void) +{ + return dns_is_completely_invalid; +} + +/** Return true iff we think that IPv6 hostname lookup is broken */ +int +dns_seems_to_be_broken_for_ipv6(void) +{ + return dns_is_broken_for_ipv6; +} + +/** Forget what we've previously learned about our DNS servers' correctness. */ +void +dns_reset_correctness_checks(void) +{ + strmap_free(dns_wildcard_response_count, tor_free_); + dns_wildcard_response_count = NULL; + + n_wildcard_requests = 0; + + n_ipv6_requests_made = n_ipv6_timeouts = 0; + + if (dns_wildcard_list) { + SMARTLIST_FOREACH(dns_wildcard_list, char *, cp, tor_free(cp)); + smartlist_clear(dns_wildcard_list); + } + if (dns_wildcarded_test_address_list) { + SMARTLIST_FOREACH(dns_wildcarded_test_address_list, char *, cp, + tor_free(cp)); + smartlist_clear(dns_wildcarded_test_address_list); + } + dns_wildcard_one_notice_given = dns_wildcard_notice_given = + dns_wildcarded_test_address_notice_given = dns_is_completely_invalid = + dns_is_broken_for_ipv6 = 0; +} + +/** Return true iff we have noticed that the dotted-quad <b>ip</b> has been + * returned in response to requests for nonexistent hostnames. */ +static int +answer_is_wildcarded(const char *ip) +{ + return dns_wildcard_list && smartlist_contains_string(dns_wildcard_list, ip); +} + +/** Exit with an assertion if <b>resolve</b> is corrupt. */ +static void +assert_resolve_ok(cached_resolve_t *resolve) +{ + tor_assert(resolve); + tor_assert(resolve->magic == CACHED_RESOLVE_MAGIC); + tor_assert(strlen(resolve->address) < MAX_ADDRESSLEN); + tor_assert(tor_strisnonupper(resolve->address)); + if (resolve->state != CACHE_STATE_PENDING) { + tor_assert(!resolve->pending_connections); + } + if (resolve->state == CACHE_STATE_PENDING || + resolve->state == CACHE_STATE_DONE) { +#if 0 + tor_assert(!resolve->ttl); + if (resolve->is_reverse) + tor_assert(!resolve->hostname); + else + tor_assert(!resolve->result_ipv4.addr_ipv4); +#endif /* 0 */ + /*XXXXX ADD MORE */ + } +} + +/** Return the number of DNS cache entries as an int */ +static int +dns_cache_entry_count(void) +{ + return HT_SIZE(&cache_root); +} + +/** Log memory information about our internal DNS cache at level 'severity'. */ +void +dump_dns_mem_usage(int severity) +{ + /* This should never be larger than INT_MAX. */ + int hash_count = dns_cache_entry_count(); + size_t hash_mem = sizeof(struct cached_resolve_t) * hash_count; + hash_mem += HT_MEM_USAGE(&cache_root); + + /* Print out the count and estimated size of our &cache_root. It undercounts + hostnames in cached reverse resolves. + */ + tor_log(severity, LD_MM, "Our DNS cache has %d entries.", hash_count); + tor_log(severity, LD_MM, "Our DNS cache size is approximately %u bytes.", + (unsigned)hash_mem); +} + +#ifdef DEBUG_DNS_CACHE +/** Exit with an assertion if the DNS cache is corrupt. */ +static void +assert_cache_ok_(void) +{ + cached_resolve_t **resolve; + int bad_rep = HT_REP_IS_BAD_(cache_map, &cache_root); + if (bad_rep) { + log_err(LD_BUG, "Bad rep type %d on dns cache hash table", bad_rep); + tor_assert(!bad_rep); + } + + HT_FOREACH(resolve, cache_map, &cache_root) { + assert_resolve_ok(*resolve); + tor_assert((*resolve)->state != CACHE_STATE_DONE); + } + if (!cached_resolve_pqueue) + return; + + smartlist_pqueue_assert_ok(cached_resolve_pqueue, + compare_cached_resolves_by_expiry_, + offsetof(cached_resolve_t, minheap_idx)); + + SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, + { + if (res->state == CACHE_STATE_DONE) { + cached_resolve_t *found = HT_FIND(cache_map, &cache_root, res); + tor_assert(!found || found != res); + } else { + cached_resolve_t *found = HT_FIND(cache_map, &cache_root, res); + tor_assert(found); + } + }); +} + +#endif /* defined(DEBUG_DNS_CACHE) */ + +cached_resolve_t * +dns_get_cache_entry(cached_resolve_t *query) +{ + return HT_FIND(cache_map, &cache_root, query); +} + +void +dns_insert_cache_entry(cached_resolve_t *new_entry) +{ + HT_INSERT(cache_map, &cache_root, new_entry); +} diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h new file mode 100644 index 0000000000..12853205ff --- /dev/null +++ b/src/feature/relay/dns.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dns.h + * \brief Header file for dns.c. + **/ + +#ifndef TOR_DNS_H +#define TOR_DNS_H + +/** Lowest value for DNS ttl that a server will give. */ +#define MIN_DNS_TTL_AT_EXIT (5*60) +/** Highest value for DNS ttl that a server will give. */ +#define MAX_DNS_TTL_AT_EXIT (60*60) + +/** How long do we keep DNS cache entries before purging them (regardless of + * their TTL)? */ +#define MAX_DNS_ENTRY_AGE (3*60*60) +/** How long do we cache/tell clients to cache DNS records when no TTL is + * known? */ +#define DEFAULT_DNS_TTL (30*60) + +int dns_init(void); +int has_dns_init_failed(void); +void dns_free_all(void); +uint32_t dns_clip_ttl(uint32_t ttl); +int dns_reset(void); +void connection_dns_remove(edge_connection_t *conn); +void assert_connection_edge_not_dns_pending(edge_connection_t *conn); +void assert_all_pending_dns_resolves_ok(void); +MOCK_DECL(void,dns_cancel_pending_resolve,(const char *question)); +int dns_resolve(edge_connection_t *exitconn); +void dns_launch_correctness_checks(void); +int dns_seems_to_be_broken(void); +int dns_seems_to_be_broken_for_ipv6(void); +void dns_reset_correctness_checks(void); +void dump_dns_mem_usage(int severity); + +#ifdef DNS_PRIVATE +#include "or/dns_structs.h" + +MOCK_DECL(STATIC int,dns_resolve_impl,(edge_connection_t *exitconn, +int is_resolve,or_circuit_t *oncirc, char **hostname_out, +int *made_connection_pending_out, cached_resolve_t **resolve_out)); + +MOCK_DECL(STATIC void,send_resolved_cell,(edge_connection_t *conn, +uint8_t answer_type,const cached_resolve_t *resolved)); + +MOCK_DECL(STATIC void,send_resolved_hostname_cell,(edge_connection_t *conn, +const char *hostname)); + +cached_resolve_t *dns_get_cache_entry(cached_resolve_t *query); +void dns_insert_cache_entry(cached_resolve_t *new_entry); + +MOCK_DECL(STATIC int, +set_exitconn_info_from_resolve,(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out)); + +MOCK_DECL(STATIC int, +launch_resolve,(cached_resolve_t *resolve)); + +#endif /* defined(DNS_PRIVATE) */ + +#endif /* !defined(TOR_DNS_H) */ + diff --git a/src/feature/relay/dns_structs.h b/src/feature/relay/dns_structs.h new file mode 100644 index 0000000000..28c48ca0bc --- /dev/null +++ b/src/feature/relay/dns_structs.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2003-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dns_structs.h + * + * \brief Structures used in dns.c. Exposed to dns.c, and to the unit tests + * that declare DNS_PRIVATE. + */ + +#ifndef TOR_DNS_STRUCTS_H +#define TOR_DNS_STRUCTS_H + +/** Longest hostname we're willing to resolve. */ +#define MAX_ADDRESSLEN 256 + +/** Linked list of connections waiting for a DNS answer. */ +typedef struct pending_connection_t { + edge_connection_t *conn; + struct pending_connection_t *next; +} pending_connection_t; + +/** Value of 'magic' field for cached_resolve_t. Used to try to catch bad + * pointers and memory stomping. */ +#define CACHED_RESOLVE_MAGIC 0x1234F00D + +/* Possible states for a cached resolve_t */ +/** We are waiting for the resolver system to tell us an answer here. + * When we get one, or when we time out, the state of this cached_resolve_t + * will become "DONE" and we'll possibly add a CACHED + * entry. This cached_resolve_t will be in the hash table so that we will + * know not to launch more requests for this addr, but rather to add more + * connections to the pending list for the addr. */ +#define CACHE_STATE_PENDING 0 +/** This used to be a pending cached_resolve_t, and we got an answer for it. + * Now we're waiting for this cached_resolve_t to expire. This should + * have no pending connections, and should not appear in the hash table. */ +#define CACHE_STATE_DONE 1 +/** We are caching an answer for this address. This should have no pending + * connections, and should appear in the hash table. */ +#define CACHE_STATE_CACHED 2 + +/** @name status values for a single DNS request. + * + * @{ */ +/** The DNS request is in progress. */ +#define RES_STATUS_INFLIGHT 1 +/** The DNS request finished and gave an answer */ +#define RES_STATUS_DONE_OK 2 +/** The DNS request finished and gave an error */ +#define RES_STATUS_DONE_ERR 3 +/**@}*/ + +/** A DNS request: possibly completed, possibly pending; cached_resolve + * structs are stored at the OR side in a hash table, and as a linked + * list from oldest to newest. + */ +typedef struct cached_resolve_t { + HT_ENTRY(cached_resolve_t) node; + uint32_t magic; /**< Must be CACHED_RESOLVE_MAGIC */ + char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */ + + union { + uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>, if successful. + * (In host order.) */ + int err_ipv4; /**< One of DNS_ERR_*, if IPv4 lookup failed. */ + } result_ipv4; /**< Outcome of IPv4 lookup */ + union { + struct in6_addr addr_ipv6; /**< IPv6 addr for <b>address</b>, if + * successful */ + int err_ipv6; /**< One of DNS_ERR_*, if IPv6 lookup failed. */ + } result_ipv6; /**< Outcome of IPv6 lookup, if any */ + union { + char *hostname; /** A hostname, if PTR lookup happened successfully*/ + int err_hostname; /** One of DNS_ERR_*, if PTR lookup failed. */ + } result_ptr; + /** @name Status fields + * + * These take one of the RES_STATUS_* values, depending on the state + * of the corresponding lookup. + * + * @{ */ + unsigned int res_status_ipv4 : 2; + unsigned int res_status_ipv6 : 2; + unsigned int res_status_hostname : 2; + /**@}*/ + uint8_t state; /**< Is this cached entry pending/done/informative? */ + + time_t expire; /**< Remove items from cache after this time. */ + uint32_t ttl_ipv4; /**< What TTL did the nameserver tell us? */ + uint32_t ttl_ipv6; /**< What TTL did the nameserver tell us? */ + uint32_t ttl_hostname; /**< What TTL did the nameserver tell us? */ + /** Connections that want to know when we get an answer for this resolve. */ + pending_connection_t *pending_connections; + /** Position of this element in the heap*/ + int minheap_idx; +} cached_resolve_t; + +#endif /* !defined(TOR_DNS_STRUCTS_H) */ + diff --git a/src/feature/relay/ext_orport.c b/src/feature/relay/ext_orport.c new file mode 100644 index 0000000000..7342a66e06 --- /dev/null +++ b/src/feature/relay/ext_orport.c @@ -0,0 +1,662 @@ +/* Copyright (c) 2012-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ext_orport.c + * \brief Code implementing the Extended ORPort. + * + * The Extended ORPort interface is used by pluggable transports to + * communicate additional information to a Tor bridge, including + * address information. For more information on this interface, + * see pt-spec.txt in torspec.git. + * + * There is no separate structure for extended ORPort connections; they use + * or_connection_t objects, and share most of their implementation with + * connection_or.c. Once the handshake is done, an extended ORPort connection + * turns into a regular OR connection, using connection_ext_or_transition(). + */ + +#define EXT_ORPORT_PRIVATE +#include "or/or.h" +#include "or/connection.h" +#include "or/connection_or.h" +#include "or/control.h" +#include "or/config.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "or/ext_orport.h" +#include "or/main.h" +#include "or/proto_ext_or.h" + +#include "or/or_connection_st.h" + +/** Allocate and return a structure capable of holding an Extended + * ORPort message of body length <b>len</b>. */ +ext_or_cmd_t * +ext_or_cmd_new(uint16_t len) +{ + size_t size = offsetof(ext_or_cmd_t, body) + len; + ext_or_cmd_t *cmd = tor_malloc(size); + cmd->len = len; + return cmd; +} + +/** Deallocate the Extended ORPort message in <b>cmd</b>. */ +void +ext_or_cmd_free_(ext_or_cmd_t *cmd) +{ + tor_free(cmd); +} + +/** Get an Extended ORPort message from <b>conn</b>, and place it in + * <b>out</b>. Return -1 on fail, 0 if we need more data, and 1 if we + * successfully extracted an Extended ORPort command from the + * buffer. */ +static int +connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) +{ + return fetch_ext_or_command_from_buf(conn->inbuf, out); +} + +/** Write an Extended ORPort message to <b>conn</b>. Use + * <b>command</b> as the command type, <b>bodylen</b> as the body + * length, and <b>body</b>, if it's present, as the body of the + * message. */ +STATIC int +connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen) +{ + char header[4]; + if (bodylen > UINT16_MAX) + return -1; + set_uint16(header, htons(command)); + set_uint16(header+2, htons(bodylen)); + connection_buf_add(header, 4, conn); + if (bodylen) { + tor_assert(body); + connection_buf_add(body, bodylen, conn); + } + return 0; +} + +/** Transition from an Extended ORPort which accepts Extended ORPort + * messages, to an Extended ORport which accepts OR traffic. */ +static void +connection_ext_or_transition(or_connection_t *conn) +{ + tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); + + conn->base_.type = CONN_TYPE_OR; + TO_CONN(conn)->state = 0; // set the state to a neutral value + control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); + connection_tls_start_handshake(conn, 1); +} + +/** Length of authentication cookie. */ +#define EXT_OR_PORT_AUTH_COOKIE_LEN 32 +/** Length of the header of the cookie file. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 +/** Static cookie file header. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" +/** Length of safe-cookie protocol hashes. */ +#define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN +/** Length of safe-cookie protocol nonces. */ +#define EXT_OR_PORT_AUTH_NONCE_LEN 32 +/** Safe-cookie protocol constants. */ +#define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ + "ExtORPort authentication server-to-client hash" +#define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ + "ExtORPort authentication client-to-server hash" + +/* Code to indicate cookie authentication */ +#define EXT_OR_AUTHTYPE_SAFECOOKIE 0x01 + +/** If true, we've set ext_or_auth_cookie to a secret code and stored + * it to disk. */ +STATIC int ext_or_auth_cookie_is_set = 0; +/** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk + * and which we're using to authenticate controllers. (If the controller can + * read it off disk, it has permission to connect.) */ +STATIC uint8_t *ext_or_auth_cookie = NULL; + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_ext_or_auth_cookie_file_name(void) +{ + const or_options_t *options = get_options(); + if (options->ExtORPortCookieAuthFile && + strlen(options->ExtORPortCookieAuthFile)) { + return tor_strdup(options->ExtORPortCookieAuthFile); + } else { + return get_datadir_fname("extended_orport_auth_cookie"); + } +} + +/* Initialize the cookie-based authentication system of the + * Extended ORPort. If <b>is_enabled</b> is 0, then disable the cookie + * authentication system. */ +int +init_ext_or_cookie_authentication(int is_enabled) +{ + char *fname = NULL; + int retval; + + if (!is_enabled) { + ext_or_auth_cookie_is_set = 0; + return 0; + } + + fname = get_ext_or_auth_cookie_file_name(); + retval = init_cookie_authentication(fname, EXT_OR_PORT_AUTH_COOKIE_HEADER, + EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, + get_options()->ExtORPortCookieAuthFileGroupReadable, + &ext_or_auth_cookie, + &ext_or_auth_cookie_is_set); + tor_free(fname); + return retval; +} + +/** Read data from <b>conn</b> and see if the client sent us the + * authentication type that they prefer to use in this session. + * + * Return -1 if we received corrupted data or if we don't support the + * authentication type. Return 0 if we need more data in + * <b>conn</b>. Return 1 if the authentication type negotiation was + * successful. */ +static int +connection_ext_or_auth_neg_auth_type(connection_t *conn) +{ + char authtype[1] = {0}; + + if (connection_get_inbuf_len(conn) < 1) + return 0; + + if (connection_buf_get_bytes(authtype, 1, conn) < 0) + return -1; + + log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); + if (authtype[0] != EXT_OR_AUTHTYPE_SAFECOOKIE) { + /* '1' is the only auth type supported atm */ + return -1; + } + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; + return 1; +} + +/* DOCDOC */ +STATIC int +handle_client_auth_nonce(const char *client_nonce, size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out) +{ + char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + char *reply; + size_t reply_len; + + if (client_nonce_len != EXT_OR_PORT_AUTH_NONCE_LEN) + return -1; + + /* Get our nonce */ + crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + { /* set up macs */ + size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + + char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); + char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); + char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); + + memcpy(hmac_s_msg, + EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + memcpy(hmac_c_msg, + EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_s_msg, + hmac_s_msg_len); + + crypto_hmac_sha256(correct_client_hash, + (char*)ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_c_msg, + hmac_c_msg_len); + + /* Store the client hash we generated. We will need to compare it + with the hash sent by the client. */ + *client_hash_out = correct_client_hash; + + memwipe(hmac_s_msg, 0, hmac_s_msg_len); + memwipe(hmac_c_msg, 0, hmac_c_msg_len); + + tor_free(hmac_s_msg); + tor_free(hmac_c_msg); + } + + { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ + char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; + char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + + base16_encode(server_hash_encoded, sizeof(server_hash_encoded), + server_hash, sizeof(server_hash)); + base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), + server_nonce, sizeof(server_nonce)); + base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + log_debug(LD_GENERAL, + "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", + server_hash_encoded, server_nonce_encoded, client_nonce_encoded); + + memwipe(server_hash_encoded, 0, sizeof(server_hash_encoded)); + memwipe(server_nonce_encoded, 0, sizeof(server_nonce_encoded)); + memwipe(client_nonce_encoded, 0, sizeof(client_nonce_encoded)); + } + + { /* write reply: (server_hash, server_nonce) */ + + reply_len = EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN; + reply = tor_malloc_zero(reply_len); + memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); + memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN); + } + + *reply_out = reply; + *reply_len_out = reply_len; + + return 0; +} + +/** Read the client's nonce out of <b>conn</b>, setup the safe-cookie + * crypto, and then send our own hash and nonce to the client + * + * Return -1 if there was an error; return 0 if we need more data in + * <b>conn</b>, and return 1 if we successfully retrieved the + * client's nonce and sent our own. */ +static int +connection_ext_or_auth_handle_client_nonce(connection_t *conn) +{ + char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN]; + char *reply=NULL; + size_t reply_len=0; + + if (!ext_or_auth_cookie_is_set) { /* this should not happen */ + log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " + "That's weird since we should have done that on startup. " + "This might be a Tor bug, please file a bug report. "); + return -1; + } + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) + return 0; + + if (connection_buf_get_bytes(client_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) + return -1; + + /* We extract the ClientNonce from the received data, and use it to + calculate ServerHash and ServerNonce according to proposal 217. + + We also calculate our own ClientHash value and save it in the + connection state. We validate it later against the ClientHash + sent by the client. */ + if (handle_client_auth_nonce(client_nonce, sizeof(client_nonce), + &TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + &reply, &reply_len) < 0) + return -1; + + connection_buf_add(reply, reply_len, conn); + + memwipe(reply, 0, reply_len); + tor_free(reply); + + log_debug(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; + return 1; +} + +#define connection_ext_or_auth_send_result_success(c) \ + connection_ext_or_auth_send_result(c, 1) +#define connection_ext_or_auth_send_result_fail(c) \ + connection_ext_or_auth_send_result(c, 0) + +/** Send authentication results to <b>conn</b>. Successful results if + * <b>success</b> is set; failure results otherwise. */ +static void +connection_ext_or_auth_send_result(connection_t *conn, int success) +{ + if (success) + connection_buf_add("\x01", 1, conn); + else + connection_buf_add("\x00", 1, conn); +} + +/** Receive the client's hash from <b>conn</b>, validate that it's + * correct, and then send the authentication results to the client. + * + * Return -1 if there was an error during validation; return 0 if we + * need more data in <b>conn</b>, and return 1 if we successfully + * validated the client's hash and sent a happy authentication + * result. */ +static int +connection_ext_or_auth_handle_client_hash(connection_t *conn) +{ + char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) + return 0; + + if (connection_buf_get_bytes(provided_client_hash, + EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) + return -1; + + if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { + log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); + connection_ext_or_auth_send_result_fail(conn); + return -1; + } + + log_debug(LD_GENERAL, "Got client's hash and it was legit."); + + /* send positive auth result */ + connection_ext_or_auth_send_result_success(conn); + conn->state = EXT_OR_CONN_STATE_OPEN; + return 1; +} + +/** Handle data from <b>or_conn</b> received on Extended ORPort. + * Return -1 on error. 0 on unsufficient data. 1 on correct. */ +static int +connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + + /* State transitions of the Extended ORPort authentication protocol: + + EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE (start state) -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE -> + EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH -> + EXT_OR_CONN_STATE_OPEN + + During EXT_OR_CONN_STATE_OPEN, data is handled by + connection_ext_or_process_inbuf(). + */ + + switch (conn->state) { /* Functionify */ + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return connection_ext_or_auth_neg_auth_type(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return connection_ext_or_auth_handle_client_nonce(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return connection_ext_or_auth_handle_client_hash(conn); + + default: + log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " + "to process Extended ORPort authentication data.", conn->state); + return -1; + } +} + +/** Extended ORPort commands (Transport-to-Bridge) */ +#define EXT_OR_CMD_TB_DONE 0x0000 +#define EXT_OR_CMD_TB_USERADDR 0x0001 +#define EXT_OR_CMD_TB_TRANSPORT 0x0002 + +/** Extended ORPort commands (Bridge-to-Transport) */ +#define EXT_OR_CMD_BT_OKAY 0x1000 +#define EXT_OR_CMD_BT_DENY 0x1001 +#define EXT_OR_CMD_BT_CONTROL 0x1002 + +/** Process a USERADDR command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the USERADDR command was well formed, change the address of + * <b>conn</b> to the address on the USERADDR command. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_useraddr(connection_t *conn, + const char *payload, uint16_t len) +{ + /* Copy address string. */ + tor_addr_t addr; + uint16_t port; + char *addr_str; + char *address_part=NULL; + int res; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort UserAddr"); + return -1; + } + + addr_str = tor_memdup_nulterm(payload, len); + + res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); + tor_free(addr_str); + if (res<0) + return -1; + if (port == 0) { + log_warn(LD_GENERAL, "Server transport proxy gave us an empty port " + "in ExtORPort UserAddr command."); + // return -1; // enable this if nothing breaks after a while. + } + + res = tor_addr_parse(&addr, address_part); + tor_free(address_part); + if (res<0) + return -1; + + { /* do some logging */ + char *old_address = tor_addr_to_str_dup(&conn->addr); + char *new_address = tor_addr_to_str_dup(&addr); + + log_debug(LD_NET, "Received USERADDR." + "We rewrite our address from '%s:%u' to '%s:%u'.", + safe_str(old_address), conn->port, safe_str(new_address), port); + + tor_free(old_address); + tor_free(new_address); + } + + /* record the address */ + tor_addr_copy(&conn->addr, &addr); + conn->port = port; + if (conn->address) { + tor_free(conn->address); + } + conn->address = tor_addr_to_str_dup(&addr); + + return 0; +} + +/** Process a TRANSPORT command from the Extended + * ORPort. <b>payload</b> is a payload of size <b>len</b>. + * + * If the TRANSPORT command was well formed, register the name of the + * transport on <b>conn</b>. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_cmd_transport(or_connection_t *conn, + const char *payload, uint16_t len) +{ + char *transport_str; + if (memchr(payload, '\0', len)) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort Transport"); + return -1; + } + + transport_str = tor_memdup_nulterm(payload, len); + + /* Transport names MUST be C-identifiers. */ + if (!string_is_C_identifier(transport_str)) { + tor_free(transport_str); + return -1; + } + + /* If ext_or_transport is already occupied (because the PT sent two + * TRANSPORT commands), deallocate the old name and keep the new + * one */ + if (conn->ext_or_transport) + tor_free(conn->ext_or_transport); + + conn->ext_or_transport = transport_str; + return 0; +} + +#define EXT_OR_CONN_STATE_IS_AUTHENTICATING(st) \ + ((st) <= EXT_OR_CONN_STATE_AUTH_MAX) + +/** Process Extended ORPort messages from <b>or_conn</b>. */ +int +connection_ext_or_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + ext_or_cmd_t *command; + int r; + + /* DOCDOC Document the state machine and transitions in this function */ + + /* If we are still in the authentication stage, process traffic as + authentication data: */ + while (EXT_OR_CONN_STATE_IS_AUTHENTICATING(conn->state)) { + log_debug(LD_GENERAL, "Got Extended ORPort authentication data (%u).", + (unsigned int) connection_get_inbuf_len(conn)); + r = connection_ext_or_auth_process_inbuf(or_conn); + if (r < 0) { + connection_mark_for_close(conn); + return -1; + } else if (r == 0) { + return 0; + } + /* if r > 0, loop and process more data (if any). */ + } + + while (1) { + log_debug(LD_GENERAL, "Got Extended ORPort data."); + command = NULL; + r = connection_fetch_ext_or_cmd_from_buf(conn, &command); + if (r < 0) + goto err; + else if (r == 0) + return 0; /* need to wait for more data */ + + /* Got a command! */ + tor_assert(command); + + if (command->cmd == EXT_OR_CMD_TB_DONE) { + if (connection_get_inbuf_len(conn)) { + /* The inbuf isn't empty; the client is misbehaving. */ + goto err; + } + + log_debug(LD_NET, "Received DONE."); + + /* If the transport proxy did not use the TRANSPORT command to + * specify the transport name, mark this as unknown transport. */ + if (!or_conn->ext_or_transport) { + /* We write this string this way to avoid ??>, which is a C + * trigraph. */ + or_conn->ext_or_transport = tor_strdup("<?" "?>"); + } + + connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); + + /* can't transition immediately; need to flush first. */ + conn->state = EXT_OR_CONN_STATE_FLUSHING; + connection_stop_reading(conn); + } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { + if (connection_ext_or_handle_cmd_useraddr(conn, + command->body, command->len) < 0) + goto err; + } else if (command->cmd == EXT_OR_CMD_TB_TRANSPORT) { + if (connection_ext_or_handle_cmd_transport(or_conn, + command->body, command->len) < 0) + goto err; + } else { + log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", + command->cmd); + } + + ext_or_cmd_free(command); + } + + return 0; + + err: + ext_or_cmd_free(command); + connection_mark_for_close(conn); + return -1; +} + +/** <b>conn</b> finished flushing Extended ORPort messages to the + * network, and is now ready to accept OR traffic. This function + * does the transition. */ +int +connection_ext_or_finished_flushing(or_connection_t *conn) +{ + if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { + connection_start_reading(TO_CONN(conn)); + connection_ext_or_transition(conn); + } + return 0; +} + +/** Initiate Extended ORPort authentication, by sending the list of + * supported authentication types to the client. */ +int +connection_ext_or_start_auth(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + const uint8_t authtypes[] = { + /* We only support authtype '1' for now. */ + EXT_OR_AUTHTYPE_SAFECOOKIE, + /* Marks the end of the list. */ + 0 + }; + + log_debug(LD_GENERAL, + "ExtORPort authentication: Sending supported authentication types"); + + connection_buf_add((const char *)authtypes, sizeof(authtypes), conn); + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; + + return 0; +} + +/** Free any leftover allocated memory of the ext_orport.c subsystem. */ +void +ext_orport_free_all(void) +{ + if (ext_or_auth_cookie) /* Free the auth cookie */ + tor_free(ext_or_auth_cookie); +} + diff --git a/src/feature/relay/ext_orport.h b/src/feature/relay/ext_orport.h new file mode 100644 index 0000000000..7eebfdb25b --- /dev/null +++ b/src/feature/relay/ext_orport.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef EXT_ORPORT_H +#define EXT_ORPORT_H + +/** States of the Extended ORPort protocol. Be careful before changing + * the numbers: they matter. */ +#define EXT_OR_CONN_STATE_MIN_ 1 +/** Extended ORPort authentication is waiting for the authentication + * type selected by the client. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE 1 +/** Extended ORPort authentication is waiting for the client nonce. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE 2 +/** Extended ORPort authentication is waiting for the client hash. */ +#define EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH 3 +#define EXT_OR_CONN_STATE_AUTH_MAX 3 +/** Authentication finished and the Extended ORPort is now accepting + * traffic. */ +#define EXT_OR_CONN_STATE_OPEN 4 +/** Extended ORPort is flushing its last messages and preparing to + * start accepting OR connections. */ +#define EXT_OR_CONN_STATE_FLUSHING 5 +#define EXT_OR_CONN_STATE_MAX_ 5 + +int connection_ext_or_start_auth(or_connection_t *or_conn); + +ext_or_cmd_t *ext_or_cmd_new(uint16_t len); + +#define ext_or_cmd_free(cmd) \ + FREE_AND_NULL(ext_or_cmd_t, ext_or_cmd_free_, (cmd)) + +void ext_or_cmd_free_(ext_or_cmd_t *cmd); +void connection_or_set_ext_or_identifier(or_connection_t *conn); +void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); +void connection_or_clear_ext_or_id_map(void); +or_connection_t *connection_or_get_by_ext_or_id(const char *id); + +int connection_ext_or_finished_flushing(or_connection_t *conn); +int connection_ext_or_process_inbuf(or_connection_t *or_conn); + +int init_ext_or_cookie_authentication(int is_enabled); +char *get_ext_or_auth_cookie_file_name(void); +void ext_orport_free_all(void); + +#ifdef EXT_ORPORT_PRIVATE +STATIC int connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen); +STATIC int handle_client_auth_nonce(const char *client_nonce, + size_t client_nonce_len, + char **client_hash_out, + char **reply_out, size_t *reply_len_out); +#ifdef TOR_UNIT_TESTS +extern uint8_t *ext_or_auth_cookie; +extern int ext_or_auth_cookie_is_set; +#endif +#endif /* defined(EXT_ORPORT_PRIVATE) */ + +#endif /* !defined(EXT_ORPORT_H) */ diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c new file mode 100644 index 0000000000..44af1e3108 --- /dev/null +++ b/src/feature/relay/router.c @@ -0,0 +1,3832 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define ROUTER_PRIVATE + +#include "or/or.h" +#include "or/circuitbuild.h" +#include "or/circuitlist.h" +#include "or/circuituse.h" +#include "or/config.h" +#include "or/connection.h" +#include "or/control.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "or/directory.h" +#include "or/dirserv.h" +#include "or/dns.h" +#include "or/geoip.h" +#include "or/hibernate.h" +#include "or/main.h" +#include "or/networkstatus.h" +#include "or/nodelist.h" +#include "or/policies.h" +#include "or/protover.h" +#include "or/relay.h" +#include "or/rephist.h" +#include "or/router.h" +#include "or/routerkeys.h" +#include "or/routerlist.h" +#include "or/routerparse.h" +#include "or/statefile.h" +#include "or/torcert.h" +#include "or/transports.h" +#include "or/routerset.h" + +#include "or/dirauth/mode.h" + +#include "or/authority_cert_st.h" +#include "or/crypt_path_st.h" +#include "or/dir_connection_st.h" +#include "or/dir_server_st.h" +#include "or/extend_info_st.h" +#include "or/extrainfo_st.h" +#include "or/node_st.h" +#include "or/origin_circuit_st.h" +#include "or/or_state_st.h" +#include "or/port_cfg_st.h" +#include "or/routerinfo_st.h" + +#include "lib/osinfo/uname.h" +#include "lib/tls/tortls.h" +#include "lib/encoding/confline.h" +#include "lib/crypt_ops/crypto_format.h" + +/** + * \file router.c + * \brief Miscellaneous relay functionality, including RSA key maintenance, + * generating and uploading server descriptors, picking an address to + * advertise, and so on. + * + * This module handles the job of deciding whether we are a Tor relay, and if + * so what kind. (Mostly through functions like server_mode() that inspect an + * or_options_t, but in some cases based on our own capabilities, such as when + * we are deciding whether to be a directory cache in + * router_has_bandwidth_to_be_dirserver().) + * + * Also in this module are the functions to generate our own routerinfo_t and + * extrainfo_t, and to encode those to signed strings for upload to the + * directory authorities. + * + * This module also handles key maintenance for RSA and Curve25519-ntor keys, + * and for our TLS context. (These functions should eventually move to + * routerkeys.c along with the code that handles Ed25519 keys now.) + **/ + +/************************************************************/ + +/***** + * Key management: ORs only. + *****/ + +/** Private keys for this OR. There is also an SSL key managed by tortls.c. + */ +static tor_mutex_t *key_lock=NULL; +static time_t onionkey_set_at=0; /**< When was onionkey last changed? */ +/** Current private onionskin decryption key: used to decode CREATE cells. */ +static crypto_pk_t *onionkey=NULL; +/** Previous private onionskin decryption key: used to decode CREATE cells + * generated by clients that have an older version of our descriptor. */ +static crypto_pk_t *lastonionkey=NULL; +/** Current private ntor secret key: used to perform the ntor handshake. */ +static curve25519_keypair_t curve25519_onion_key; +/** Previous private ntor secret key: used to perform the ntor handshake + * with clients that have an older version of our descriptor. */ +static curve25519_keypair_t last_curve25519_onion_key; +/** Private server "identity key": used to sign directory info and TLS + * certificates. Never changes. */ +static crypto_pk_t *server_identitykey=NULL; +/** Digest of server_identitykey. */ +static char server_identitykey_digest[DIGEST_LEN]; +/** Private client "identity key": used to sign bridges' and clients' + * outbound TLS certificates. Regenerated on startup and on IP address + * change. */ +static crypto_pk_t *client_identitykey=NULL; +/** Signing key used for v3 directory material; only set for authorities. */ +static crypto_pk_t *authority_signing_key = NULL; +/** Key certificate to authenticate v3 directory material; only set for + * authorities. */ +static authority_cert_t *authority_key_certificate = NULL; + +/** For emergency V3 authority key migration: An extra signing key that we use + * with our old (obsolete) identity key for a while. */ +static crypto_pk_t *legacy_signing_key = NULL; +/** For emergency V3 authority key migration: An extra certificate to + * authenticate legacy_signing_key with our obsolete identity key.*/ +static authority_cert_t *legacy_key_certificate = NULL; + +/* (Note that v3 authorities also have a separate "authority identity key", + * but this key is never actually loaded by the Tor process. Instead, it's + * used by tor-gencert to sign new signing keys and make new key + * certificates. */ + +const char *format_node_description(char *buf, + const char *id_digest, + int is_named, + const char *nickname, + const tor_addr_t *addr, + uint32_t addr32h); + +/** Return a readonly string with human readable description + * of <b>err</b>. + */ +const char * +routerinfo_err_to_string(int err) +{ + switch (err) { + case TOR_ROUTERINFO_ERROR_NO_EXT_ADDR: + return "No known exit address yet"; + case TOR_ROUTERINFO_ERROR_CANNOT_PARSE: + return "Cannot parse descriptor"; + case TOR_ROUTERINFO_ERROR_NOT_A_SERVER: + return "Not running in server mode"; + case TOR_ROUTERINFO_ERROR_DIGEST_FAILED: + return "Key digest failed"; + case TOR_ROUTERINFO_ERROR_CANNOT_GENERATE: + return "Cannot generate descriptor"; + case TOR_ROUTERINFO_ERROR_DESC_REBUILDING: + return "Descriptor still rebuilding - not ready yet"; + } + + log_warn(LD_BUG, "unknown routerinfo error %d - shouldn't happen", err); + tor_assert_unreached(); + + return "Unknown error"; +} + +/** Return true if we expect given error to be transient. + * Return false otherwise. + */ +int +routerinfo_err_is_transient(int err) +{ + switch (err) { + case TOR_ROUTERINFO_ERROR_NO_EXT_ADDR: + return 1; + case TOR_ROUTERINFO_ERROR_CANNOT_PARSE: + return 1; + case TOR_ROUTERINFO_ERROR_NOT_A_SERVER: + return 0; + case TOR_ROUTERINFO_ERROR_DIGEST_FAILED: + return 0; // XXX: bug? + case TOR_ROUTERINFO_ERROR_CANNOT_GENERATE: + return 1; + case TOR_ROUTERINFO_ERROR_DESC_REBUILDING: + return 1; + } + + return 0; +} + +/** Replace the current onion key with <b>k</b>. Does not affect + * lastonionkey; to update lastonionkey correctly, call rotate_onion_key(). + */ +static void +set_onion_key(crypto_pk_t *k) +{ + if (onionkey && crypto_pk_eq_keys(onionkey, k)) { + /* k is already our onion key; free it and return */ + crypto_pk_free(k); + return; + } + tor_mutex_acquire(key_lock); + crypto_pk_free(onionkey); + onionkey = k; + tor_mutex_release(key_lock); + mark_my_descriptor_dirty("set onion key"); +} + +/** Return the current onion key. Requires that the onion key has been + * loaded or generated. */ +crypto_pk_t * +get_onion_key(void) +{ + tor_assert(onionkey); + return onionkey; +} + +/** Store a full copy of the current onion key into *<b>key</b>, and a full + * copy of the most recent onion key into *<b>last</b>. Store NULL into + * a pointer if the corresponding key does not exist. + */ +void +dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) +{ + tor_assert(key); + tor_assert(last); + tor_mutex_acquire(key_lock); + if (onionkey) + *key = crypto_pk_copy_full(onionkey); + else + *key = NULL; + if (lastonionkey) + *last = crypto_pk_copy_full(lastonionkey); + else + *last = NULL; + tor_mutex_release(key_lock); +} + +/** Expire our old set of onion keys. This is done by setting + * last_curve25519_onion_key and lastonionkey to all zero's and NULL + * respectively. + * + * This function does not perform any grace period checks for the old onion + * keys. + */ +void +expire_old_onion_keys(void) +{ + char *fname = NULL; + + tor_mutex_acquire(key_lock); + + /* Free lastonionkey and set it to NULL. */ + if (lastonionkey) { + crypto_pk_free(lastonionkey); + lastonionkey = NULL; + } + + /* We zero out the keypair. See the tor_mem_is_zero() check made in + * construct_ntor_key_map() below. */ + memset(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); + + tor_mutex_release(key_lock); + + fname = get_keydir_fname("secret_onion_key.old"); + if (file_status(fname) == FN_FILE) { + if (tor_unlink(fname) != 0) { + log_warn(LD_FS, "Couldn't unlink old onion key file %s: %s", + fname, strerror(errno)); + } + } + tor_free(fname); + + fname = get_keydir_fname("secret_onion_key_ntor.old"); + if (file_status(fname) == FN_FILE) { + if (tor_unlink(fname) != 0) { + log_warn(LD_FS, "Couldn't unlink old ntor onion key file %s: %s", + fname, strerror(errno)); + } + } + tor_free(fname); +} + +/** Return the current secret onion key for the ntor handshake. Must only + * be called from the main thread. */ +static const curve25519_keypair_t * +get_current_curve25519_keypair(void) +{ + return &curve25519_onion_key; +} +/** Return a map from KEYID (the key itself) to keypairs for use in the ntor + * handshake. Must only be called from the main thread. */ +di_digest256_map_t * +construct_ntor_key_map(void) +{ + di_digest256_map_t *m = NULL; + + if (!tor_mem_is_zero((const char*) + curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN)) { + dimap_add_entry(&m, + curve25519_onion_key.pubkey.public_key, + tor_memdup(&curve25519_onion_key, + sizeof(curve25519_keypair_t))); + } + if (!tor_mem_is_zero((const char*) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN)) { + dimap_add_entry(&m, + last_curve25519_onion_key.pubkey.public_key, + tor_memdup(&last_curve25519_onion_key, + sizeof(curve25519_keypair_t))); + } + + return m; +} +/** Helper used to deallocate a di_digest256_map_t returned by + * construct_ntor_key_map. */ +static void +ntor_key_map_free_helper(void *arg) +{ + curve25519_keypair_t *k = arg; + memwipe(k, 0, sizeof(*k)); + tor_free(k); +} +/** Release all storage from a keymap returned by construct_ntor_key_map. */ +void +ntor_key_map_free_(di_digest256_map_t *map) +{ + if (!map) + return; + dimap_free(map, ntor_key_map_free_helper); +} + +/** Return the time when the onion key was last set. This is either the time + * when the process launched, or the time of the most recent key rotation since + * the process launched. + */ +time_t +get_onion_key_set_at(void) +{ + return onionkey_set_at; +} + +/** Set the current server identity key to <b>k</b>. + */ +void +set_server_identity_key(crypto_pk_t *k) +{ + crypto_pk_free(server_identitykey); + server_identitykey = k; + if (crypto_pk_get_digest(server_identitykey, + server_identitykey_digest) < 0) { + log_err(LD_BUG, "Couldn't compute our own identity key digest."); + tor_assert(0); + } +} + +/** Make sure that we have set up our identity keys to match or not match as + * appropriate, and die with an assertion if we have not. */ +static void +assert_identity_keys_ok(void) +{ + if (1) + return; + tor_assert(client_identitykey); + if (public_server_mode(get_options())) { + /* assert that we have set the client and server keys to be equal */ + tor_assert(server_identitykey); + tor_assert(crypto_pk_eq_keys(client_identitykey, server_identitykey)); + } else { + /* assert that we have set the client and server keys to be unequal */ + if (server_identitykey) + tor_assert(!crypto_pk_eq_keys(client_identitykey, server_identitykey)); + } +} + +/** Returns the current server identity key; requires that the key has + * been set, and that we are running as a Tor server. + */ +crypto_pk_t * +get_server_identity_key(void) +{ + tor_assert(server_identitykey); + tor_assert(server_mode(get_options())); + assert_identity_keys_ok(); + return server_identitykey; +} + +/** Return true iff we are a server and the server identity key + * has been set. */ +int +server_identity_key_is_set(void) +{ + return server_mode(get_options()) && server_identitykey != NULL; +} + +/** Set the current client identity key to <b>k</b>. + */ +void +set_client_identity_key(crypto_pk_t *k) +{ + crypto_pk_free(client_identitykey); + client_identitykey = k; +} + +/** Returns the current client identity key for use on outgoing TLS + * connections; requires that the key has been set. + */ +crypto_pk_t * +get_tlsclient_identity_key(void) +{ + tor_assert(client_identitykey); + assert_identity_keys_ok(); + return client_identitykey; +} + +/** Return true iff the client identity key has been set. */ +int +client_identity_key_is_set(void) +{ + return client_identitykey != NULL; +} + +/** Return the key certificate for this v3 (voting) authority, or NULL + * if we have no such certificate. */ +MOCK_IMPL(authority_cert_t *, +get_my_v3_authority_cert, (void)) +{ + return authority_key_certificate; +} + +/** Return the v3 signing key for this v3 (voting) authority, or NULL + * if we have no such key. */ +crypto_pk_t * +get_my_v3_authority_signing_key(void) +{ + return authority_signing_key; +} + +/** If we're an authority, and we're using a legacy authority identity key for + * emergency migration purposes, return the certificate associated with that + * key. */ +authority_cert_t * +get_my_v3_legacy_cert(void) +{ + return legacy_key_certificate; +} + +/** If we're an authority, and we're using a legacy authority identity key for + * emergency migration purposes, return that key. */ +crypto_pk_t * +get_my_v3_legacy_signing_key(void) +{ + return legacy_signing_key; +} + +/** Replace the previous onion key with the current onion key, and generate + * a new previous onion key. Immediately after calling this function, + * the OR should: + * - schedule all previous cpuworkers to shut down _after_ processing + * pending work. (This will cause fresh cpuworkers to be generated.) + * - generate and upload a fresh routerinfo. + */ +void +rotate_onion_key(void) +{ + char *fname, *fname_prev; + crypto_pk_t *prkey = NULL; + or_state_t *state = get_or_state(); + curve25519_keypair_t new_curve25519_keypair; + time_t now; + fname = get_keydir_fname("secret_onion_key"); + fname_prev = get_keydir_fname("secret_onion_key.old"); + /* There isn't much point replacing an old key with an empty file */ + if (file_status(fname) == FN_FILE) { + if (replace_file(fname, fname_prev)) + goto error; + } + if (!(prkey = crypto_pk_new())) { + log_err(LD_GENERAL,"Error constructing rotated onion key"); + goto error; + } + if (crypto_pk_generate_key(prkey)) { + log_err(LD_BUG,"Error generating onion key"); + goto error; + } + if (crypto_pk_write_private_key_to_filename(prkey, fname)) { + log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname); + goto error; + } + tor_free(fname); + tor_free(fname_prev); + fname = get_keydir_fname("secret_onion_key_ntor"); + fname_prev = get_keydir_fname("secret_onion_key_ntor.old"); + if (curve25519_keypair_generate(&new_curve25519_keypair, 1) < 0) + goto error; + /* There isn't much point replacing an old key with an empty file */ + if (file_status(fname) == FN_FILE) { + if (replace_file(fname, fname_prev)) + goto error; + } + if (curve25519_keypair_write_to_file(&new_curve25519_keypair, fname, + "onion") < 0) { + log_err(LD_FS,"Couldn't write curve25519 onion key to \"%s\".",fname); + goto error; + } + log_info(LD_GENERAL, "Rotating onion key"); + tor_mutex_acquire(key_lock); + crypto_pk_free(lastonionkey); + lastonionkey = onionkey; + onionkey = prkey; + memcpy(&last_curve25519_onion_key, &curve25519_onion_key, + sizeof(curve25519_keypair_t)); + memcpy(&curve25519_onion_key, &new_curve25519_keypair, + sizeof(curve25519_keypair_t)); + now = time(NULL); + state->LastRotatedOnionKey = onionkey_set_at = now; + tor_mutex_release(key_lock); + mark_my_descriptor_dirty("rotated onion key"); + or_state_mark_dirty(state, get_options()->AvoidDiskWrites ? now+3600 : 0); + goto done; + error: + log_warn(LD_GENERAL, "Couldn't rotate onion key."); + if (prkey) + crypto_pk_free(prkey); + done: + memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair)); + tor_free(fname); + tor_free(fname_prev); +} + +/** Log greeting message that points to new relay lifecycle document the + * first time this function has been called. + */ +static void +log_new_relay_greeting(void) +{ + static int already_logged = 0; + + if (already_logged) + return; + + tor_log(LOG_NOTICE, LD_GENERAL, "You are running a new relay. " + "Thanks for helping the Tor network! If you wish to know " + "what will happen in the upcoming weeks regarding its usage, " + "have a look at https://blog.torproject.org/blog/lifecycle-of" + "-a-new-relay"); + + already_logged = 1; +} + +/** Try to read an RSA key from <b>fname</b>. If <b>fname</b> doesn't exist + * and <b>generate</b> is true, create a new RSA key and save it in + * <b>fname</b>. Return the read/created key, or NULL on error. Log all + * errors at level <b>severity</b>. If <b>log_greeting</b> is non-zero and a + * new key was created, log_new_relay_greeting() is called. + */ +crypto_pk_t * +init_key_from_file(const char *fname, int generate, int severity, + int log_greeting) +{ + crypto_pk_t *prkey = NULL; + + if (!(prkey = crypto_pk_new())) { + tor_log(severity, LD_GENERAL,"Error constructing key"); + goto error; + } + + switch (file_status(fname)) { + case FN_DIR: + case FN_ERROR: + tor_log(severity, LD_FS,"Can't read key from \"%s\"", fname); + goto error; + /* treat empty key files as if the file doesn't exist, and, + * if generate is set, replace the empty file in + * crypto_pk_write_private_key_to_filename() */ + case FN_NOENT: + case FN_EMPTY: + if (generate) { + if (!have_lockfile()) { + if (try_locking(get_options(), 0)<0) { + /* Make sure that --list-fingerprint only creates new keys + * if there is no possibility for a deadlock. */ + tor_log(severity, LD_FS, "Another Tor process has locked \"%s\". " + "Not writing any new keys.", fname); + /*XXXX The 'other process' might make a key in a second or two; + * maybe we should wait for it. */ + goto error; + } + } + log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.", + fname); + if (crypto_pk_generate_key(prkey)) { + tor_log(severity, LD_GENERAL,"Error generating onion key"); + goto error; + } + if (crypto_pk_check_key(prkey) <= 0) { + tor_log(severity, LD_GENERAL,"Generated key seems invalid"); + goto error; + } + log_info(LD_GENERAL, "Generated key seems valid"); + if (log_greeting) { + log_new_relay_greeting(); + } + if (crypto_pk_write_private_key_to_filename(prkey, fname)) { + tor_log(severity, LD_FS, + "Couldn't write generated key to \"%s\".", fname); + goto error; + } + } else { + tor_log(severity, LD_GENERAL, "No key found in \"%s\"", fname); + goto error; + } + return prkey; + case FN_FILE: + if (crypto_pk_read_private_key_from_filename(prkey, fname)) { + tor_log(severity, LD_GENERAL,"Error loading private key."); + goto error; + } + return prkey; + default: + tor_assert(0); + } + + error: + if (prkey) + crypto_pk_free(prkey); + return NULL; +} + +/** Load a curve25519 keypair from the file <b>fname</b>, writing it into + * <b>keys_out</b>. If the file isn't found, or is empty, and <b>generate</b> + * is true, create a new keypair and write it into the file. If there are + * errors, log them at level <b>severity</b>. Generate files using <b>tag</b> + * in their ASCII wrapper. */ +static int +init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, + const char *fname, + int generate, + int severity, + const char *tag) +{ + switch (file_status(fname)) { + case FN_DIR: + case FN_ERROR: + tor_log(severity, LD_FS,"Can't read key from \"%s\"", fname); + goto error; + /* treat empty key files as if the file doesn't exist, and, if generate + * is set, replace the empty file in curve25519_keypair_write_to_file() */ + case FN_NOENT: + case FN_EMPTY: + if (generate) { + if (!have_lockfile()) { + if (try_locking(get_options(), 0)<0) { + /* Make sure that --list-fingerprint only creates new keys + * if there is no possibility for a deadlock. */ + tor_log(severity, LD_FS, "Another Tor process has locked \"%s\". " + "Not writing any new keys.", fname); + /*XXXX The 'other process' might make a key in a second or two; + * maybe we should wait for it. */ + goto error; + } + } + log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.", + fname); + if (curve25519_keypair_generate(keys_out, 1) < 0) + goto error; + if (curve25519_keypair_write_to_file(keys_out, fname, tag)<0) { + tor_log(severity, LD_FS, + "Couldn't write generated key to \"%s\".", fname); + memwipe(keys_out, 0, sizeof(*keys_out)); + goto error; + } + } else { + log_info(LD_GENERAL, "No key found in \"%s\"", fname); + } + return 0; + case FN_FILE: + { + char *tag_in=NULL; + if (curve25519_keypair_read_from_file(keys_out, &tag_in, fname) < 0) { + tor_log(severity, LD_GENERAL,"Error loading private key."); + tor_free(tag_in); + goto error; + } + if (!tag_in || strcmp(tag_in, tag)) { + tor_log(severity, LD_GENERAL,"Unexpected tag %s on private key.", + escaped(tag_in)); + tor_free(tag_in); + goto error; + } + tor_free(tag_in); + return 0; + } + default: + tor_assert(0); + } + + error: + return -1; +} + +/** Try to load the vote-signing private key and certificate for being a v3 + * directory authority, and make sure they match. If <b>legacy</b>, load a + * legacy key/cert set for emergency key migration; otherwise load the regular + * key/cert set. On success, store them into *<b>key_out</b> and + * *<b>cert_out</b> respectively, and return 0. On failure, return -1. */ +static int +load_authority_keyset(int legacy, crypto_pk_t **key_out, + authority_cert_t **cert_out) +{ + int r = -1; + char *fname = NULL, *cert = NULL; + const char *eos = NULL; + crypto_pk_t *signing_key = NULL; + authority_cert_t *parsed = NULL; + + fname = get_keydir_fname( + legacy ? "legacy_signing_key" : "authority_signing_key"); + signing_key = init_key_from_file(fname, 0, LOG_ERR, 0); + if (!signing_key) { + log_warn(LD_DIR, "No version 3 directory key found in %s", fname); + goto done; + } + tor_free(fname); + fname = get_keydir_fname( + legacy ? "legacy_certificate" : "authority_certificate"); + cert = read_file_to_str(fname, 0, NULL); + if (!cert) { + log_warn(LD_DIR, "Signing key found, but no certificate found in %s", + fname); + goto done; + } + parsed = authority_cert_parse_from_string(cert, &eos); + if (!parsed) { + log_warn(LD_DIR, "Unable to parse certificate in %s", fname); + goto done; + } + if (!crypto_pk_eq_keys(signing_key, parsed->signing_key)) { + log_warn(LD_DIR, "Stored signing key does not match signing key in " + "certificate"); + goto done; + } + + crypto_pk_free(*key_out); + authority_cert_free(*cert_out); + + *key_out = signing_key; + *cert_out = parsed; + r = 0; + signing_key = NULL; + parsed = NULL; + + done: + tor_free(fname); + tor_free(cert); + crypto_pk_free(signing_key); + authority_cert_free(parsed); + return r; +} + +/** Load the v3 (voting) authority signing key and certificate, if they are + * present. Return -1 if anything is missing, mismatched, or unloadable; + * return 0 on success. */ +static int +init_v3_authority_keys(void) +{ + if (load_authority_keyset(0, &authority_signing_key, + &authority_key_certificate)<0) + return -1; + + if (get_options()->V3AuthUseLegacyKey && + load_authority_keyset(1, &legacy_signing_key, + &legacy_key_certificate)<0) + return -1; + + return 0; +} + +/** If we're a v3 authority, check whether we have a certificate that's + * likely to expire soon. Warn if we do, but not too often. */ +void +v3_authority_check_key_expiry(void) +{ + time_t now, expires; + static time_t last_warned = 0; + int badness, time_left, warn_interval; + if (!authdir_mode_v3(get_options()) || !authority_key_certificate) + return; + + now = time(NULL); + expires = authority_key_certificate->expires; + time_left = (int)( expires - now ); + if (time_left <= 0) { + badness = LOG_ERR; + warn_interval = 60*60; + } else if (time_left <= 24*60*60) { + badness = LOG_WARN; + warn_interval = 60*60; + } else if (time_left <= 24*60*60*7) { + badness = LOG_WARN; + warn_interval = 24*60*60; + } else if (time_left <= 24*60*60*30) { + badness = LOG_WARN; + warn_interval = 24*60*60*5; + } else { + return; + } + + if (last_warned + warn_interval > now) + return; + + if (time_left <= 0) { + tor_log(badness, LD_DIR, "Your v3 authority certificate has expired." + " Generate a new one NOW."); + } else if (time_left <= 24*60*60) { + tor_log(badness, LD_DIR, "Your v3 authority certificate expires in %d " + "hours; Generate a new one NOW.", time_left/(60*60)); + } else { + tor_log(badness, LD_DIR, "Your v3 authority certificate expires in %d " + "days; Generate a new one soon.", time_left/(24*60*60)); + } + last_warned = now; +} + +/** Get the lifetime of an onion key in days. This value is defined by the + * network consesus parameter "onion-key-rotation-days". Always returns a value + * between <b>MIN_ONION_KEY_LIFETIME_DAYS</b> and + * <b>MAX_ONION_KEY_LIFETIME_DAYS</b>. + */ +static int +get_onion_key_rotation_days_(void) +{ + return networkstatus_get_param(NULL, + "onion-key-rotation-days", + DEFAULT_ONION_KEY_LIFETIME_DAYS, + MIN_ONION_KEY_LIFETIME_DAYS, + MAX_ONION_KEY_LIFETIME_DAYS); +} + +/** Get the current lifetime of an onion key in seconds. This value is defined + * by the network consesus parameter "onion-key-rotation-days", but the value + * is converted to seconds. + */ +int +get_onion_key_lifetime(void) +{ + return get_onion_key_rotation_days_()*24*60*60; +} + +/** Get the grace period of an onion key in seconds. This value is defined by + * the network consesus parameter "onion-key-grace-period-days", but the value + * is converted to seconds. + */ +int +get_onion_key_grace_period(void) +{ + int grace_period; + grace_period = networkstatus_get_param(NULL, + "onion-key-grace-period-days", + DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS, + MIN_ONION_KEY_GRACE_PERIOD_DAYS, + get_onion_key_rotation_days_()); + return grace_period*24*60*60; +} + +/** Set up Tor's TLS contexts, based on our configuration and keys. Return 0 + * on success, and -1 on failure. */ +int +router_initialize_tls_context(void) +{ + unsigned int flags = 0; + const or_options_t *options = get_options(); + int lifetime = options->SSLKeyLifetime; + if (public_server_mode(options)) + flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER; + if (!lifetime) { /* we should guess a good ssl cert lifetime */ + + /* choose between 5 and 365 days, and round to the day */ + unsigned int five_days = 5*24*3600; + unsigned int one_year = 365*24*3600; + lifetime = crypto_rand_int_range(five_days, one_year); + lifetime -= lifetime % (24*3600); + + if (crypto_rand_int(2)) { + /* Half the time we expire at midnight, and half the time we expire + * one second before midnight. (Some CAs wobble their expiry times a + * bit in practice, perhaps to reduce collision attacks; see ticket + * 8443 for details about observed certs in the wild.) */ + lifetime--; + } + } + + /* It's ok to pass lifetime in as an unsigned int, since + * config_parse_interval() checked it. */ + return tor_tls_context_init(flags, + get_tlsclient_identity_key(), + server_mode(options) ? + get_server_identity_key() : NULL, + (unsigned int)lifetime); +} + +/** Compute fingerprint (or hashed fingerprint if hashed is 1) and write + * it to 'fingerprint' (or 'hashed-fingerprint'). Return 0 on success, or + * -1 if Tor should die, + */ +STATIC int +router_write_fingerprint(int hashed) +{ + char *keydir = NULL, *cp = NULL; + const char *fname = hashed ? "hashed-fingerprint" : + "fingerprint"; + char fingerprint[FINGERPRINT_LEN+1]; + const or_options_t *options = get_options(); + char *fingerprint_line = NULL; + int result = -1; + + keydir = get_datadir_fname(fname); + log_info(LD_GENERAL,"Dumping %sfingerprint to \"%s\"...", + hashed ? "hashed " : "", keydir); + if (!hashed) { + if (crypto_pk_get_fingerprint(get_server_identity_key(), + fingerprint, 0) < 0) { + log_err(LD_GENERAL,"Error computing fingerprint"); + goto done; + } + } else { + if (crypto_pk_get_hashed_fingerprint(get_server_identity_key(), + fingerprint) < 0) { + log_err(LD_GENERAL,"Error computing hashed fingerprint"); + goto done; + } + } + + tor_asprintf(&fingerprint_line, "%s %s\n", options->Nickname, fingerprint); + + /* Check whether we need to write the (hashed-)fingerprint file. */ + + cp = read_file_to_str(keydir, RFTS_IGNORE_MISSING, NULL); + if (!cp || strcmp(cp, fingerprint_line)) { + if (write_str_to_file(keydir, fingerprint_line, 0)) { + log_err(LD_FS, "Error writing %sfingerprint line to file", + hashed ? "hashed " : ""); + goto done; + } + } + + log_notice(LD_GENERAL, "Your Tor %s identity key fingerprint is '%s %s'", + hashed ? "bridge's hashed" : "server's", options->Nickname, + fingerprint); + + result = 0; + done: + tor_free(cp); + tor_free(keydir); + tor_free(fingerprint_line); + return result; +} + +static int +init_keys_common(void) +{ + if (!key_lock) + key_lock = tor_mutex_new(); + + /* There are a couple of paths that put us here before we've asked + * openssl to initialize itself. */ + if (crypto_global_init(get_options()->HardwareAccel, + get_options()->AccelName, + get_options()->AccelDir)) { + log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting."); + return -1; + } + + return 0; +} + +int +init_keys_client(void) +{ + crypto_pk_t *prkey; + if (init_keys_common() < 0) + return -1; + + if (!(prkey = crypto_pk_new())) + return -1; + if (crypto_pk_generate_key(prkey)) { + crypto_pk_free(prkey); + return -1; + } + set_client_identity_key(prkey); + /* Create a TLS context. */ + if (router_initialize_tls_context() < 0) { + log_err(LD_GENERAL,"Error creating TLS context for Tor client."); + return -1; + } + return 0; +} + +/** Initialize all OR private keys, and the TLS context, as necessary. + * On OPs, this only initializes the tls context. Return 0 on success, + * or -1 if Tor should die. + */ +int +init_keys(void) +{ + char *keydir; + const char *mydesc; + crypto_pk_t *prkey; + char digest[DIGEST_LEN]; + char v3_digest[DIGEST_LEN]; + const or_options_t *options = get_options(); + dirinfo_type_t type; + time_t now = time(NULL); + dir_server_t *ds; + int v3_digest_set = 0; + authority_cert_t *cert = NULL; + + /* OP's don't need persistent keys; just make up an identity and + * initialize the TLS context. */ + if (!server_mode(options)) { + return init_keys_client(); + } + if (init_keys_common() < 0) + return -1; + + if (create_keys_directory(options) < 0) + return -1; + + /* 1a. Read v3 directory authority key/cert information. */ + memset(v3_digest, 0, sizeof(v3_digest)); + if (authdir_mode_v3(options)) { + if (init_v3_authority_keys()<0) { + log_err(LD_GENERAL, "We're configured as a V3 authority, but we " + "were unable to load our v3 authority keys and certificate! " + "Use tor-gencert to generate them. Dying."); + return -1; + } + cert = get_my_v3_authority_cert(); + if (cert) { + if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, + v3_digest) < 0) { + log_err(LD_BUG, "Couldn't compute my v3 authority identity key " + "digest."); + return -1; + } + v3_digest_set = 1; + } + } + + /* 1b. Read identity key. Make it if none is found. */ + keydir = get_keydir_fname("secret_id_key"); + log_info(LD_GENERAL,"Reading/making identity key \"%s\"...",keydir); + prkey = init_key_from_file(keydir, 1, LOG_ERR, 1); + tor_free(keydir); + if (!prkey) return -1; + set_server_identity_key(prkey); + + /* 1c. If we are configured as a bridge, generate a client key; + * otherwise, set the server identity key as our client identity + * key. */ + if (public_server_mode(options)) { + set_client_identity_key(crypto_pk_dup_key(prkey)); /* set above */ + } else { + if (!(prkey = crypto_pk_new())) + return -1; + if (crypto_pk_generate_key(prkey)) { + crypto_pk_free(prkey); + return -1; + } + set_client_identity_key(prkey); + } + + /* 1d. Load all ed25519 keys */ + const int new_signing_key = load_ed_keys(options,now); + if (new_signing_key < 0) + return -1; + + /* 2. Read onion key. Make it if none is found. */ + keydir = get_keydir_fname("secret_onion_key"); + log_info(LD_GENERAL,"Reading/making onion key \"%s\"...",keydir); + prkey = init_key_from_file(keydir, 1, LOG_ERR, 1); + tor_free(keydir); + if (!prkey) return -1; + set_onion_key(prkey); + if (options->command == CMD_RUN_TOR) { + /* only mess with the state file if we're actually running Tor */ + or_state_t *state = get_or_state(); + if (state->LastRotatedOnionKey > 100 && state->LastRotatedOnionKey < now) { + /* We allow for some parsing slop, but we don't want to risk accepting + * values in the distant future. If we did, we might never rotate the + * onion key. */ + onionkey_set_at = state->LastRotatedOnionKey; + } else { + /* We have no LastRotatedOnionKey set; either we just created the key + * or it's a holdover from 0.1.2.4-alpha-dev or earlier. In either case, + * start the clock ticking now so that we will eventually rotate it even + * if we don't stay up for the full lifetime of an onion key. */ + state->LastRotatedOnionKey = onionkey_set_at = now; + or_state_mark_dirty(state, options->AvoidDiskWrites ? + time(NULL)+3600 : 0); + } + } + + keydir = get_keydir_fname("secret_onion_key.old"); + if (!lastonionkey && file_status(keydir) == FN_FILE) { + /* Load keys from non-empty files only. + * Missing old keys won't be replaced with freshly generated keys. */ + prkey = init_key_from_file(keydir, 0, LOG_ERR, 0); + if (prkey) + lastonionkey = prkey; + } + tor_free(keydir); + + { + /* 2b. Load curve25519 onion keys. */ + int r; + keydir = get_keydir_fname("secret_onion_key_ntor"); + r = init_curve25519_keypair_from_file(&curve25519_onion_key, + keydir, 1, LOG_ERR, "onion"); + tor_free(keydir); + if (r<0) + return -1; + + keydir = get_keydir_fname("secret_onion_key_ntor.old"); + if (tor_mem_is_zero((const char *) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN) && + file_status(keydir) == FN_FILE) { + /* Load keys from non-empty files only. + * Missing old keys won't be replaced with freshly generated keys. */ + init_curve25519_keypair_from_file(&last_curve25519_onion_key, + keydir, 0, LOG_ERR, "onion"); + } + tor_free(keydir); + } + + /* 3. Initialize link key and TLS context. */ + if (router_initialize_tls_context() < 0) { + log_err(LD_GENERAL,"Error initializing TLS context"); + return -1; + } + + /* 3b. Get an ed25519 link certificate. Note that we need to do this + * after we set up the TLS context */ + if (generate_ed_link_cert(options, now, new_signing_key > 0) < 0) { + log_err(LD_GENERAL,"Couldn't make link cert"); + return -1; + } + + /* 4. Build our router descriptor. */ + /* Must be called after keys are initialized. */ + mydesc = router_get_my_descriptor(); + if (authdir_mode_v3(options)) { + const char *m = NULL; + routerinfo_t *ri; + /* We need to add our own fingerprint so it gets recognized. */ + if (dirserv_add_own_fingerprint(get_server_identity_key())) { + log_err(LD_GENERAL,"Error adding own fingerprint to set of relays"); + return -1; + } + if (mydesc) { + was_router_added_t added; + ri = router_parse_entry_from_string(mydesc, NULL, 1, 0, NULL, NULL); + if (!ri) { + log_err(LD_GENERAL,"Generated a routerinfo we couldn't parse."); + return -1; + } + added = dirserv_add_descriptor(ri, &m, "self"); + if (!WRA_WAS_ADDED(added)) { + if (!WRA_WAS_OUTDATED(added)) { + log_err(LD_GENERAL, "Unable to add own descriptor to directory: %s", + m?m:"<unknown error>"); + return -1; + } else { + /* If the descriptor was outdated, that's ok. This can happen + * when some config options are toggled that affect workers, but + * we don't really need new keys yet so the descriptor doesn't + * change and the old one is still fresh. */ + log_info(LD_GENERAL, "Couldn't add own descriptor to directory " + "after key init: %s This is usually not a problem.", + m?m:"<unknown error>"); + } + } + } + } + + /* 5. Dump fingerprint and possibly hashed fingerprint to files. */ + if (router_write_fingerprint(0)) { + log_err(LD_FS, "Error writing fingerprint to file"); + return -1; + } + if (!public_server_mode(options) && router_write_fingerprint(1)) { + log_err(LD_FS, "Error writing hashed fingerprint to file"); + return -1; + } + + if (!authdir_mode(options)) + return 0; + /* 6. [authdirserver only] load approved-routers file */ + if (dirserv_load_fingerprint_file() < 0) { + log_err(LD_GENERAL,"Error loading fingerprints"); + return -1; + } + /* 6b. [authdirserver only] add own key to approved directories. */ + crypto_pk_get_digest(get_server_identity_key(), digest); + type = ((options->V3AuthoritativeDir ? + (V3_DIRINFO|MICRODESC_DIRINFO|EXTRAINFO_DIRINFO) : NO_DIRINFO) | + (options->BridgeAuthoritativeDir ? BRIDGE_DIRINFO : NO_DIRINFO)); + + ds = router_get_trusteddirserver_by_digest(digest); + if (!ds) { + ds = trusted_dir_server_new(options->Nickname, NULL, + router_get_advertised_dir_port(options, 0), + router_get_advertised_or_port(options), + NULL, + digest, + v3_digest, + type, 0.0); + if (!ds) { + log_err(LD_GENERAL,"We want to be a directory authority, but we " + "couldn't add ourselves to the authority list. Failing."); + return -1; + } + dir_server_add(ds); + } + if (ds->type != type) { + log_warn(LD_DIR, "Configured authority type does not match authority " + "type in DirAuthority list. Adjusting. (%d v %d)", + type, ds->type); + ds->type = type; + } + if (v3_digest_set && (ds->type & V3_DIRINFO) && + tor_memneq(v3_digest, ds->v3_identity_digest, DIGEST_LEN)) { + log_warn(LD_DIR, "V3 identity key does not match identity declared in " + "DirAuthority line. Adjusting."); + memcpy(ds->v3_identity_digest, v3_digest, DIGEST_LEN); + } + + if (cert) { /* add my own cert to the list of known certs */ + log_info(LD_DIR, "adding my own v3 cert"); + if (trusted_dirs_load_certs_from_string( + cert->cache_info.signed_descriptor_body, + TRUSTED_DIRS_CERTS_SRC_SELF, 0, + NULL)<0) { + log_warn(LD_DIR, "Unable to parse my own v3 cert! Failing."); + return -1; + } + } + + return 0; /* success */ +} + +/* Keep track of whether we should upload our server descriptor, + * and what type of server we are. + */ + +/** Whether we can reach our ORPort from the outside. */ +static int can_reach_or_port = 0; +/** Whether we can reach our DirPort from the outside. */ +static int can_reach_dir_port = 0; + +/** Forget what we have learned about our reachability status. */ +void +router_reset_reachability(void) +{ + can_reach_or_port = can_reach_dir_port = 0; +} + +/** Return 1 if we won't do reachability checks, because: + * - AssumeReachable is set, or + * - the network is disabled. + * Otherwise, return 0. + */ +static int +router_reachability_checks_disabled(const or_options_t *options) +{ + return options->AssumeReachable || + net_is_disabled(); +} + +/** Return 0 if we need to do an ORPort reachability check, because: + * - no reachability check has been done yet, or + * - we've initiated reachability checks, but none have succeeded. + * Return 1 if we don't need to do an ORPort reachability check, because: + * - we've seen a successful reachability check, or + * - AssumeReachable is set, or + * - the network is disabled. + */ +int +check_whether_orport_reachable(const or_options_t *options) +{ + int reach_checks_disabled = router_reachability_checks_disabled(options); + return reach_checks_disabled || + can_reach_or_port; +} + +/** Return 0 if we need to do a DirPort reachability check, because: + * - no reachability check has been done yet, or + * - we've initiated reachability checks, but none have succeeded. + * Return 1 if we don't need to do a DirPort reachability check, because: + * - we've seen a successful reachability check, or + * - there is no DirPort set, or + * - AssumeReachable is set, or + * - the network is disabled. + */ +int +check_whether_dirport_reachable(const or_options_t *options) +{ + int reach_checks_disabled = router_reachability_checks_disabled(options) || + !options->DirPort_set; + return reach_checks_disabled || + can_reach_dir_port; +} + +/** The lower threshold of remaining bandwidth required to advertise (or + * automatically provide) directory services */ +/* XXX Should this be increased? */ +#define MIN_BW_TO_ADVERTISE_DIRSERVER 51200 + +/** Return true iff we have enough configured bandwidth to advertise or + * automatically provide directory services from cache directory + * information. */ +static int +router_has_bandwidth_to_be_dirserver(const or_options_t *options) +{ + if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) { + return 0; + } + if (options->RelayBandwidthRate > 0 && + options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) { + return 0; + } + return 1; +} + +/** Helper: Return 1 if we have sufficient resources for serving directory + * requests, return 0 otherwise. + * dir_port is either 0 or the configured DirPort number. + * If AccountingMax is set less than our advertised bandwidth, then don't + * serve requests. Likewise, if our advertised bandwidth is less than + * MIN_BW_TO_ADVERTISE_DIRSERVER, don't bother trying to serve requests. + */ +static int +router_should_be_dirserver(const or_options_t *options, int dir_port) +{ + static int advertising=1; /* start out assuming we will advertise */ + int new_choice=1; + const char *reason = NULL; + + if (accounting_is_enabled(options) && + get_options()->AccountingRule != ACCT_IN) { + /* Don't spend bytes for directory traffic if we could end up hibernating, + * but allow DirPort otherwise. Some relay operators set AccountingMax + * because they're confused or to get statistics. Directory traffic has a + * much larger effect on output than input so there is no reason to turn it + * off if using AccountingRule in. */ + int interval_length = accounting_get_interval_length(); + uint32_t effective_bw = get_effective_bwrate(options); + uint64_t acc_bytes; + if (!interval_length) { + log_warn(LD_BUG, "An accounting interval is not allowed to be zero " + "seconds long. Raising to 1."); + interval_length = 1; + } + log_info(LD_GENERAL, "Calculating whether to advertise %s: effective " + "bwrate: %u, AccountingMax: %"PRIu64", " + "accounting interval length %d", + dir_port ? "dirport" : "begindir", + effective_bw, (options->AccountingMax), + interval_length); + + acc_bytes = options->AccountingMax; + if (get_options()->AccountingRule == ACCT_SUM) + acc_bytes /= 2; + if (effective_bw >= + acc_bytes / interval_length) { + new_choice = 0; + reason = "AccountingMax enabled"; + } + } else if (! router_has_bandwidth_to_be_dirserver(options)) { + /* if we're advertising a small amount */ + new_choice = 0; + reason = "BandwidthRate under 50KB"; + } + + if (advertising != new_choice) { + if (new_choice == 1) { + if (dir_port > 0) + log_notice(LD_DIR, "Advertising DirPort as %d", dir_port); + else + log_notice(LD_DIR, "Advertising directory service support"); + } else { + tor_assert(reason); + log_notice(LD_DIR, "Not advertising Dir%s (Reason: %s)", + dir_port ? "Port" : "ectory Service support", reason); + } + advertising = new_choice; + } + + return advertising; +} + +/** Return 1 if we are configured to accept either relay or directory requests + * from clients and we aren't at risk of exceeding our bandwidth limits, thus + * we should be a directory server. If not, return 0. + */ +int +dir_server_mode(const or_options_t *options) +{ + if (!options->DirCache) + return 0; + return options->DirPort_set || + (server_mode(options) && router_has_bandwidth_to_be_dirserver(options)); +} + +/** Look at a variety of factors, and return 0 if we don't want to + * advertise the fact that we have a DirPort open or begindir support, else + * return 1. + * + * Where dir_port or supports_tunnelled_dir_requests are not relevant, they + * must be 0. + * + * Log a helpful message if we change our mind about whether to publish. + */ +static int +decide_to_advertise_dir_impl(const or_options_t *options, + uint16_t dir_port, + int supports_tunnelled_dir_requests) +{ + /* Part one: reasons to publish or not publish that aren't + * worth mentioning to the user, either because they're obvious + * or because they're normal behavior. */ + + /* short circuit the rest of the function */ + if (!dir_port && !supports_tunnelled_dir_requests) + return 0; + if (authdir_mode(options)) /* always publish */ + return 1; + if (net_is_disabled()) + return 0; + if (dir_port && !router_get_advertised_dir_port(options, dir_port)) + return 0; + if (supports_tunnelled_dir_requests && + !router_get_advertised_or_port(options)) + return 0; + + /* Part two: consider config options that could make us choose to + * publish or not publish that the user might find surprising. */ + return router_should_be_dirserver(options, dir_port); +} + +/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to + * advertise the fact that we have a DirPort open, else return the + * DirPort we want to advertise. + */ +static int +router_should_advertise_dirport(const or_options_t *options, uint16_t dir_port) +{ + /* supports_tunnelled_dir_requests is not relevant, pass 0 */ + return decide_to_advertise_dir_impl(options, dir_port, 0) ? dir_port : 0; +} + +/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to + * advertise the fact that we support begindir requests, else return 1. + */ +static int +router_should_advertise_begindir(const or_options_t *options, + int supports_tunnelled_dir_requests) +{ + /* dir_port is not relevant, pass 0 */ + return decide_to_advertise_dir_impl(options, 0, + supports_tunnelled_dir_requests); +} + +/** Allocate and return a new extend_info_t that can be used to build + * a circuit to or through the router <b>r</b>. Uses the primary + * address of the router, so should only be called on a server. */ +static extend_info_t * +extend_info_from_router(const routerinfo_t *r) +{ + tor_addr_port_t ap; + tor_assert(r); + + /* Make sure we don't need to check address reachability */ + tor_assert_nonfatal(router_skip_or_reachability(get_options(), 0)); + + const ed25519_public_key_t *ed_id_key; + if (r->cache_info.signing_key_cert) + ed_id_key = &r->cache_info.signing_key_cert->signing_key; + else + ed_id_key = NULL; + + router_get_prim_orport(r, &ap); + return extend_info_new(r->nickname, r->cache_info.identity_digest, + ed_id_key, + r->onion_pkey, r->onion_curve25519_pkey, + &ap.addr, ap.port); +} + +/**See if we currently believe our ORPort or DirPort to be + * unreachable. If so, return 1 else return 0. + */ +static int +router_should_check_reachability(int test_or, int test_dir) +{ + const routerinfo_t *me = router_get_my_routerinfo(); + const or_options_t *options = get_options(); + + if (!me) + return 0; + + if (routerset_contains_router(options->ExcludeNodes, me, -1) && + options->StrictNodes) { + /* If we've excluded ourself, and StrictNodes is set, we can't test + * ourself. */ + if (test_or || test_dir) { +#define SELF_EXCLUDED_WARN_INTERVAL 3600 + static ratelim_t warning_limit=RATELIM_INIT(SELF_EXCLUDED_WARN_INTERVAL); + log_fn_ratelim(&warning_limit, LOG_WARN, LD_CIRC, + "Can't peform self-tests for this relay: we have " + "listed ourself in ExcludeNodes, and StrictNodes is set. " + "We cannot learn whether we are usable, and will not " + "be able to advertise ourself."); + } + return 0; + } + return 1; +} + +/** Some time has passed, or we just got new directory information. + * See if we currently believe our ORPort or DirPort to be + * unreachable. If so, launch a new test for it. + * + * For ORPort, we simply try making a circuit that ends at ourselves. + * Success is noticed in onionskin_answer(). + * + * For DirPort, we make a connection via Tor to our DirPort and ask + * for our own server descriptor. + * Success is noticed in connection_dir_client_reached_eof(). + */ +void +router_do_reachability_checks(int test_or, int test_dir) +{ + const routerinfo_t *me = router_get_my_routerinfo(); + const or_options_t *options = get_options(); + int orport_reachable = check_whether_orport_reachable(options); + tor_addr_t addr; + + if (router_should_check_reachability(test_or, test_dir)) { + if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) { + extend_info_t *ei = extend_info_from_router(me); + /* XXX IPv6 self testing */ + log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", + !orport_reachable ? "reachability" : "bandwidth", + fmt_addr32(me->addr), me->or_port); + circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei, + CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + extend_info_free(ei); + } + + /* XXX IPv6 self testing */ + tor_addr_from_ipv4h(&addr, me->addr); + if (test_dir && !check_whether_dirport_reachable(options) && + !connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, me->dir_port, + DIR_PURPOSE_FETCH_SERVERDESC)) { + tor_addr_port_t my_orport, my_dirport; + memcpy(&my_orport.addr, &addr, sizeof(addr)); + memcpy(&my_dirport.addr, &addr, sizeof(addr)); + my_orport.port = me->or_port; + my_dirport.port = me->dir_port; + /* ask myself, via tor, for my server descriptor. */ + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC); + directory_request_set_or_addr_port(req, &my_orport); + directory_request_set_dir_addr_port(req, &my_dirport); + directory_request_set_directory_id_digest(req, + me->cache_info.identity_digest); + // ask via an anon circuit, connecting to our dirport. + directory_request_set_indirection(req, DIRIND_ANON_DIRPORT); + directory_request_set_resource(req, "authority.z"); + directory_initiate_request(req); + directory_request_free(req); + } + } +} + +/** Annotate that we found our ORPort reachable. */ +void +router_orport_found_reachable(void) +{ + const routerinfo_t *me = router_get_my_routerinfo(); + const or_options_t *options = get_options(); + if (!can_reach_or_port && me) { + char *address = tor_dup_ip(me->addr); + log_notice(LD_OR,"Self-testing indicates your ORPort is reachable from " + "the outside. Excellent.%s", + options->PublishServerDescriptor_ != NO_DIRINFO + && check_whether_dirport_reachable(options) ? + " Publishing server descriptor." : ""); + can_reach_or_port = 1; + mark_my_descriptor_dirty("ORPort found reachable"); + /* This is a significant enough change to upload immediately, + * at least in a test network */ + if (options->TestingTorNetwork == 1) { + reschedule_descriptor_update_check(); + } + control_event_server_status(LOG_NOTICE, + "REACHABILITY_SUCCEEDED ORADDRESS=%s:%d", + address, me->or_port); + tor_free(address); + } +} + +/** Annotate that we found our DirPort reachable. */ +void +router_dirport_found_reachable(void) +{ + const routerinfo_t *me = router_get_my_routerinfo(); + const or_options_t *options = get_options(); + if (!can_reach_dir_port && me) { + char *address = tor_dup_ip(me->addr); + log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable " + "from the outside. Excellent.%s", + options->PublishServerDescriptor_ != NO_DIRINFO + && check_whether_orport_reachable(options) ? + " Publishing server descriptor." : ""); + can_reach_dir_port = 1; + if (router_should_advertise_dirport(options, me->dir_port)) { + mark_my_descriptor_dirty("DirPort found reachable"); + /* This is a significant enough change to upload immediately, + * at least in a test network */ + if (options->TestingTorNetwork == 1) { + reschedule_descriptor_update_check(); + } + } + control_event_server_status(LOG_NOTICE, + "REACHABILITY_SUCCEEDED DIRADDRESS=%s:%d", + address, me->dir_port); + tor_free(address); + } +} + +/** We have enough testing circuits open. Send a bunch of "drop" + * cells down each of them, to exercise our bandwidth. */ +void +router_perform_bandwidth_test(int num_circs, time_t now) +{ + int num_cells = (int)(get_options()->BandwidthRate * 10 / + CELL_MAX_NETWORK_SIZE); + int max_cells = num_cells < CIRCWINDOW_START ? + num_cells : CIRCWINDOW_START; + int cells_per_circuit = max_cells / num_circs; + origin_circuit_t *circ = NULL; + + log_notice(LD_OR,"Performing bandwidth self-test...done."); + while ((circ = circuit_get_next_by_pk_and_purpose(circ, NULL, + CIRCUIT_PURPOSE_TESTING))) { + /* dump cells_per_circuit drop cells onto this circ */ + int i = cells_per_circuit; + if (circ->base_.state != CIRCUIT_STATE_OPEN) + continue; + circ->base_.timestamp_dirty = now; + while (i-- > 0) { + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), + RELAY_COMMAND_DROP, + NULL, 0, circ->cpath->prev)<0) { + return; /* stop if error */ + } + } + } +} + +/** Return true iff our network is in some sense disabled or shutting down: + * either we're hibernating, entering hibernation, or the network is turned + * off with DisableNetwork. */ +int +net_is_disabled(void) +{ + return get_options()->DisableNetwork || we_are_hibernating(); +} + +/** Return true iff our network is in some sense "completely disabled" either + * we're fully hibernating or the network is turned off with + * DisableNetwork. */ +int +net_is_completely_disabled(void) +{ + return get_options()->DisableNetwork || we_are_fully_hibernating(); +} + +/** Return true iff we believe ourselves to be an authoritative + * directory server. + */ +int +authdir_mode(const or_options_t *options) +{ + return options->AuthoritativeDir != 0; +} +/** Return true iff we are an authoritative directory server that is + * authoritative about receiving and serving descriptors of type + * <b>purpose</b> on its dirport. + */ +int +authdir_mode_handles_descs(const or_options_t *options, int purpose) +{ + if (BUG(purpose < 0)) /* Deprecated. */ + return authdir_mode(options); + else if (purpose == ROUTER_PURPOSE_GENERAL) + return authdir_mode_v3(options); + else if (purpose == ROUTER_PURPOSE_BRIDGE) + return authdir_mode_bridge(options); + else + return 0; +} +/** Return true iff we are an authoritative directory server that + * publishes its own network statuses. + */ +int +authdir_mode_publishes_statuses(const or_options_t *options) +{ + if (authdir_mode_bridge(options)) + return 0; + return authdir_mode(options); +} +/** Return true iff we are an authoritative directory server that + * tests reachability of the descriptors it learns about. + */ +int +authdir_mode_tests_reachability(const or_options_t *options) +{ + return authdir_mode(options); +} +/** Return true iff we believe ourselves to be a bridge authoritative + * directory server. + */ +int +authdir_mode_bridge(const or_options_t *options) +{ + return authdir_mode(options) && options->BridgeAuthoritativeDir != 0; +} + +/** Return true iff we are trying to be a server. + */ +MOCK_IMPL(int, +server_mode,(const or_options_t *options)) +{ + if (options->ClientOnly) return 0; + return (options->ORPort_set); +} + +/** Return true iff we are trying to be a non-bridge server. + */ +MOCK_IMPL(int, +public_server_mode,(const or_options_t *options)) +{ + if (!server_mode(options)) return 0; + return (!options->BridgeRelay); +} + +/** Return true iff the combination of options in <b>options</b> and parameters + * in the consensus mean that we don't want to allow exits from circuits + * we got from addresses not known to be servers. */ +int +should_refuse_unknown_exits(const or_options_t *options) +{ + if (options->RefuseUnknownExits != -1) { + return options->RefuseUnknownExits; + } else { + return networkstatus_get_param(NULL, "refuseunknownexits", 1, 0, 1); + } +} + +/** Remember if we've advertised ourselves to the dirservers. */ +static int server_is_advertised=0; + +/** Return true iff we have published our descriptor lately. + */ +MOCK_IMPL(int, +advertised_server_mode,(void)) +{ + return server_is_advertised; +} + +/** + * Called with a boolean: set whether we have recently published our + * descriptor. + */ +static void +set_server_advertised(int s) +{ + server_is_advertised = s; +} + +/** Return true iff we are trying to proxy client connections. */ +int +proxy_mode(const or_options_t *options) +{ + (void)options; + SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { + if (p->type == CONN_TYPE_AP_LISTENER || + p->type == CONN_TYPE_AP_TRANS_LISTENER || + p->type == CONN_TYPE_AP_DNS_LISTENER || + p->type == CONN_TYPE_AP_NATD_LISTENER) + return 1; + } SMARTLIST_FOREACH_END(p); + return 0; +} + +/** Decide if we're a publishable server. We are a publishable server if: + * - We don't have the ClientOnly option set + * and + * - We have the PublishServerDescriptor option set to non-empty + * and + * - We have ORPort set + * and + * - We believe our ORPort and DirPort (if present) are reachable from + * the outside; or + * - We believe our ORPort is reachable from the outside, and we can't + * check our DirPort because the consensus has no exits; or + * - We are an authoritative directory server. + */ +static int +decide_if_publishable_server(void) +{ + const or_options_t *options = get_options(); + + if (options->ClientOnly) + return 0; + if (options->PublishServerDescriptor_ == NO_DIRINFO) + return 0; + if (!server_mode(options)) + return 0; + if (authdir_mode(options)) + return 1; + if (!router_get_advertised_or_port(options)) + return 0; + if (!check_whether_orport_reachable(options)) + return 0; + if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL) { + /* All set: there are no exits in the consensus (maybe this is a tiny + * test network), so we can't check our DirPort reachability. */ + return 1; + } else { + return check_whether_dirport_reachable(options); + } +} + +/** Initiate server descriptor upload as reasonable (if server is publishable, + * etc). <b>force</b> is as for router_upload_dir_desc_to_dirservers. + * + * We need to rebuild the descriptor if it's dirty even if we're not + * uploading, because our reachability testing *uses* our descriptor to + * determine what IP address and ports to test. + */ +void +consider_publishable_server(int force) +{ + int rebuilt; + + if (!server_mode(get_options())) + return; + + rebuilt = router_rebuild_descriptor(0); + if (decide_if_publishable_server()) { + set_server_advertised(1); + if (rebuilt == 0) + router_upload_dir_desc_to_dirservers(force); + } else { + set_server_advertised(0); + } +} + +/** Return the port of the first active listener of type + * <b>listener_type</b>. */ +/** XXX not a very good interface. it's not reliable when there are + multiple listeners. */ +uint16_t +router_get_active_listener_port_by_type_af(int listener_type, + sa_family_t family) +{ + /* Iterate all connections, find one of the right kind and return + the port. Not very sophisticated or fast, but effective. */ + smartlist_t *conns = get_connection_array(); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + if (conn->type == listener_type && !conn->marked_for_close && + conn->socket_family == family) { + return conn->port; + } + } SMARTLIST_FOREACH_END(conn); + + return 0; +} + +/** Return the port that we should advertise as our ORPort; this is either + * the one configured in the ORPort option, or the one we actually bound to + * if ORPort is "auto". + */ +uint16_t +router_get_advertised_or_port(const or_options_t *options) +{ + return router_get_advertised_or_port_by_af(options, AF_INET); +} + +/** As router_get_advertised_or_port(), but allows an address family argument. + */ +uint16_t +router_get_advertised_or_port_by_af(const or_options_t *options, + sa_family_t family) +{ + int port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + family); + (void)options; + + /* If the port is in 'auto' mode, we have to use + router_get_listener_port_by_type(). */ + if (port == CFG_AUTO_PORT) + return router_get_active_listener_port_by_type_af(CONN_TYPE_OR_LISTENER, + family); + + return port; +} + +/** Return the port that we should advertise as our DirPort; + * this is one of three possibilities: + * The one that is passed as <b>dirport</b> if the DirPort option is 0, or + * the one configured in the DirPort option, + * or the one we actually bound to if DirPort is "auto". */ +uint16_t +router_get_advertised_dir_port(const or_options_t *options, uint16_t dirport) +{ + int dirport_configured = get_primary_dir_port(); + (void)options; + + if (!dirport_configured) + return dirport; + + if (dirport_configured == CFG_AUTO_PORT) + return router_get_active_listener_port_by_type_af(CONN_TYPE_DIR_LISTENER, + AF_INET); + + return dirport_configured; +} + +/* + * OR descriptor generation. + */ + +/** My routerinfo. */ +static routerinfo_t *desc_routerinfo = NULL; +/** My extrainfo */ +static extrainfo_t *desc_extrainfo = NULL; +/** Why did we most recently decide to regenerate our descriptor? Used to + * tell the authorities why we're sending it to them. */ +static const char *desc_gen_reason = "uninitialized reason"; +/** Since when has our descriptor been "clean"? 0 if we need to regenerate it + * now. */ +static time_t desc_clean_since = 0; +/** Why did we mark the descriptor dirty? */ +static const char *desc_dirty_reason = "Tor just started"; +/** Boolean: do we need to regenerate the above? */ +static int desc_needs_upload = 0; + +/** OR only: If <b>force</b> is true, or we haven't uploaded this + * descriptor successfully yet, try to upload our signed descriptor to + * all the directory servers we know about. + */ +void +router_upload_dir_desc_to_dirservers(int force) +{ + const routerinfo_t *ri; + extrainfo_t *ei; + char *msg; + size_t desc_len, extra_len = 0, total_len; + dirinfo_type_t auth = get_options()->PublishServerDescriptor_; + + ri = router_get_my_routerinfo(); + if (!ri) { + log_info(LD_GENERAL, "No descriptor; skipping upload"); + return; + } + ei = router_get_my_extrainfo(); + if (auth == NO_DIRINFO) + return; + if (!force && !desc_needs_upload) + return; + + log_info(LD_OR, "Uploading relay descriptor to directory authorities%s", + force ? " (forced)" : ""); + + desc_needs_upload = 0; + + desc_len = ri->cache_info.signed_descriptor_len; + extra_len = ei ? ei->cache_info.signed_descriptor_len : 0; + total_len = desc_len + extra_len + 1; + msg = tor_malloc(total_len); + memcpy(msg, ri->cache_info.signed_descriptor_body, desc_len); + if (ei) { + memcpy(msg+desc_len, ei->cache_info.signed_descriptor_body, extra_len); + } + msg[desc_len+extra_len] = 0; + + directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_DIR, + (auth & BRIDGE_DIRINFO) ? + ROUTER_PURPOSE_BRIDGE : + ROUTER_PURPOSE_GENERAL, + auth, msg, desc_len, extra_len); + tor_free(msg); +} + +/** OR only: Check whether my exit policy says to allow connection to + * conn. Return 0 if we accept; non-0 if we reject. + */ +int +router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) +{ + const routerinfo_t *me = router_get_my_routerinfo(); + if (!me) /* make sure routerinfo exists */ + return -1; + + /* make sure it's resolved to something. this way we can't get a + 'maybe' below. */ + if (tor_addr_is_null(addr)) + return -1; + + /* look at router_get_my_routerinfo()->exit_policy for both the v4 and the + * v6 policies. The exit_policy field in router_get_my_routerinfo() is a + * bit unusual, in that it contains IPv6 and IPv6 entries. We don't want to + * look at router_get_my_routerinfo()->ipv6_exit_policy, since that's a port + * summary. */ + if ((tor_addr_family(addr) == AF_INET || + tor_addr_family(addr) == AF_INET6)) { + return compare_tor_addr_to_addr_policy(addr, port, + me->exit_policy) != ADDR_POLICY_ACCEPTED; +#if 0 + } else if (tor_addr_family(addr) == AF_INET6) { + return get_options()->IPv6Exit && + desc_routerinfo->ipv6_exit_policy && + compare_tor_addr_to_short_policy(addr, port, + me->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED; +#endif /* 0 */ + } else { + return -1; + } +} + +/** Return true iff my exit policy is reject *:*. Return -1 if we don't + * have a descriptor */ +MOCK_IMPL(int, +router_my_exit_policy_is_reject_star,(void)) +{ + if (!router_get_my_routerinfo()) /* make sure routerinfo exists */ + return -1; + + return router_get_my_routerinfo()->policy_is_reject_star; +} + +/** Return true iff I'm a server and <b>digest</b> is equal to + * my server identity key digest. */ +int +router_digest_is_me(const char *digest) +{ + return (server_identitykey && + tor_memeq(server_identitykey_digest, digest, DIGEST_LEN)); +} + +/** Return my identity digest. */ +const uint8_t * +router_get_my_id_digest(void) +{ + return (const uint8_t *)server_identitykey_digest; +} + +/** Return true iff I'm a server and <b>digest</b> is equal to + * my identity digest. */ +int +router_extrainfo_digest_is_me(const char *digest) +{ + extrainfo_t *ei = router_get_my_extrainfo(); + if (!ei) + return 0; + + return tor_memeq(digest, + ei->cache_info.signed_descriptor_digest, + DIGEST_LEN); +} + +/** A wrapper around router_digest_is_me(). */ +int +router_is_me(const routerinfo_t *router) +{ + return router_digest_is_me(router->cache_info.identity_digest); +} + +/** Return a routerinfo for this OR, rebuilding a fresh one if + * necessary. Return NULL on error, or if called on an OP. */ +MOCK_IMPL(const routerinfo_t *, +router_get_my_routerinfo,(void)) +{ + return router_get_my_routerinfo_with_err(NULL); +} + +/** Return routerinfo of this OR. Rebuild it from + * scratch if needed. Set <b>*err</b> to 0 on success or to + * appropriate TOR_ROUTERINFO_ERROR_* value on failure. + */ +MOCK_IMPL(const routerinfo_t *, +router_get_my_routerinfo_with_err,(int *err)) +{ + if (!server_mode(get_options())) { + if (err) + *err = TOR_ROUTERINFO_ERROR_NOT_A_SERVER; + + return NULL; + } + + if (!desc_clean_since) { + int rebuild_err = router_rebuild_descriptor(0); + if (rebuild_err < 0) { + if (err) + *err = rebuild_err; + + return NULL; + } + } + + if (!desc_routerinfo) { + if (err) + *err = TOR_ROUTERINFO_ERROR_DESC_REBUILDING; + + return NULL; + } + + if (err) + *err = 0; + + return desc_routerinfo; +} + +/** OR only: Return a signed server descriptor for this OR, rebuilding a fresh + * one if necessary. Return NULL on error. + */ +const char * +router_get_my_descriptor(void) +{ + const char *body; + const routerinfo_t *me = router_get_my_routerinfo(); + if (! me) + return NULL; + tor_assert(me->cache_info.saved_location == SAVED_NOWHERE); + body = signed_descriptor_get_body(&me->cache_info); + /* Make sure this is nul-terminated. */ + tor_assert(!body[me->cache_info.signed_descriptor_len]); + log_debug(LD_GENERAL,"my desc is '%s'", body); + return body; +} + +/** Return the extrainfo document for this OR, or NULL if we have none. + * Rebuilt it (and the server descriptor) if necessary. */ +extrainfo_t * +router_get_my_extrainfo(void) +{ + if (!server_mode(get_options())) + return NULL; + if (router_rebuild_descriptor(0)) + return NULL; + return desc_extrainfo; +} + +/** Return a human-readable string describing what triggered us to generate + * our current descriptor, or NULL if we don't know. */ +const char * +router_get_descriptor_gen_reason(void) +{ + return desc_gen_reason; +} + +/** A list of nicknames that we've warned about including in our family + * declaration verbatim rather than as digests. */ +static smartlist_t *warned_nonexistent_family = NULL; + +static int router_guess_address_from_dir_headers(uint32_t *guess); + +/** Make a current best guess at our address, either because + * it's configured in torrc, or because we've learned it from + * dirserver headers. Place the answer in *<b>addr</b> and return + * 0 on success, else return -1 if we have no guess. + * + * If <b>cache_only</b> is true, just return any cached answers, and + * don't try to get any new answers. + */ +MOCK_IMPL(int, +router_pick_published_address,(const or_options_t *options, uint32_t *addr, + int cache_only)) +{ + /* First, check the cached output from resolve_my_address(). */ + *addr = get_last_resolved_addr(); + if (*addr) + return 0; + + /* Second, consider doing a resolve attempt right here. */ + if (!cache_only) { + if (resolve_my_address(LOG_INFO, options, addr, NULL, NULL) >= 0) { + log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr)); + return 0; + } + } + + /* Third, check the cached output from router_new_address_suggestion(). */ + if (router_guess_address_from_dir_headers(addr) >= 0) + return 0; + + /* We have no useful cached answers. Return failure. */ + return -1; +} + +/* Like router_check_descriptor_address_consistency, but specifically for the + * ORPort or DirPort. + * listener_type is either CONN_TYPE_OR_LISTENER or CONN_TYPE_DIR_LISTENER. */ +static void +router_check_descriptor_address_port_consistency(uint32_t ipv4h_desc_addr, + int listener_type) +{ + tor_assert(listener_type == CONN_TYPE_OR_LISTENER || + listener_type == CONN_TYPE_DIR_LISTENER); + + /* The first advertised Port may be the magic constant CFG_AUTO_PORT. + */ + int port_v4_cfg = get_first_advertised_port_by_type_af(listener_type, + AF_INET); + if (port_v4_cfg != 0 && + !port_exists_by_type_addr32h_port(listener_type, + ipv4h_desc_addr, port_v4_cfg, 1)) { + const tor_addr_t *port_addr = get_first_advertised_addr_by_type_af( + listener_type, + AF_INET); + /* If we're building a descriptor with no advertised address, + * something is terribly wrong. */ + tor_assert(port_addr); + + tor_addr_t desc_addr; + char port_addr_str[TOR_ADDR_BUF_LEN]; + char desc_addr_str[TOR_ADDR_BUF_LEN]; + + tor_addr_to_str(port_addr_str, port_addr, TOR_ADDR_BUF_LEN, 0); + + tor_addr_from_ipv4h(&desc_addr, ipv4h_desc_addr); + tor_addr_to_str(desc_addr_str, &desc_addr, TOR_ADDR_BUF_LEN, 0); + + const char *listener_str = (listener_type == CONN_TYPE_OR_LISTENER ? + "OR" : "Dir"); + log_warn(LD_CONFIG, "The IPv4 %sPort address %s does not match the " + "descriptor address %s. If you have a static public IPv4 " + "address, use 'Address <IPv4>' and 'OutboundBindAddress " + "<IPv4>'. If you are behind a NAT, use two %sPort lines: " + "'%sPort <PublicPort> NoListen' and '%sPort <InternalPort> " + "NoAdvertise'.", + listener_str, port_addr_str, desc_addr_str, listener_str, + listener_str, listener_str); + } +} + +/* Tor relays only have one IPv4 address in the descriptor, which is derived + * from the Address torrc option, or guessed using various methods in + * router_pick_published_address(). + * Warn the operator if there is no ORPort on the descriptor address + * ipv4h_desc_addr. + * Warn the operator if there is no DirPort on the descriptor address. + * This catches a few common config errors: + * - operators who expect ORPorts and DirPorts to be advertised on the + * ports' listen addresses, rather than the torrc Address (or guessed + * addresses in the absence of an Address config). This includes + * operators who attempt to put their ORPort and DirPort on different + * addresses; + * - discrepancies between guessed addresses and configured listen + * addresses (when the Address option isn't set). + * If a listener is listening on all IPv4 addresses, it is assumed that it + * is listening on the configured Address, and no messages are logged. + * If an operators has specified NoAdvertise ORPorts in a NAT setting, + * no messages are logged, unless they have specified other advertised + * addresses. + * The message tells operators to configure an ORPort and DirPort that match + * the Address (using NoListen if needed). + */ +static void +router_check_descriptor_address_consistency(uint32_t ipv4h_desc_addr) +{ + router_check_descriptor_address_port_consistency(ipv4h_desc_addr, + CONN_TYPE_OR_LISTENER); + router_check_descriptor_address_port_consistency(ipv4h_desc_addr, + CONN_TYPE_DIR_LISTENER); +} + +/** Build a fresh routerinfo, signed server descriptor, and extra-info document + * for this OR. Set r to the generated routerinfo, e to the generated + * extra-info document. Return 0 on success, -1 on temporary error. Failure to + * generate an extra-info document is not an error and is indicated by setting + * e to NULL. Caller is responsible for freeing generated documents if 0 is + * returned. + */ +int +router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) +{ + routerinfo_t *ri; + extrainfo_t *ei; + uint32_t addr; + char platform[256]; + int hibernating = we_are_hibernating(); + const or_options_t *options = get_options(); + + if (router_pick_published_address(options, &addr, 0) < 0) { + log_warn(LD_CONFIG, "Don't know my address while generating descriptor"); + return TOR_ROUTERINFO_ERROR_NO_EXT_ADDR; + } + + /* Log a message if the address in the descriptor doesn't match the ORPort + * and DirPort addresses configured by the operator. */ + router_check_descriptor_address_consistency(addr); + + ri = tor_malloc_zero(sizeof(routerinfo_t)); + ri->cache_info.routerlist_index = -1; + ri->nickname = tor_strdup(options->Nickname); + ri->addr = addr; + ri->or_port = router_get_advertised_or_port(options); + ri->dir_port = router_get_advertised_dir_port(options, 0); + ri->supports_tunnelled_dir_requests = + directory_permits_begindir_requests(options); + ri->cache_info.published_on = time(NULL); + ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from + * main thread */ + ri->onion_curve25519_pkey = + tor_memdup(&get_current_curve25519_keypair()->pubkey, + sizeof(curve25519_public_key_t)); + + /* For now, at most one IPv6 or-address is being advertised. */ + { + const port_cfg_t *ipv6_orport = NULL; + SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { + if (p->type == CONN_TYPE_OR_LISTENER && + ! p->server_cfg.no_advertise && + ! p->server_cfg.bind_ipv4_only && + tor_addr_family(&p->addr) == AF_INET6) { + /* Like IPv4, if the relay is configured using the default + * authorities, disallow internal IPs. Otherwise, allow them. */ + const int default_auth = using_default_dir_authorities(options); + if (! tor_addr_is_internal(&p->addr, 0) || ! default_auth) { + ipv6_orport = p; + break; + } else { + char addrbuf[TOR_ADDR_BUF_LEN]; + log_warn(LD_CONFIG, + "Unable to use configured IPv6 address \"%s\" in a " + "descriptor. Skipping it. " + "Try specifying a globally reachable address explicitly.", + tor_addr_to_str(addrbuf, &p->addr, sizeof(addrbuf), 1)); + } + } + } SMARTLIST_FOREACH_END(p); + if (ipv6_orport) { + tor_addr_copy(&ri->ipv6_addr, &ipv6_orport->addr); + ri->ipv6_orport = ipv6_orport->port; + } + } + + ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key()); + if (crypto_pk_get_digest(ri->identity_pkey, + ri->cache_info.identity_digest)<0) { + routerinfo_free(ri); + return TOR_ROUTERINFO_ERROR_DIGEST_FAILED; + } + ri->cache_info.signing_key_cert = + tor_cert_dup(get_master_signing_key_cert()); + + get_platform_str(platform, sizeof(platform)); + ri->platform = tor_strdup(platform); + + ri->protocol_list = tor_strdup(protover_get_supported_protocols()); + + /* compute ri->bandwidthrate as the min of various options */ + ri->bandwidthrate = get_effective_bwrate(options); + + /* and compute ri->bandwidthburst similarly */ + ri->bandwidthburst = get_effective_bwburst(options); + + /* Report bandwidth, unless we're hibernating or shutting down */ + ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess(); + + if (dns_seems_to_be_broken() || has_dns_init_failed()) { + /* DNS is screwed up; don't claim to be an exit. */ + policies_exit_policy_append_reject_star(&ri->exit_policy); + } else { + policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr, + &ri->exit_policy); + } + ri->policy_is_reject_star = + policy_is_reject_star(ri->exit_policy, AF_INET, 1) && + policy_is_reject_star(ri->exit_policy, AF_INET6, 1); + + if (options->IPv6Exit) { + char *p_tmp = policy_summarize(ri->exit_policy, AF_INET6); + if (p_tmp) + ri->ipv6_exit_policy = parse_short_policy(p_tmp); + tor_free(p_tmp); + } + + if (options->MyFamily && ! options->BridgeRelay) { + if (!warned_nonexistent_family) + warned_nonexistent_family = smartlist_new(); + ri->declared_family = smartlist_new(); + config_line_t *family; + for (family = options->MyFamily; family; family = family->next) { + char *name = family->value; + const node_t *member; + if (!strcasecmp(name, options->Nickname)) + continue; /* Don't list ourself, that's redundant */ + else + member = node_get_by_nickname(name, 0); + if (!member) { + int is_legal = is_legal_nickname_or_hexdigest(name); + if (!smartlist_contains_string(warned_nonexistent_family, name) && + !is_legal_hexdigest(name)) { + if (is_legal) + log_warn(LD_CONFIG, + "I have no descriptor for the router named \"%s\" in my " + "declared family; I'll use the nickname as is, but " + "this may confuse clients.", name); + else + log_warn(LD_CONFIG, "There is a router named \"%s\" in my " + "declared family, but that isn't a legal nickname. " + "Skipping it.", escaped(name)); + smartlist_add_strdup(warned_nonexistent_family, name); + } + if (is_legal) { + smartlist_add_strdup(ri->declared_family, name); + } + } else if (router_digest_is_me(member->identity)) { + /* Don't list ourself in our own family; that's redundant */ + /* XXX shouldn't be possible */ + } else { + char *fp = tor_malloc(HEX_DIGEST_LEN+2); + fp[0] = '$'; + base16_encode(fp+1,HEX_DIGEST_LEN+1, + member->identity, DIGEST_LEN); + smartlist_add(ri->declared_family, fp); + if (smartlist_contains_string(warned_nonexistent_family, name)) + smartlist_string_remove(warned_nonexistent_family, name); + } + } + + /* remove duplicates from the list */ + smartlist_sort_strings(ri->declared_family); + smartlist_uniq_strings(ri->declared_family); + } + + /* Now generate the extrainfo. */ + ei = tor_malloc_zero(sizeof(extrainfo_t)); + ei->cache_info.is_extrainfo = 1; + strlcpy(ei->nickname, get_options()->Nickname, sizeof(ei->nickname)); + ei->cache_info.published_on = ri->cache_info.published_on; + ei->cache_info.signing_key_cert = + tor_cert_dup(get_master_signing_key_cert()); + + memcpy(ei->cache_info.identity_digest, ri->cache_info.identity_digest, + DIGEST_LEN); + if (extrainfo_dump_to_string(&ei->cache_info.signed_descriptor_body, + ei, get_server_identity_key(), + get_master_signing_keypair()) < 0) { + log_warn(LD_BUG, "Couldn't generate extra-info descriptor."); + extrainfo_free(ei); + ei = NULL; + } else { + ei->cache_info.signed_descriptor_len = + strlen(ei->cache_info.signed_descriptor_body); + router_get_extrainfo_hash(ei->cache_info.signed_descriptor_body, + ei->cache_info.signed_descriptor_len, + ei->cache_info.signed_descriptor_digest); + crypto_digest256((char*) ei->digest256, + ei->cache_info.signed_descriptor_body, + ei->cache_info.signed_descriptor_len, + DIGEST_SHA256); + } + + /* Now finish the router descriptor. */ + if (ei) { + memcpy(ri->cache_info.extra_info_digest, + ei->cache_info.signed_descriptor_digest, + DIGEST_LEN); + memcpy(ri->cache_info.extra_info_digest256, + ei->digest256, + DIGEST256_LEN); + } else { + /* ri was allocated with tor_malloc_zero, so there is no need to + * zero ri->cache_info.extra_info_digest here. */ + } + if (! (ri->cache_info.signed_descriptor_body = + router_dump_router_to_string(ri, get_server_identity_key(), + get_onion_key(), + get_current_curve25519_keypair(), + get_master_signing_keypair())) ) { + log_warn(LD_BUG, "Couldn't generate router descriptor."); + routerinfo_free(ri); + extrainfo_free(ei); + return TOR_ROUTERINFO_ERROR_CANNOT_GENERATE; + } + ri->cache_info.signed_descriptor_len = + strlen(ri->cache_info.signed_descriptor_body); + + ri->purpose = + options->BridgeRelay ? ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL; + if (options->BridgeRelay) { + /* Bridges shouldn't be able to send their descriptors unencrypted, + anyway, since they don't have a DirPort, and always connect to the + bridge authority anonymously. But just in case they somehow think of + sending them on an unencrypted connection, don't allow them to try. */ + ri->cache_info.send_unencrypted = 0; + if (ei) + ei->cache_info.send_unencrypted = 0; + } else { + ri->cache_info.send_unencrypted = 1; + if (ei) + ei->cache_info.send_unencrypted = 1; + } + + router_get_router_hash(ri->cache_info.signed_descriptor_body, + strlen(ri->cache_info.signed_descriptor_body), + ri->cache_info.signed_descriptor_digest); + + if (ei) { + tor_assert(! + routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei, + &ri->cache_info, NULL)); + } + + *r = ri; + *e = ei; + return 0; +} + +/** If <b>force</b> is true, or our descriptor is out-of-date, rebuild a fresh + * routerinfo, signed server descriptor, and extra-info document for this OR. + * Return 0 on success, -1 on temporary error. + */ +int +router_rebuild_descriptor(int force) +{ + int err = 0; + routerinfo_t *ri; + extrainfo_t *ei; + uint32_t addr; + const or_options_t *options = get_options(); + + if (desc_clean_since && !force) + return 0; + + if (router_pick_published_address(options, &addr, 0) < 0 || + router_get_advertised_or_port(options) == 0) { + /* Stop trying to rebuild our descriptor every second. We'll + * learn that it's time to try again when ip_address_changed() + * marks it dirty. */ + desc_clean_since = time(NULL); + return TOR_ROUTERINFO_ERROR_DESC_REBUILDING; + } + + log_info(LD_OR, "Rebuilding relay descriptor%s", force ? " (forced)" : ""); + + err = router_build_fresh_descriptor(&ri, &ei); + if (err < 0) { + return err; + } + + routerinfo_free(desc_routerinfo); + desc_routerinfo = ri; + extrainfo_free(desc_extrainfo); + desc_extrainfo = ei; + + desc_clean_since = time(NULL); + desc_needs_upload = 1; + desc_gen_reason = desc_dirty_reason; + if (BUG(desc_gen_reason == NULL)) { + desc_gen_reason = "descriptor was marked dirty earlier, for no reason."; + } + desc_dirty_reason = NULL; + control_event_my_descriptor_changed(); + return 0; +} + +/** If our router descriptor ever goes this long without being regenerated + * because something changed, we force an immediate regenerate-and-upload. */ +#define FORCE_REGENERATE_DESCRIPTOR_INTERVAL (18*60*60) + +/** If our router descriptor seems to be missing or unacceptable according + * to the authorities, regenerate and reupload it _this_ often. */ +#define FAST_RETRY_DESCRIPTOR_INTERVAL (90*60) + +/** Mark descriptor out of date if it's been "too long" since we last tried + * to upload one. */ +void +mark_my_descriptor_dirty_if_too_old(time_t now) +{ + networkstatus_t *ns; + const routerstatus_t *rs; + const char *retry_fast_reason = NULL; /* Set if we should retry frequently */ + const time_t slow_cutoff = now - FORCE_REGENERATE_DESCRIPTOR_INTERVAL; + const time_t fast_cutoff = now - FAST_RETRY_DESCRIPTOR_INTERVAL; + + /* If it's already dirty, don't mark it. */ + if (! desc_clean_since) + return; + + /* If it's older than FORCE_REGENERATE_DESCRIPTOR_INTERVAL, it's always + * time to rebuild it. */ + if (desc_clean_since < slow_cutoff) { + mark_my_descriptor_dirty("time for new descriptor"); + return; + } + /* Now we see whether we want to be retrying frequently or no. The + * rule here is that we'll retry frequently if we aren't listed in the + * live consensus we have, or if the publication time of the + * descriptor listed for us in the consensus is very old. */ + ns = networkstatus_get_live_consensus(now); + if (ns) { + rs = networkstatus_vote_find_entry(ns, server_identitykey_digest); + if (rs == NULL) + retry_fast_reason = "not listed in consensus"; + else if (rs->published_on < slow_cutoff) + retry_fast_reason = "version listed in consensus is quite old"; + } + + if (retry_fast_reason && desc_clean_since < fast_cutoff) + mark_my_descriptor_dirty(retry_fast_reason); +} + +/** Call when the current descriptor is out of date. */ +void +mark_my_descriptor_dirty(const char *reason) +{ + const or_options_t *options = get_options(); + if (BUG(reason == NULL)) { + reason = "marked descriptor dirty for unspecified reason"; + } + if (server_mode(options) && options->PublishServerDescriptor_) + log_info(LD_OR, "Decided to publish new relay descriptor: %s", reason); + desc_clean_since = 0; + if (!desc_dirty_reason) + desc_dirty_reason = reason; +} + +/** How frequently will we republish our descriptor because of large (factor + * of 2) shifts in estimated bandwidth? Note: We don't use this constant + * if our previous bandwidth estimate was exactly 0. */ +#define MAX_BANDWIDTH_CHANGE_FREQ (3*60*60) + +/** Check whether bandwidth has changed a lot since the last time we announced + * bandwidth. If so, mark our descriptor dirty. */ +void +check_descriptor_bandwidth_changed(time_t now) +{ + static time_t last_changed = 0; + uint64_t prev, cur; + if (!router_get_my_routerinfo()) + return; + + prev = router_get_my_routerinfo()->bandwidthcapacity; + /* Consider ourselves to have zero bandwidth if we're hibernating or + * shutting down. */ + cur = we_are_hibernating() ? 0 : rep_hist_bandwidth_assess(); + if ((prev != cur && (!prev || !cur)) || + cur > prev*2 || + cur < prev/2) { + if (last_changed+MAX_BANDWIDTH_CHANGE_FREQ < now || !prev) { + log_info(LD_GENERAL, + "Measured bandwidth has changed; rebuilding descriptor."); + mark_my_descriptor_dirty("bandwidth has changed"); + last_changed = now; + } + } +} + +/** Note at log level severity that our best guess of address has changed from + * <b>prev</b> to <b>cur</b>. */ +static void +log_addr_has_changed(int severity, + const tor_addr_t *prev, + const tor_addr_t *cur, + const char *source) +{ + char addrbuf_prev[TOR_ADDR_BUF_LEN]; + char addrbuf_cur[TOR_ADDR_BUF_LEN]; + + if (tor_addr_to_str(addrbuf_prev, prev, sizeof(addrbuf_prev), 1) == NULL) + strlcpy(addrbuf_prev, "???", TOR_ADDR_BUF_LEN); + if (tor_addr_to_str(addrbuf_cur, cur, sizeof(addrbuf_cur), 1) == NULL) + strlcpy(addrbuf_cur, "???", TOR_ADDR_BUF_LEN); + + if (!tor_addr_is_null(prev)) + log_fn(severity, LD_GENERAL, + "Our IP Address has changed from %s to %s; " + "rebuilding descriptor (source: %s).", + addrbuf_prev, addrbuf_cur, source); + else + log_notice(LD_GENERAL, + "Guessed our IP address as %s (source: %s).", + addrbuf_cur, source); +} + +/** Check whether our own address as defined by the Address configuration + * has changed. This is for routers that get their address from a service + * like dyndns. If our address has changed, mark our descriptor dirty. */ +void +check_descriptor_ipaddress_changed(time_t now) +{ + uint32_t prev, cur; + const or_options_t *options = get_options(); + const char *method = NULL; + char *hostname = NULL; + + (void) now; + + if (router_get_my_routerinfo() == NULL) + return; + + /* XXXX ipv6 */ + prev = router_get_my_routerinfo()->addr; + if (resolve_my_address(LOG_INFO, options, &cur, &method, &hostname) < 0) { + log_info(LD_CONFIG,"options->Address didn't resolve into an IP."); + return; + } + + if (prev != cur) { + char *source; + tor_addr_t tmp_prev, tmp_cur; + + tor_addr_from_ipv4h(&tmp_prev, prev); + tor_addr_from_ipv4h(&tmp_cur, cur); + + tor_asprintf(&source, "METHOD=%s%s%s", method, + hostname ? " HOSTNAME=" : "", + hostname ? hostname : ""); + + log_addr_has_changed(LOG_NOTICE, &tmp_prev, &tmp_cur, source); + tor_free(source); + + ip_address_changed(0); + } + + tor_free(hostname); +} + +/** The most recently guessed value of our IP address, based on directory + * headers. */ +static tor_addr_t last_guessed_ip = TOR_ADDR_NULL; + +/** A directory server <b>d_conn</b> told us our IP address is + * <b>suggestion</b>. + * If this address is different from the one we think we are now, and + * if our computer doesn't actually know its IP address, then switch. */ +void +router_new_address_suggestion(const char *suggestion, + const dir_connection_t *d_conn) +{ + tor_addr_t addr; + uint32_t cur = 0; /* Current IPv4 address. */ + const or_options_t *options = get_options(); + + /* first, learn what the IP address actually is */ + if (tor_addr_parse(&addr, suggestion) == -1) { + log_debug(LD_DIR, "Malformed X-Your-Address-Is header %s. Ignoring.", + escaped(suggestion)); + return; + } + + log_debug(LD_DIR, "Got X-Your-Address-Is: %s.", suggestion); + + if (!server_mode(options)) { + tor_addr_copy(&last_guessed_ip, &addr); + return; + } + + /* XXXX ipv6 */ + cur = get_last_resolved_addr(); + if (cur || + resolve_my_address(LOG_INFO, options, &cur, NULL, NULL) >= 0) { + /* We're all set -- we already know our address. Great. */ + tor_addr_from_ipv4h(&last_guessed_ip, cur); /* store it in case we + need it later */ + return; + } + if (tor_addr_is_internal(&addr, 0)) { + /* Don't believe anybody who says our IP is, say, 127.0.0.1. */ + return; + } + if (tor_addr_eq(&d_conn->base_.addr, &addr)) { + /* Don't believe anybody who says our IP is their IP. */ + log_debug(LD_DIR, "A directory server told us our IP address is %s, " + "but they are just reporting their own IP address. Ignoring.", + suggestion); + return; + } + + /* Okay. We can't resolve our own address, and X-Your-Address-Is is giving + * us an answer different from what we had the last time we managed to + * resolve it. */ + if (!tor_addr_eq(&last_guessed_ip, &addr)) { + control_event_server_status(LOG_NOTICE, + "EXTERNAL_ADDRESS ADDRESS=%s METHOD=DIRSERV", + suggestion); + log_addr_has_changed(LOG_NOTICE, &last_guessed_ip, &addr, + d_conn->base_.address); + ip_address_changed(0); + tor_addr_copy(&last_guessed_ip, &addr); /* router_rebuild_descriptor() + will fetch it */ + } +} + +/** We failed to resolve our address locally, but we'd like to build + * a descriptor and publish / test reachability. If we have a guess + * about our address based on directory headers, answer it and return + * 0; else return -1. */ +static int +router_guess_address_from_dir_headers(uint32_t *guess) +{ + if (!tor_addr_is_null(&last_guessed_ip)) { + *guess = tor_addr_to_ipv4h(&last_guessed_ip); + return 0; + } + return -1; +} + +/** Set <b>platform</b> (max length <b>len</b>) to a NUL-terminated short + * string describing the version of Tor and the operating system we're + * currently running on. + */ +STATIC void +get_platform_str(char *platform, size_t len) +{ + tor_snprintf(platform, len, "Tor %s on %s", + get_short_version(), get_uname()); +} + +/* XXX need to audit this thing and count fenceposts. maybe + * refactor so we don't have to keep asking if we're + * near the end of maxlen? + */ +#define DEBUG_ROUTER_DUMP_ROUTER_TO_STRING + +/** OR only: Given a routerinfo for this router, and an identity key to sign + * with, encode the routerinfo as a signed server descriptor and return a new + * string encoding the result, or NULL on failure. + */ +char * +router_dump_router_to_string(routerinfo_t *router, + const crypto_pk_t *ident_key, + const crypto_pk_t *tap_key, + const curve25519_keypair_t *ntor_keypair, + const ed25519_keypair_t *signing_keypair) +{ + char *address = NULL; + char *onion_pkey = NULL; /* Onion key, PEM-encoded. */ + char *identity_pkey = NULL; /* Identity key, PEM-encoded. */ + char digest[DIGEST256_LEN]; + char published[ISO_TIME_LEN+1]; + char fingerprint[FINGERPRINT_LEN+1]; + char *extra_info_line = NULL; + size_t onion_pkeylen, identity_pkeylen; + char *family_line = NULL; + char *extra_or_address = NULL; + const or_options_t *options = get_options(); + smartlist_t *chunks = NULL; + char *output = NULL; + const int emit_ed_sigs = signing_keypair && + router->cache_info.signing_key_cert; + char *ed_cert_line = NULL; + char *rsa_tap_cc_line = NULL; + char *ntor_cc_line = NULL; + char *proto_line = NULL; + + /* Make sure the identity key matches the one in the routerinfo. */ + if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) { + log_warn(LD_BUG,"Tried to sign a router with a private key that didn't " + "match router's public key!"); + goto err; + } + if (emit_ed_sigs) { + if (!router->cache_info.signing_key_cert->signing_key_included || + !ed25519_pubkey_eq(&router->cache_info.signing_key_cert->signed_key, + &signing_keypair->pubkey)) { + log_warn(LD_BUG, "Tried to sign a router descriptor with a mismatched " + "ed25519 key chain %d", + router->cache_info.signing_key_cert->signing_key_included); + goto err; + } + } + + /* record our fingerprint, so we can include it in the descriptor */ + if (crypto_pk_get_fingerprint(router->identity_pkey, fingerprint, 1)<0) { + log_err(LD_BUG,"Error computing fingerprint"); + goto err; + } + + if (emit_ed_sigs) { + /* Encode ed25519 signing cert */ + char ed_cert_base64[256]; + char ed_fp_base64[ED25519_BASE64_LEN+1]; + if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64), + (const char*)router->cache_info.signing_key_cert->encoded, + router->cache_info.signing_key_cert->encoded_len, + BASE64_ENCODE_MULTILINE) < 0) { + log_err(LD_BUG,"Couldn't base64-encode signing key certificate!"); + goto err; + } + if (ed25519_public_to_base64(ed_fp_base64, + &router->cache_info.signing_key_cert->signing_key)<0) { + log_err(LD_BUG,"Couldn't base64-encode identity key\n"); + goto err; + } + tor_asprintf(&ed_cert_line, "identity-ed25519\n" + "-----BEGIN ED25519 CERT-----\n" + "%s" + "-----END ED25519 CERT-----\n" + "master-key-ed25519 %s\n", + ed_cert_base64, ed_fp_base64); + } + + /* PEM-encode the onion key */ + if (crypto_pk_write_public_key_to_string(router->onion_pkey, + &onion_pkey,&onion_pkeylen)<0) { + log_warn(LD_BUG,"write onion_pkey to string failed!"); + goto err; + } + + /* PEM-encode the identity key */ + if (crypto_pk_write_public_key_to_string(router->identity_pkey, + &identity_pkey,&identity_pkeylen)<0) { + log_warn(LD_BUG,"write identity_pkey to string failed!"); + goto err; + } + + /* Cross-certify with RSA key */ + if (tap_key && router->cache_info.signing_key_cert && + router->cache_info.signing_key_cert->signing_key_included) { + char buf[256]; + int tap_cc_len = 0; + uint8_t *tap_cc = + make_tap_onion_key_crosscert(tap_key, + &router->cache_info.signing_key_cert->signing_key, + router->identity_pkey, + &tap_cc_len); + if (!tap_cc) { + log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!"); + goto err; + } + + if (base64_encode(buf, sizeof(buf), (const char*)tap_cc, tap_cc_len, + BASE64_ENCODE_MULTILINE) < 0) { + log_warn(LD_BUG,"base64_encode(rsa_crosscert) failed!"); + tor_free(tap_cc); + goto err; + } + tor_free(tap_cc); + + tor_asprintf(&rsa_tap_cc_line, + "onion-key-crosscert\n" + "-----BEGIN CROSSCERT-----\n" + "%s" + "-----END CROSSCERT-----\n", buf); + } + + /* Cross-certify with onion keys */ + if (ntor_keypair && router->cache_info.signing_key_cert && + router->cache_info.signing_key_cert->signing_key_included) { + int sign = 0; + char buf[256]; + /* XXXX Base the expiration date on the actual onion key expiration time?*/ + tor_cert_t *cert = + make_ntor_onion_key_crosscert(ntor_keypair, + &router->cache_info.signing_key_cert->signing_key, + router->cache_info.published_on, + get_onion_key_lifetime(), &sign); + if (!cert) { + log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!"); + goto err; + } + tor_assert(sign == 0 || sign == 1); + + if (base64_encode(buf, sizeof(buf), + (const char*)cert->encoded, cert->encoded_len, + BASE64_ENCODE_MULTILINE)<0) { + log_warn(LD_BUG,"base64_encode(ntor_crosscert) failed!"); + tor_cert_free(cert); + goto err; + } + tor_cert_free(cert); + + tor_asprintf(&ntor_cc_line, + "ntor-onion-key-crosscert %d\n" + "-----BEGIN ED25519 CERT-----\n" + "%s" + "-----END ED25519 CERT-----\n", sign, buf); + } + + /* Encode the publication time. */ + format_iso_time(published, router->cache_info.published_on); + + if (router->declared_family && smartlist_len(router->declared_family)) { + char *family = smartlist_join_strings(router->declared_family, + " ", 0, NULL); + tor_asprintf(&family_line, "family %s\n", family); + tor_free(family); + } else { + family_line = tor_strdup(""); + } + + if (!tor_digest_is_zero(router->cache_info.extra_info_digest)) { + char extra_info_digest[HEX_DIGEST_LEN+1]; + base16_encode(extra_info_digest, sizeof(extra_info_digest), + router->cache_info.extra_info_digest, DIGEST_LEN); + if (!tor_digest256_is_zero(router->cache_info.extra_info_digest256)) { + char d256_64[BASE64_DIGEST256_LEN+1]; + digest256_to_base64(d256_64, router->cache_info.extra_info_digest256); + tor_asprintf(&extra_info_line, "extra-info-digest %s %s\n", + extra_info_digest, d256_64); + } else { + tor_asprintf(&extra_info_line, "extra-info-digest %s\n", + extra_info_digest); + } + } + + if (router->ipv6_orport && + tor_addr_family(&router->ipv6_addr) == AF_INET6) { + char addr[TOR_ADDR_BUF_LEN]; + const char *a; + a = tor_addr_to_str(addr, &router->ipv6_addr, sizeof(addr), 1); + if (a) { + tor_asprintf(&extra_or_address, + "or-address %s:%d\n", a, router->ipv6_orport); + log_debug(LD_OR, "My or-address line is <%s>", extra_or_address); + } + } + + if (router->protocol_list) { + tor_asprintf(&proto_line, "proto %s\n", router->protocol_list); + } else { + proto_line = tor_strdup(""); + } + + address = tor_dup_ip(router->addr); + chunks = smartlist_new(); + + /* Generate the easy portion of the router descriptor. */ + smartlist_add_asprintf(chunks, + "router %s %s %d 0 %d\n" + "%s" + "%s" + "platform %s\n" + "%s" + "published %s\n" + "fingerprint %s\n" + "uptime %ld\n" + "bandwidth %d %d %d\n" + "%s%s" + "onion-key\n%s" + "signing-key\n%s" + "%s%s" + "%s%s%s", + router->nickname, + address, + router->or_port, + router_should_advertise_dirport(options, router->dir_port), + ed_cert_line ? ed_cert_line : "", + extra_or_address ? extra_or_address : "", + router->platform, + proto_line, + published, + fingerprint, + get_uptime(), + (int) router->bandwidthrate, + (int) router->bandwidthburst, + (int) router->bandwidthcapacity, + extra_info_line ? extra_info_line : "", + (options->DownloadExtraInfo || options->V3AuthoritativeDir) ? + "caches-extra-info\n" : "", + onion_pkey, identity_pkey, + rsa_tap_cc_line ? rsa_tap_cc_line : "", + ntor_cc_line ? ntor_cc_line : "", + family_line, + we_are_hibernating() ? "hibernating 1\n" : "", + "hidden-service-dir\n"); + + if (options->ContactInfo && strlen(options->ContactInfo)) { + const char *ci = options->ContactInfo; + if (strchr(ci, '\n') || strchr(ci, '\r')) + ci = escaped(ci); + smartlist_add_asprintf(chunks, "contact %s\n", ci); + } + + if (options->BridgeRelay) { + const char *bd; + if (options->BridgeDistribution && strlen(options->BridgeDistribution)) { + bd = options->BridgeDistribution; + } else { + bd = "any"; + } + if (strchr(bd, '\n') || strchr(bd, '\r')) + bd = escaped(bd); + smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd); + } + + if (router->onion_curve25519_pkey) { + char kbuf[128]; + base64_encode(kbuf, sizeof(kbuf), + (const char *)router->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN, BASE64_ENCODE_MULTILINE); + smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); + } else { + /* Authorities will start rejecting relays without ntor keys in 0.2.9 */ + log_err(LD_BUG, "A relay must have an ntor onion key"); + goto err; + } + + /* Write the exit policy to the end of 's'. */ + if (!router->exit_policy || !smartlist_len(router->exit_policy)) { + smartlist_add_strdup(chunks, "reject *:*\n"); + } else if (router->exit_policy) { + char *exit_policy = router_dump_exit_policy_to_string(router,1,0); + + if (!exit_policy) + goto err; + + smartlist_add_asprintf(chunks, "%s\n", exit_policy); + tor_free(exit_policy); + } + + if (router->ipv6_exit_policy) { + char *p6 = write_short_policy(router->ipv6_exit_policy); + if (p6 && strcmp(p6, "reject 1-65535")) { + smartlist_add_asprintf(chunks, + "ipv6-policy %s\n", p6); + } + tor_free(p6); + } + + if (router_should_advertise_begindir(options, + router->supports_tunnelled_dir_requests)) { + smartlist_add_strdup(chunks, "tunnelled-dir-server\n"); + } + + /* Sign the descriptor with Ed25519 */ + if (emit_ed_sigs) { + smartlist_add_strdup(chunks, "router-sig-ed25519 "); + crypto_digest_smartlist_prefix(digest, DIGEST256_LEN, + ED_DESC_SIGNATURE_PREFIX, + chunks, "", DIGEST_SHA256); + ed25519_signature_t sig; + char buf[ED25519_SIG_BASE64_LEN+1]; + if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN, + signing_keypair) < 0) + goto err; + if (ed25519_signature_to_base64(buf, &sig) < 0) + goto err; + + smartlist_add_asprintf(chunks, "%s\n", buf); + } + + /* Sign the descriptor with RSA */ + smartlist_add_strdup(chunks, "router-signature\n"); + + crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); + + { + char *sig; + if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) { + log_warn(LD_BUG, "Couldn't sign router descriptor"); + goto err; + } + smartlist_add(chunks, sig); + } + + /* include a last '\n' */ + smartlist_add_strdup(chunks, "\n"); + + output = smartlist_join_strings(chunks, "", 0, NULL); + +#ifdef DEBUG_ROUTER_DUMP_ROUTER_TO_STRING + { + char *s_dup; + const char *cp; + routerinfo_t *ri_tmp; + cp = s_dup = tor_strdup(output); + ri_tmp = router_parse_entry_from_string(cp, NULL, 1, 0, NULL, NULL); + if (!ri_tmp) { + log_err(LD_BUG, + "We just generated a router descriptor we can't parse."); + log_err(LD_BUG, "Descriptor was: <<%s>>", output); + goto err; + } + tor_free(s_dup); + routerinfo_free(ri_tmp); + } +#endif /* defined(DEBUG_ROUTER_DUMP_ROUTER_TO_STRING) */ + + goto done; + + err: + tor_free(output); /* sets output to NULL */ + done: + if (chunks) { + SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); + smartlist_free(chunks); + } + tor_free(address); + tor_free(family_line); + tor_free(onion_pkey); + tor_free(identity_pkey); + tor_free(extra_or_address); + tor_free(ed_cert_line); + tor_free(rsa_tap_cc_line); + tor_free(ntor_cc_line); + tor_free(extra_info_line); + tor_free(proto_line); + + return output; +} + +/** + * OR only: Given <b>router</b>, produce a string with its exit policy. + * If <b>include_ipv4</b> is true, include IPv4 entries. + * If <b>include_ipv6</b> is true, include IPv6 entries. + */ +char * +router_dump_exit_policy_to_string(const routerinfo_t *router, + int include_ipv4, + int include_ipv6) +{ + if ((!router->exit_policy) || (router->policy_is_reject_star)) { + return tor_strdup("reject *:*"); + } + + return policy_dump_to_string(router->exit_policy, + include_ipv4, + include_ipv6); +} + +/** Copy the primary (IPv4) OR port (IP address and TCP port) for + * <b>router</b> into *<b>ap_out</b>. */ +void +router_get_prim_orport(const routerinfo_t *router, tor_addr_port_t *ap_out) +{ + tor_assert(ap_out != NULL); + tor_addr_from_ipv4h(&ap_out->addr, router->addr); + ap_out->port = router->or_port; +} + +/** Return 1 if any of <b>router</b>'s addresses are <b>addr</b>. + * Otherwise return 0. */ +int +router_has_addr(const routerinfo_t *router, const tor_addr_t *addr) +{ + return + tor_addr_eq_ipv4h(addr, router->addr) || + tor_addr_eq(&router->ipv6_addr, addr); +} + +int +router_has_orport(const routerinfo_t *router, const tor_addr_port_t *orport) +{ + return + (tor_addr_eq_ipv4h(&orport->addr, router->addr) && + orport->port == router->or_port) || + (tor_addr_eq(&orport->addr, &router->ipv6_addr) && + orport->port == router->ipv6_orport); +} + +/** Load the contents of <b>filename</b>, find the last line starting with + * <b>end_line</b>, ensure that its timestamp is not more than 25 hours in + * the past or more than 1 hour in the future with respect to <b>now</b>, + * and write the file contents starting with that line to *<b>out</b>. + * Return 1 for success, 0 if the file does not exist or is empty, or -1 + * if the file does not contain a line matching these criteria or other + * failure. */ +static int +load_stats_file(const char *filename, const char *end_line, time_t now, + char **out) +{ + int r = -1; + char *fname = get_datadir_fname(filename); + char *contents, *start = NULL, *tmp, timestr[ISO_TIME_LEN+1]; + time_t written; + switch (file_status(fname)) { + case FN_FILE: + /* X022 Find an alternative to reading the whole file to memory. */ + if ((contents = read_file_to_str(fname, 0, NULL))) { + tmp = strstr(contents, end_line); + /* Find last block starting with end_line */ + while (tmp) { + start = tmp; + tmp = strstr(tmp + 1, end_line); + } + if (!start) + goto notfound; + if (strlen(start) < strlen(end_line) + 1 + sizeof(timestr)) + goto notfound; + strlcpy(timestr, start + 1 + strlen(end_line), sizeof(timestr)); + if (parse_iso_time(timestr, &written) < 0) + goto notfound; + if (written < now - (25*60*60) || written > now + (1*60*60)) + goto notfound; + *out = tor_strdup(start); + r = 1; + } + notfound: + tor_free(contents); + break; + /* treat empty stats files as if the file doesn't exist */ + case FN_NOENT: + case FN_EMPTY: + r = 0; + break; + case FN_ERROR: + case FN_DIR: + default: + break; + } + tor_free(fname); + return r; +} + +/** Write the contents of <b>extrainfo</b> and aggregated statistics to + * *<b>s_out</b>, signing them with <b>ident_key</b>. Return 0 on + * success, negative on failure. */ +int +extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, + crypto_pk_t *ident_key, + const ed25519_keypair_t *signing_keypair) +{ + const or_options_t *options = get_options(); + char identity[HEX_DIGEST_LEN+1]; + char published[ISO_TIME_LEN+1]; + char digest[DIGEST_LEN]; + char *bandwidth_usage; + int result; + static int write_stats_to_extrainfo = 1; + char sig[DIROBJ_MAX_SIG_LEN+1]; + char *s = NULL, *pre, *contents, *cp, *s_dup = NULL; + time_t now = time(NULL); + smartlist_t *chunks = smartlist_new(); + extrainfo_t *ei_tmp = NULL; + const int emit_ed_sigs = signing_keypair && + extrainfo->cache_info.signing_key_cert; + char *ed_cert_line = NULL; + + base16_encode(identity, sizeof(identity), + extrainfo->cache_info.identity_digest, DIGEST_LEN); + format_iso_time(published, extrainfo->cache_info.published_on); + bandwidth_usage = rep_hist_get_bandwidth_lines(); + if (emit_ed_sigs) { + if (!extrainfo->cache_info.signing_key_cert->signing_key_included || + !ed25519_pubkey_eq(&extrainfo->cache_info.signing_key_cert->signed_key, + &signing_keypair->pubkey)) { + log_warn(LD_BUG, "Tried to sign a extrainfo descriptor with a " + "mismatched ed25519 key chain %d", + extrainfo->cache_info.signing_key_cert->signing_key_included); + goto err; + } + char ed_cert_base64[256]; + if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64), + (const char*)extrainfo->cache_info.signing_key_cert->encoded, + extrainfo->cache_info.signing_key_cert->encoded_len, + BASE64_ENCODE_MULTILINE) < 0) { + log_err(LD_BUG,"Couldn't base64-encode signing key certificate!"); + goto err; + } + tor_asprintf(&ed_cert_line, "identity-ed25519\n" + "-----BEGIN ED25519 CERT-----\n" + "%s" + "-----END ED25519 CERT-----\n", ed_cert_base64); + } else { + ed_cert_line = tor_strdup(""); + } + + tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n%s", + extrainfo->nickname, identity, + ed_cert_line, + published, bandwidth_usage); + smartlist_add(chunks, pre); + + if (geoip_is_loaded(AF_INET)) + smartlist_add_asprintf(chunks, "geoip-db-digest %s\n", + geoip_db_digest(AF_INET)); + if (geoip_is_loaded(AF_INET6)) + smartlist_add_asprintf(chunks, "geoip6-db-digest %s\n", + geoip_db_digest(AF_INET6)); + + if (options->ExtraInfoStatistics && write_stats_to_extrainfo) { + log_info(LD_GENERAL, "Adding stats to extra-info descriptor."); + if (options->DirReqStatistics && + load_stats_file("stats"PATH_SEPARATOR"dirreq-stats", + "dirreq-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + if (options->HiddenServiceStatistics && + load_stats_file("stats"PATH_SEPARATOR"hidserv-stats", + "hidserv-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + if (options->EntryStatistics && + load_stats_file("stats"PATH_SEPARATOR"entry-stats", + "entry-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + if (options->CellStatistics && + load_stats_file("stats"PATH_SEPARATOR"buffer-stats", + "cell-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + if (options->ExitPortStatistics && + load_stats_file("stats"PATH_SEPARATOR"exit-stats", + "exit-stats-end", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + if (options->ConnDirectionStatistics && + load_stats_file("stats"PATH_SEPARATOR"conn-stats", + "conn-bi-direct", now, &contents) > 0) { + smartlist_add(chunks, contents); + } + } + + if (options->PaddingStatistics) { + contents = rep_hist_get_padding_count_lines(); + if (contents) + smartlist_add(chunks, contents); + } + + /* Add information about the pluggable transports we support. */ + if (options->ServerTransportPlugin) { + char *pluggable_transports = pt_get_extra_info_descriptor_string(); + if (pluggable_transports) + smartlist_add(chunks, pluggable_transports); + } + + if (should_record_bridge_info(options) && write_stats_to_extrainfo) { + const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); + if (bridge_stats) { + smartlist_add_strdup(chunks, bridge_stats); + } + } + + if (emit_ed_sigs) { + char sha256_digest[DIGEST256_LEN]; + smartlist_add_strdup(chunks, "router-sig-ed25519 "); + crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, + ED_DESC_SIGNATURE_PREFIX, + chunks, "", DIGEST_SHA256); + ed25519_signature_t ed_sig; + char buf[ED25519_SIG_BASE64_LEN+1]; + if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN, + signing_keypair) < 0) + goto err; + if (ed25519_signature_to_base64(buf, &ed_sig) < 0) + goto err; + + smartlist_add_asprintf(chunks, "%s\n", buf); + } + + smartlist_add_strdup(chunks, "router-signature\n"); + s = smartlist_join_strings(chunks, "", 0, NULL); + + while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) { + /* So long as there are at least two chunks (one for the initial + * extra-info line and one for the router-signature), we can keep removing + * things. */ + if (smartlist_len(chunks) > 2) { + /* We remove the next-to-last element (remember, len-1 is the last + element), since we need to keep the router-signature element. */ + int idx = smartlist_len(chunks) - 2; + char *e = smartlist_get(chunks, idx); + smartlist_del_keeporder(chunks, idx); + log_warn(LD_GENERAL, "We just generated an extra-info descriptor " + "with statistics that exceeds the 50 KB " + "upload limit. Removing last added " + "statistics."); + tor_free(e); + tor_free(s); + s = smartlist_join_strings(chunks, "", 0, NULL); + } else { + log_warn(LD_BUG, "We just generated an extra-info descriptors that " + "exceeds the 50 KB upload limit."); + goto err; + } + } + + memset(sig, 0, sizeof(sig)); + if (router_get_extrainfo_hash(s, strlen(s), digest) < 0 || + router_append_dirobj_signature(sig, sizeof(sig), digest, DIGEST_LEN, + ident_key) < 0) { + log_warn(LD_BUG, "Could not append signature to extra-info " + "descriptor."); + goto err; + } + smartlist_add_strdup(chunks, sig); + tor_free(s); + s = smartlist_join_strings(chunks, "", 0, NULL); + + cp = s_dup = tor_strdup(s); + ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL, NULL); + if (!ei_tmp) { + if (write_stats_to_extrainfo) { + log_warn(LD_GENERAL, "We just generated an extra-info descriptor " + "with statistics that we can't parse. Not " + "adding statistics to this or any future " + "extra-info descriptors."); + write_stats_to_extrainfo = 0; + result = extrainfo_dump_to_string(s_out, extrainfo, ident_key, + signing_keypair); + goto done; + } else { + log_warn(LD_BUG, "We just generated an extrainfo descriptor we " + "can't parse."); + goto err; + } + } + + *s_out = s; + s = NULL; /* prevent free */ + result = 0; + goto done; + + err: + result = -1; + + done: + tor_free(s); + SMARTLIST_FOREACH(chunks, char *, chunk, tor_free(chunk)); + smartlist_free(chunks); + tor_free(s_dup); + tor_free(ed_cert_line); + extrainfo_free(ei_tmp); + tor_free(bandwidth_usage); + + return result; +} + +/** Return true iff <b>s</b> is a valid server nickname. (That is, a string + * containing between 1 and MAX_NICKNAME_LEN characters from + * LEGAL_NICKNAME_CHARACTERS.) */ +int +is_legal_nickname(const char *s) +{ + size_t len; + tor_assert(s); + len = strlen(s); + return len > 0 && len <= MAX_NICKNAME_LEN && + strspn(s,LEGAL_NICKNAME_CHARACTERS) == len; +} + +/** Return true iff <b>s</b> is a valid server nickname or + * hex-encoded identity-key digest. */ +int +is_legal_nickname_or_hexdigest(const char *s) +{ + if (*s!='$') + return is_legal_nickname(s); + else + return is_legal_hexdigest(s); +} + +/** Return true iff <b>s</b> is a valid hex-encoded identity-key + * digest. (That is, an optional $, followed by 40 hex characters, + * followed by either nothing, or = or ~ followed by a nickname, or + * a character other than =, ~, or a hex character.) + */ +int +is_legal_hexdigest(const char *s) +{ + size_t len; + tor_assert(s); + if (s[0] == '$') s++; + len = strlen(s); + if (len > HEX_DIGEST_LEN) { + if (s[HEX_DIGEST_LEN] == '=' || + s[HEX_DIGEST_LEN] == '~') { + if (!is_legal_nickname(s+HEX_DIGEST_LEN+1)) + return 0; + } else { + return 0; + } + } + return (len >= HEX_DIGEST_LEN && + strspn(s,HEX_CHARACTERS)==HEX_DIGEST_LEN); +} + +/** + * Longest allowed output of format_node_description, plus 1 character for + * NUL. This allows space for: + * "$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF~xxxxxxxxxxxxxxxxxxx at" + * " [ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]" + * plus a terminating NUL. + */ +#define NODE_DESC_BUF_LEN (MAX_VERBOSE_NICKNAME_LEN+4+TOR_ADDR_BUF_LEN) + +/** Use <b>buf</b> (which must be at least NODE_DESC_BUF_LEN bytes long) to + * hold a human-readable description of a node with identity digest + * <b>id_digest</b>, named-status <b>is_named</b>, nickname <b>nickname</b>, + * and address <b>addr</b> or <b>addr32h</b>. + * + * The <b>nickname</b> and <b>addr</b> fields are optional and may be set to + * NULL. The <b>addr32h</b> field is optional and may be set to 0. + * + * Return a pointer to the front of <b>buf</b>. + */ +const char * +format_node_description(char *buf, + const char *id_digest, + int is_named, + const char *nickname, + const tor_addr_t *addr, + uint32_t addr32h) +{ + char *cp; + + if (!buf) + return "<NULL BUFFER>"; + + buf[0] = '$'; + base16_encode(buf+1, HEX_DIGEST_LEN+1, id_digest, DIGEST_LEN); + cp = buf+1+HEX_DIGEST_LEN; + if (nickname) { + buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + strlcpy(buf+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); + cp += strlen(cp); + } + if (addr32h || addr) { + memcpy(cp, " at ", 4); + cp += 4; + if (addr) { + tor_addr_to_str(cp, addr, TOR_ADDR_BUF_LEN, 0); + } else { + struct in_addr in; + in.s_addr = htonl(addr32h); + tor_inet_ntoa(&in, cp, INET_NTOA_BUF_LEN); + } + } + return buf; +} + +/** Return a human-readable description of the routerinfo_t <b>ri</b>. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +const char * +router_describe(const routerinfo_t *ri) +{ + static char buf[NODE_DESC_BUF_LEN]; + + if (!ri) + return "<null>"; + return format_node_description(buf, + ri->cache_info.identity_digest, + 0, + ri->nickname, + NULL, + ri->addr); +} + +/** Return a human-readable description of the node_t <b>node</b>. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +const char * +node_describe(const node_t *node) +{ + static char buf[NODE_DESC_BUF_LEN]; + const char *nickname = NULL; + uint32_t addr32h = 0; + int is_named = 0; + + if (!node) + return "<null>"; + + if (node->rs) { + nickname = node->rs->nickname; + is_named = node->rs->is_named; + addr32h = node->rs->addr; + } else if (node->ri) { + nickname = node->ri->nickname; + addr32h = node->ri->addr; + } + + return format_node_description(buf, + node->identity, + is_named, + nickname, + NULL, + addr32h); +} + +/** Return a human-readable description of the routerstatus_t <b>rs</b>. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +const char * +routerstatus_describe(const routerstatus_t *rs) +{ + static char buf[NODE_DESC_BUF_LEN]; + + if (!rs) + return "<null>"; + return format_node_description(buf, + rs->identity_digest, + rs->is_named, + rs->nickname, + NULL, + rs->addr); +} + +/** Return a human-readable description of the extend_info_t <b>ei</b>. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +const char * +extend_info_describe(const extend_info_t *ei) +{ + static char buf[NODE_DESC_BUF_LEN]; + + if (!ei) + return "<null>"; + return format_node_description(buf, + ei->identity_digest, + 0, + ei->nickname, + &ei->addr, + 0); +} + +/** Set <b>buf</b> (which must have MAX_VERBOSE_NICKNAME_LEN+1 bytes) to the + * verbose representation of the identity of <b>router</b>. The format is: + * A dollar sign. + * The upper-case hexadecimal encoding of the SHA1 hash of router's identity. + * A "=" if the router is named (no longer implemented); a "~" if it is not. + * The router's nickname. + **/ +void +router_get_verbose_nickname(char *buf, const routerinfo_t *router) +{ + buf[0] = '$'; + base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, + DIGEST_LEN); + buf[1+HEX_DIGEST_LEN] = '~'; + strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); +} + +/** Forget that we have issued any router-related warnings, so that we'll + * warn again if we see the same errors. */ +void +router_reset_warnings(void) +{ + if (warned_nonexistent_family) { + SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); + smartlist_clear(warned_nonexistent_family); + } +} + +/** Given a router purpose, convert it to a string. Don't call this on + * ROUTER_PURPOSE_UNKNOWN: The whole point of that value is that we don't + * know its string representation. */ +const char * +router_purpose_to_string(uint8_t p) +{ + switch (p) + { + case ROUTER_PURPOSE_GENERAL: return "general"; + case ROUTER_PURPOSE_BRIDGE: return "bridge"; + case ROUTER_PURPOSE_CONTROLLER: return "controller"; + default: + tor_assert(0); + } + return NULL; +} + +/** Given a string, convert it to a router purpose. */ +uint8_t +router_purpose_from_string(const char *s) +{ + if (!strcmp(s, "general")) + return ROUTER_PURPOSE_GENERAL; + else if (!strcmp(s, "bridge")) + return ROUTER_PURPOSE_BRIDGE; + else if (!strcmp(s, "controller")) + return ROUTER_PURPOSE_CONTROLLER; + else + return ROUTER_PURPOSE_UNKNOWN; +} + +/** Release all static resources held in router.c */ +void +router_free_all(void) +{ + crypto_pk_free(onionkey); + crypto_pk_free(lastonionkey); + crypto_pk_free(server_identitykey); + crypto_pk_free(client_identitykey); + + tor_mutex_free(key_lock); + routerinfo_free(desc_routerinfo); + extrainfo_free(desc_extrainfo); + crypto_pk_free(authority_signing_key); + authority_cert_free(authority_key_certificate); + crypto_pk_free(legacy_signing_key); + authority_cert_free(legacy_key_certificate); + + memwipe(&curve25519_onion_key, 0, sizeof(curve25519_onion_key)); + memwipe(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); + + if (warned_nonexistent_family) { + SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); + smartlist_free(warned_nonexistent_family); + } +} + +/** Return a smartlist of tor_addr_port_t's with all the OR ports of + <b>ri</b>. Note that freeing of the items in the list as well as + the smartlist itself is the callers responsibility. */ +smartlist_t * +router_get_all_orports(const routerinfo_t *ri) +{ + tor_assert(ri); + node_t fake_node; + memset(&fake_node, 0, sizeof(fake_node)); + /* we don't modify ri, fake_node is passed as a const node_t * + */ + fake_node.ri = (routerinfo_t *)ri; + return node_get_all_orports(&fake_node); +} diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h new file mode 100644 index 0000000000..51ac365798 --- /dev/null +++ b/src/feature/relay/router.h @@ -0,0 +1,161 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file router.h + * \brief Header file for router.c. + **/ + +#ifndef TOR_ROUTER_H +#define TOR_ROUTER_H + +#include "lib/testsupport/testsupport.h" + +struct curve25519_keypair_t; +struct ed25519_keypair_t; + +#define TOR_ROUTERINFO_ERROR_NO_EXT_ADDR (-1) +#define TOR_ROUTERINFO_ERROR_CANNOT_PARSE (-2) +#define TOR_ROUTERINFO_ERROR_NOT_A_SERVER (-3) +#define TOR_ROUTERINFO_ERROR_DIGEST_FAILED (-4) +#define TOR_ROUTERINFO_ERROR_CANNOT_GENERATE (-5) +#define TOR_ROUTERINFO_ERROR_DESC_REBUILDING (-6) + +crypto_pk_t *get_onion_key(void); +time_t get_onion_key_set_at(void); +void set_server_identity_key(crypto_pk_t *k); +crypto_pk_t *get_server_identity_key(void); +int server_identity_key_is_set(void); +void set_client_identity_key(crypto_pk_t *k); +crypto_pk_t *get_tlsclient_identity_key(void); +int client_identity_key_is_set(void); +MOCK_DECL(authority_cert_t *, get_my_v3_authority_cert, (void)); +crypto_pk_t *get_my_v3_authority_signing_key(void); +authority_cert_t *get_my_v3_legacy_cert(void); +crypto_pk_t *get_my_v3_legacy_signing_key(void); +void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last); +void expire_old_onion_keys(void); +void rotate_onion_key(void); +crypto_pk_t *init_key_from_file(const char *fname, int generate, + int severity, int log_greeting); +void v3_authority_check_key_expiry(void); +int get_onion_key_lifetime(void); +int get_onion_key_grace_period(void); + +di_digest256_map_t *construct_ntor_key_map(void); +void ntor_key_map_free_(di_digest256_map_t *map); +#define ntor_key_map_free(map) \ + FREE_AND_NULL(di_digest256_map_t, ntor_key_map_free_, (map)) + +int router_initialize_tls_context(void); +int init_keys(void); +int init_keys_client(void); + +int check_whether_orport_reachable(const or_options_t *options); +int check_whether_dirport_reachable(const or_options_t *options); +int dir_server_mode(const or_options_t *options); +void router_do_reachability_checks(int test_or, int test_dir); +void router_orport_found_reachable(void); +void router_dirport_found_reachable(void); +void router_perform_bandwidth_test(int num_circs, time_t now); + +int net_is_disabled(void); +int net_is_completely_disabled(void); + +int authdir_mode(const or_options_t *options); +int authdir_mode_handles_descs(const or_options_t *options, int purpose); +int authdir_mode_publishes_statuses(const or_options_t *options); +int authdir_mode_tests_reachability(const or_options_t *options); +int authdir_mode_bridge(const or_options_t *options); + +uint16_t router_get_active_listener_port_by_type_af(int listener_type, + sa_family_t family); +uint16_t router_get_advertised_or_port(const or_options_t *options); +uint16_t router_get_advertised_or_port_by_af(const or_options_t *options, + sa_family_t family); +uint16_t router_get_advertised_dir_port(const or_options_t *options, + uint16_t dirport); + +MOCK_DECL(int, server_mode, (const or_options_t *options)); +MOCK_DECL(int, public_server_mode, (const or_options_t *options)); +MOCK_DECL(int, advertised_server_mode, (void)); +int proxy_mode(const or_options_t *options); +void consider_publishable_server(int force); +int should_refuse_unknown_exits(const or_options_t *options); + +void router_upload_dir_desc_to_dirservers(int force); +void mark_my_descriptor_dirty_if_too_old(time_t now); +void mark_my_descriptor_dirty(const char *reason); +void check_descriptor_bandwidth_changed(time_t now); +void check_descriptor_ipaddress_changed(time_t now); +void router_new_address_suggestion(const char *suggestion, + const dir_connection_t *d_conn); +int router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port); +MOCK_DECL(int, router_my_exit_policy_is_reject_star,(void)); +MOCK_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); +MOCK_DECL(const routerinfo_t *, router_get_my_routerinfo_with_err,(int *err)); +extrainfo_t *router_get_my_extrainfo(void); +const char *router_get_my_descriptor(void); +const char *router_get_descriptor_gen_reason(void); +int router_digest_is_me(const char *digest); +const uint8_t *router_get_my_id_digest(void); +int router_extrainfo_digest_is_me(const char *digest); +int router_is_me(const routerinfo_t *router); +MOCK_DECL(int,router_pick_published_address,(const or_options_t *options, + uint32_t *addr, + int cache_only)); +int router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e); +int router_rebuild_descriptor(int force); +char *router_dump_router_to_string(routerinfo_t *router, + const crypto_pk_t *ident_key, + const crypto_pk_t *tap_key, + const struct curve25519_keypair_t *ntor_keypair, + const struct ed25519_keypair_t *signing_keypair); +char *router_dump_exit_policy_to_string(const routerinfo_t *router, + int include_ipv4, + int include_ipv6); +void router_get_prim_orport(const routerinfo_t *router, + tor_addr_port_t *addr_port_out); +void router_get_pref_orport(const routerinfo_t *router, + tor_addr_port_t *addr_port_out); +void router_get_pref_ipv6_orport(const routerinfo_t *router, + tor_addr_port_t *addr_port_out); +int router_ipv6_preferred(const routerinfo_t *router); +int router_has_addr(const routerinfo_t *router, const tor_addr_t *addr); +int router_has_orport(const routerinfo_t *router, + const tor_addr_port_t *orport); +int extrainfo_dump_to_string(char **s, extrainfo_t *extrainfo, + crypto_pk_t *ident_key, + const struct ed25519_keypair_t *signing_keypair); +int is_legal_nickname(const char *s); +int is_legal_nickname_or_hexdigest(const char *s); +int is_legal_hexdigest(const char *s); + +const char *router_describe(const routerinfo_t *ri); +const char *node_describe(const node_t *node); +const char *routerstatus_describe(const routerstatus_t *ri); +const char *extend_info_describe(const extend_info_t *ei); + +const char *routerinfo_err_to_string(int err); +int routerinfo_err_is_transient(int err); + +void router_get_verbose_nickname(char *buf, const routerinfo_t *router); +void router_reset_warnings(void); +void router_reset_reachability(void); +void router_free_all(void); + +const char *router_purpose_to_string(uint8_t p); +uint8_t router_purpose_from_string(const char *s); + +smartlist_t *router_get_all_orports(const routerinfo_t *ri); + +#ifdef ROUTER_PRIVATE +/* Used only by router.c and test.c */ +STATIC void get_platform_str(char *platform, size_t len); +STATIC int router_write_fingerprint(int hashed); +#endif + +#endif /* !defined(TOR_ROUTER_H) */ diff --git a/src/feature/relay/routerkeys.c b/src/feature/relay/routerkeys.c new file mode 100644 index 0000000000..bb04a8b220 --- /dev/null +++ b/src/feature/relay/routerkeys.c @@ -0,0 +1,1413 @@ +/* Copyright (c) 2014-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file routerkeys.c + * + * \brief Functions and structures to handle generating and maintaining the + * set of keypairs necessary to be an OR. + * + * The keys handled here now are the Ed25519 keys that Tor relays use to sign + * descriptors, authenticate themselves on links, and identify one another + * uniquely. Other keys are maintained in router.c and rendservice.c. + * + * (TODO: The keys in router.c should go here too.) + */ + +#include "or/or.h" +#include "or/config.h" +#include "or/router.h" +#include "or/routerkeys.h" +#include "or/torcert.h" + +#include "lib/crypt_ops/crypto_pwbox.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/term/getpass.h" +#include "lib/tls/tortls.h" +#include "lib/crypt_ops/crypto_format.h" + +#define ENC_KEY_HEADER "Boxed Ed25519 key" +#define ENC_KEY_TAG "master" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* DOCDOC */ +static ssize_t +do_getpass(const char *prompt, char *buf, size_t buflen, + int twice, const or_options_t *options) +{ + if (options->keygen_force_passphrase == FORCE_PASSPHRASE_OFF) { + tor_assert(buflen); + buf[0] = 0; + return 0; + } + + char *prompt2 = NULL; + char *buf2 = NULL; + int fd = -1; + ssize_t length = -1; + + if (options->use_keygen_passphrase_fd) { + twice = 0; + fd = options->keygen_passphrase_fd; + length = read_all_from_fd(fd, buf, buflen-1); + if (length >= 0) + buf[length] = 0; + goto done_reading; + } + + if (twice) { + const char msg[] = "One more time:"; + size_t p2len = strlen(prompt) + 1; + if (p2len < sizeof(msg)) + p2len = sizeof(msg); + prompt2 = tor_malloc(p2len); + memset(prompt2, ' ', p2len); + memcpy(prompt2 + p2len - sizeof(msg), msg, sizeof(msg)); + + buf2 = tor_malloc_zero(buflen); + } + + while (1) { + length = tor_getpass(prompt, buf, buflen); + if (length < 0) + goto done_reading; + + if (! twice) + break; + + ssize_t length2 = tor_getpass(prompt2, buf2, buflen); + + if (length != length2 || tor_memneq(buf, buf2, length)) { + fprintf(stderr, "That didn't match.\n"); + } else { + break; + } + } + + done_reading: + if (twice) { + tor_free(prompt2); + memwipe(buf2, 0, buflen); + tor_free(buf2); + } + + if (options->keygen_force_passphrase == FORCE_PASSPHRASE_ON && length == 0) + return -1; + + return length; +} + +/* DOCDOC */ +int +read_encrypted_secret_key(ed25519_secret_key_t *out, + const char *fname) +{ + int r = -1; + uint8_t *secret = NULL; + size_t secret_len = 0; + char pwbuf[256]; + uint8_t encrypted_key[256]; + char *tag = NULL; + int saved_errno = 0; + + ssize_t encrypted_len = crypto_read_tagged_contents_from_file(fname, + ENC_KEY_HEADER, + &tag, + encrypted_key, + sizeof(encrypted_key)); + if (encrypted_len < 0) { + saved_errno = errno; + log_info(LD_OR, "%s is missing", fname); + r = 0; + goto done; + } + if (strcmp(tag, ENC_KEY_TAG)) { + saved_errno = EINVAL; + goto done; + } + + while (1) { + ssize_t pwlen = + do_getpass("Enter passphrase for master key:", pwbuf, sizeof(pwbuf), 0, + get_options()); + if (pwlen < 0) { + saved_errno = EINVAL; + goto done; + } + const int r_unbox = crypto_unpwbox(&secret, &secret_len, + encrypted_key, encrypted_len, + pwbuf, pwlen); + if (r_unbox == UNPWBOX_CORRUPTED) { + log_err(LD_OR, "%s is corrupted.", fname); + saved_errno = EINVAL; + goto done; + } else if (r_unbox == UNPWBOX_OKAY) { + break; + } + + /* Otherwise, passphrase is bad, so try again till user does ctrl-c or gets + * it right. */ + } + + if (secret_len != ED25519_SECKEY_LEN) { + log_err(LD_OR, "%s is corrupted.", fname); + saved_errno = EINVAL; + goto done; + } + memcpy(out->seckey, secret, ED25519_SECKEY_LEN); + r = 1; + + done: + memwipe(encrypted_key, 0, sizeof(encrypted_key)); + memwipe(pwbuf, 0, sizeof(pwbuf)); + tor_free(tag); + if (secret) { + memwipe(secret, 0, secret_len); + tor_free(secret); + } + if (saved_errno) + errno = saved_errno; + return r; +} + +/* DOCDOC */ +int +write_encrypted_secret_key(const ed25519_secret_key_t *key, + const char *fname) +{ + int r = -1; + char pwbuf0[256]; + uint8_t *encrypted_key = NULL; + size_t encrypted_len = 0; + + if (do_getpass("Enter new passphrase:", pwbuf0, sizeof(pwbuf0), 1, + get_options()) < 0) { + log_warn(LD_OR, "NO/failed passphrase"); + return -1; + } + + if (strlen(pwbuf0) == 0) { + if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_ON) + return -1; + else + return 0; + } + + if (crypto_pwbox(&encrypted_key, &encrypted_len, + key->seckey, sizeof(key->seckey), + pwbuf0, strlen(pwbuf0), 0) < 0) { + log_warn(LD_OR, "crypto_pwbox failed!?"); + goto done; + } + if (crypto_write_tagged_contents_to_file(fname, + ENC_KEY_HEADER, + ENC_KEY_TAG, + encrypted_key, encrypted_len) < 0) + goto done; + r = 1; + done: + if (encrypted_key) { + memwipe(encrypted_key, 0, encrypted_len); + tor_free(encrypted_key); + } + memwipe(pwbuf0, 0, sizeof(pwbuf0)); + return r; +} + +/* DOCDOC */ +static int +write_secret_key(const ed25519_secret_key_t *key, int encrypted, + const char *fname, + const char *fname_tag, + const char *encrypted_fname) +{ + if (encrypted) { + int r = write_encrypted_secret_key(key, encrypted_fname); + if (r == 1) { + /* Success! */ + + /* Try to unlink the unencrypted key, if any existed before */ + if (strcmp(fname, encrypted_fname)) + unlink(fname); + return r; + } else if (r != 0) { + /* Unrecoverable failure! */ + return r; + } + + fprintf(stderr, "Not encrypting the secret key.\n"); + } + return ed25519_seckey_write_to_file(key, fname, fname_tag); +} + +/** + * Read an ed25519 key and associated certificates from files beginning with + * <b>fname</b>, with certificate type <b>cert_type</b>. On failure, return + * NULL; on success return the keypair. + * + * If INIT_ED_KEY_CREATE is set in <b>flags</b>, then create the key (and + * certificate if requested) if it doesn't exist, and save it to disk. + * + * If INIT_ED_KEY_NEEDCERT is set in <b>flags</b>, load/create a certificate + * too and store it in *<b>cert_out</b>. Fail if the cert can't be + * found/created. To create a certificate, <b>signing_key</b> must be set to + * the key that should sign it; <b>now</b> to the current time, and + * <b>lifetime</b> to the lifetime of the key. + * + * If INIT_ED_KEY_REPLACE is set in <b>flags</b>, then create and save new key + * whether we can read the old one or not. + * + * If INIT_ED_KEY_EXTRA_STRONG is set in <b>flags</b>, set the extra_strong + * flag when creating the secret key. + * + * If INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT is set in <b>flags</b>, and + * we create a new certificate, create it with the signing key embedded. + * + * If INIT_ED_KEY_SPLIT is set in <b>flags</b>, and we create a new key, + * store the public key in a separate file from the secret key. + * + * If INIT_ED_KEY_MISSING_SECRET_OK is set in <b>flags</b>, and we find a + * public key file but no secret key file, return successfully anyway. + * + * If INIT_ED_KEY_OMIT_SECRET is set in <b>flags</b>, do not try to load a + * secret key unless no public key is found. Do not return a secret key. (but + * create and save one if needed). + * + * If INIT_ED_KEY_NO_LOAD_SECRET is set in <b>flags</b>, don't try to load + * a secret key, no matter what. + * + * If INIT_ED_KEY_TRY_ENCRYPTED is set, we look for an encrypted secret key + * and consider encrypting any new secret key. + * + * If INIT_ED_KEY_NO_REPAIR is set, and there is any issue loading the keys + * from disk _other than their absence_ (full or partial), we do not try to + * replace them. + * + * If INIT_ED_KEY_SUGGEST_KEYGEN is set, have log messages about failures + * refer to the --keygen option. + * + * If INIT_ED_KEY_EXPLICIT_FNAME is set, use the provided file name for the + * secret key file, encrypted or not. + */ +ed25519_keypair_t * +ed_key_init_from_file(const char *fname, uint32_t flags, + int severity, + const ed25519_keypair_t *signing_key, + time_t now, + time_t lifetime, + uint8_t cert_type, + struct tor_cert_st **cert_out) +{ + char *secret_fname = NULL; + char *encrypted_secret_fname = NULL; + char *public_fname = NULL; + char *cert_fname = NULL; + const char *loaded_secret_fname = NULL; + int created_pk = 0, created_sk = 0, created_cert = 0; + const int try_to_load = ! (flags & INIT_ED_KEY_REPLACE); + const int encrypt_key = !! (flags & INIT_ED_KEY_TRY_ENCRYPTED); + const int norepair = !! (flags & INIT_ED_KEY_NO_REPAIR); + const int split = !! (flags & INIT_ED_KEY_SPLIT); + const int omit_secret = !! (flags & INIT_ED_KEY_OMIT_SECRET); + const int offline_secret = !! (flags & INIT_ED_KEY_OFFLINE_SECRET); + const int explicit_fname = !! (flags & INIT_ED_KEY_EXPLICIT_FNAME); + + /* we don't support setting both of these flags at once. */ + tor_assert((flags & (INIT_ED_KEY_NO_REPAIR|INIT_ED_KEY_NEEDCERT)) != + (INIT_ED_KEY_NO_REPAIR|INIT_ED_KEY_NEEDCERT)); + + char tag[8]; + tor_snprintf(tag, sizeof(tag), "type%d", (int)cert_type); + + tor_cert_t *cert = NULL; + char *got_tag = NULL; + ed25519_keypair_t *keypair = tor_malloc_zero(sizeof(ed25519_keypair_t)); + + if (explicit_fname) { + secret_fname = tor_strdup(fname); + encrypted_secret_fname = tor_strdup(fname); + } else { + tor_asprintf(&secret_fname, "%s_secret_key", fname); + tor_asprintf(&encrypted_secret_fname, "%s_secret_key_encrypted", fname); + } + tor_asprintf(&public_fname, "%s_public_key", fname); + tor_asprintf(&cert_fname, "%s_cert", fname); + + /* Try to read the secret key. */ + int have_secret = 0; + int load_secret = try_to_load && + !offline_secret && + (!omit_secret || file_status(public_fname)==FN_NOENT); + if (load_secret) { + int rv = ed25519_seckey_read_from_file(&keypair->seckey, + &got_tag, secret_fname); + if (rv == 0) { + have_secret = 1; + loaded_secret_fname = secret_fname; + tor_assert(got_tag); + } else { + if (errno != ENOENT && norepair) { + tor_log(severity, LD_OR, "Unable to read %s: %s", secret_fname, + strerror(errno)); + goto err; + } + } + } + + /* Should we try for an encrypted key? */ + int have_encrypted_secret_file = 0; + if (!have_secret && try_to_load && encrypt_key) { + int r = read_encrypted_secret_key(&keypair->seckey, + encrypted_secret_fname); + if (r > 0) { + have_secret = 1; + have_encrypted_secret_file = 1; + tor_free(got_tag); /* convince coverity we aren't leaking */ + got_tag = tor_strdup(tag); + loaded_secret_fname = encrypted_secret_fname; + } else if (errno != ENOENT && norepair) { + tor_log(severity, LD_OR, "Unable to read %s: %s", + encrypted_secret_fname, strerror(errno)); + goto err; + } + } else { + if (try_to_load) { + /* Check if it's there anyway, so we don't replace it. */ + if (file_status(encrypted_secret_fname) != FN_NOENT) + have_encrypted_secret_file = 1; + } + } + + if (have_secret) { + if (strcmp(got_tag, tag)) { + tor_log(severity, LD_OR, "%s has wrong tag", loaded_secret_fname); + goto err; + } + /* Derive the public key */ + if (ed25519_public_key_generate(&keypair->pubkey, &keypair->seckey)<0) { + tor_log(severity, LD_OR, "%s can't produce a public key", + loaded_secret_fname); + goto err; + } + } + + /* If we do split keys here, try to read the pubkey. */ + int found_public = 0; + if (try_to_load && (!have_secret || split)) { + ed25519_public_key_t pubkey_tmp; + tor_free(got_tag); + found_public = ed25519_pubkey_read_from_file(&pubkey_tmp, + &got_tag, public_fname) == 0; + if (!found_public && errno != ENOENT && norepair) { + tor_log(severity, LD_OR, "Unable to read %s: %s", public_fname, + strerror(errno)); + goto err; + } + if (found_public && strcmp(got_tag, tag)) { + tor_log(severity, LD_OR, "%s has wrong tag", public_fname); + goto err; + } + if (found_public) { + if (have_secret) { + /* If we have a secret key and we're reloading the public key, + * the key must match! */ + if (! ed25519_pubkey_eq(&keypair->pubkey, &pubkey_tmp)) { + tor_log(severity, LD_OR, "%s does not match %s! If you are trying " + "to restore from backup, make sure you didn't mix up the " + "key files. If you are absolutely sure that %s is the right " + "key for this relay, delete %s or move it out of the way.", + public_fname, loaded_secret_fname, + loaded_secret_fname, public_fname); + goto err; + } + } else { + /* We only have the public key; better use that. */ + tor_assert(split); + memcpy(&keypair->pubkey, &pubkey_tmp, sizeof(pubkey_tmp)); + } + } else { + /* We have no public key file, but we do have a secret key, make the + * public key file! */ + if (have_secret) { + if (ed25519_pubkey_write_to_file(&keypair->pubkey, public_fname, tag) + < 0) { + tor_log(severity, LD_OR, "Couldn't repair %s", public_fname); + goto err; + } else { + tor_log(LOG_NOTICE, LD_OR, + "Found secret key but not %s. Regenerating.", + public_fname); + } + } + } + } + + /* If the secret key is absent and it's not allowed to be, fail. */ + if (!have_secret && found_public && + !(flags & INIT_ED_KEY_MISSING_SECRET_OK)) { + if (have_encrypted_secret_file) { + tor_log(severity, LD_OR, "We needed to load a secret key from %s, " + "but it was encrypted. Try 'tor --keygen' instead, so you " + "can enter the passphrase.", + secret_fname); + } else if (offline_secret) { + tor_log(severity, LD_OR, "We wanted to load a secret key from %s, " + "but you're keeping it offline. (OfflineMasterKey is set.)", + secret_fname); + } else { + tor_log(severity, LD_OR, "We needed to load a secret key from %s, " + "but couldn't find it. %s", secret_fname, + (flags & INIT_ED_KEY_SUGGEST_KEYGEN) ? + "If you're keeping your master secret key offline, you will " + "need to run 'tor --keygen' to generate new signing keys." : + "Did you forget to copy it over when you copied the rest of the " + "signing key material?"); + } + goto err; + } + + /* If it's absent, and we're not supposed to make a new keypair, fail. */ + if (!have_secret && !found_public && !(flags & INIT_ED_KEY_CREATE)) { + if (split) { + tor_log(severity, LD_OR, "No key found in %s or %s.", + secret_fname, public_fname); + } else { + tor_log(severity, LD_OR, "No key found in %s.", secret_fname); + } + goto err; + } + + /* If the secret key is absent, but the encrypted key would be present, + * that's an error */ + if (!have_secret && !found_public && have_encrypted_secret_file) { + tor_assert(!encrypt_key); + tor_log(severity, LD_OR, "Found an encrypted secret key, " + "but not public key file %s!", public_fname); + goto err; + } + + /* if it's absent, make a new keypair... */ + if (!have_secret && !found_public) { + tor_free(keypair); + keypair = ed_key_new(signing_key, flags, now, lifetime, + cert_type, &cert); + if (!keypair) { + tor_log(severity, LD_OR, "Couldn't create keypair"); + goto err; + } + created_pk = created_sk = created_cert = 1; + } + + /* Write it to disk if we're supposed to do with a new passphrase, or if + * we just created it. */ + if (created_sk || (have_secret && get_options()->change_key_passphrase)) { + if (write_secret_key(&keypair->seckey, + encrypt_key, + secret_fname, tag, encrypted_secret_fname) < 0 + || + (split && + ed25519_pubkey_write_to_file(&keypair->pubkey, public_fname, tag) < 0) + || + (cert && + crypto_write_tagged_contents_to_file(cert_fname, "ed25519v1-cert", + tag, cert->encoded, cert->encoded_len) < 0)) { + tor_log(severity, LD_OR, "Couldn't write keys or cert to file."); + goto err; + } + goto done; + } + + /* If we're not supposed to get a cert, we're done. */ + if (! (flags & INIT_ED_KEY_NEEDCERT)) + goto done; + + /* Read a cert. */ + tor_free(got_tag); + uint8_t certbuf[256]; + ssize_t cert_body_len = crypto_read_tagged_contents_from_file( + cert_fname, "ed25519v1-cert", + &got_tag, certbuf, sizeof(certbuf)); + if (cert_body_len >= 0 && !strcmp(got_tag, tag)) + cert = tor_cert_parse(certbuf, cert_body_len); + + /* If we got it, check it to the extent we can. */ + int bad_cert = 0; + + if (! cert) { + tor_log(severity, LD_OR, "Cert was unparseable"); + bad_cert = 1; + } else if (!tor_memeq(cert->signed_key.pubkey, keypair->pubkey.pubkey, + ED25519_PUBKEY_LEN)) { + tor_log(severity, LD_OR, "Cert was for wrong key"); + bad_cert = 1; + } else if (signing_key && + tor_cert_checksig(cert, &signing_key->pubkey, now) < 0) { + tor_log(severity, LD_OR, "Can't check certificate: %s", + tor_cert_describe_signature_status(cert)); + bad_cert = 1; + } else if (cert->cert_expired) { + tor_log(severity, LD_OR, "Certificate is expired"); + bad_cert = 1; + } else if (signing_key && cert->signing_key_included && + ! ed25519_pubkey_eq(&signing_key->pubkey, &cert->signing_key)) { + tor_log(severity, LD_OR, "Certificate signed by unexpectd key!"); + bad_cert = 1; + } + + if (bad_cert) { + tor_cert_free(cert); + cert = NULL; + } + + /* If we got a cert, we're done. */ + if (cert) + goto done; + + /* If we didn't get a cert, and we're not supposed to make one, fail. */ + if (!signing_key || !(flags & INIT_ED_KEY_CREATE)) { + tor_log(severity, LD_OR, "Without signing key, can't create certificate"); + goto err; + } + + /* We have keys but not a certificate, so make one. */ + uint32_t cert_flags = 0; + if (flags & INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT) + cert_flags |= CERT_FLAG_INCLUDE_SIGNING_KEY; + cert = tor_cert_create(signing_key, cert_type, + &keypair->pubkey, + now, lifetime, + cert_flags); + + if (! cert) { + tor_log(severity, LD_OR, "Couldn't create certificate"); + goto err; + } + + /* Write it to disk. */ + created_cert = 1; + if (crypto_write_tagged_contents_to_file(cert_fname, "ed25519v1-cert", + tag, cert->encoded, cert->encoded_len) < 0) { + tor_log(severity, LD_OR, "Couldn't write cert to disk."); + goto err; + } + + done: + if (cert_out) + *cert_out = cert; + else + tor_cert_free(cert); + + goto cleanup; + + err: + if (keypair) + memwipe(keypair, 0, sizeof(*keypair)); + tor_free(keypair); + tor_cert_free(cert); + if (cert_out) + *cert_out = NULL; + if (created_sk) + unlink(secret_fname); + if (created_pk) + unlink(public_fname); + if (created_cert) + unlink(cert_fname); + + cleanup: + tor_free(encrypted_secret_fname); + tor_free(secret_fname); + tor_free(public_fname); + tor_free(cert_fname); + tor_free(got_tag); + + return keypair; +} + +/** + * Create a new signing key and (optionally) certficiate; do not read or write + * from disk. See ed_key_init_from_file() for more information. + */ +ed25519_keypair_t * +ed_key_new(const ed25519_keypair_t *signing_key, + uint32_t flags, + time_t now, + time_t lifetime, + uint8_t cert_type, + struct tor_cert_st **cert_out) +{ + if (cert_out) + *cert_out = NULL; + + const int extra_strong = !! (flags & INIT_ED_KEY_EXTRA_STRONG); + ed25519_keypair_t *keypair = tor_malloc_zero(sizeof(ed25519_keypair_t)); + if (ed25519_keypair_generate(keypair, extra_strong) < 0) + goto err; + + if (! (flags & INIT_ED_KEY_NEEDCERT)) + return keypair; + + tor_assert(signing_key); + tor_assert(cert_out); + uint32_t cert_flags = 0; + if (flags & INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT) + cert_flags |= CERT_FLAG_INCLUDE_SIGNING_KEY; + tor_cert_t *cert = tor_cert_create(signing_key, cert_type, + &keypair->pubkey, + now, lifetime, + cert_flags); + if (! cert) + goto err; + + *cert_out = cert; + return keypair; + + err: + tor_free(keypair); + return NULL; +} + +static ed25519_keypair_t *master_identity_key = NULL; +static ed25519_keypair_t *master_signing_key = NULL; +static ed25519_keypair_t *current_auth_key = NULL; +static tor_cert_t *signing_key_cert = NULL; +static tor_cert_t *link_cert_cert = NULL; +static tor_cert_t *auth_key_cert = NULL; + +static uint8_t *rsa_ed_crosscert = NULL; +static size_t rsa_ed_crosscert_len = 0; +static time_t rsa_ed_crosscert_expiration = 0; + +/** + * Running as a server: load, reload, or refresh our ed25519 keys and + * certificates, creating and saving new ones as needed. + * + * Return -1 on failure; 0 on success if the signing key was not replaced; + * and 1 on success if the signing key was replaced. + */ +int +load_ed_keys(const or_options_t *options, time_t now) +{ + ed25519_keypair_t *id = NULL; + ed25519_keypair_t *sign = NULL; + ed25519_keypair_t *auth = NULL; + const ed25519_keypair_t *sign_signing_key_with_id = NULL; + const ed25519_keypair_t *use_signing = NULL; + const tor_cert_t *check_signing_cert = NULL; + tor_cert_t *sign_cert = NULL; + tor_cert_t *auth_cert = NULL; + int signing_key_changed = 0; + + // It is later than 1972, since otherwise there would be no C compilers. + // (Try to diagnose #22466.) + tor_assert_nonfatal(now >= 2 * 365 * 86400); + +#define FAIL(msg) do { \ + log_warn(LD_OR, (msg)); \ + goto err; \ + } while (0) +#define SET_KEY(key, newval) do { \ + if ((key) != (newval)) \ + ed25519_keypair_free(key); \ + key = (newval); \ + } while (0) +#define SET_CERT(cert, newval) do { \ + if ((cert) != (newval)) \ + tor_cert_free(cert); \ + cert = (newval); \ + } while (0) +#define HAPPENS_SOON(when, interval) \ + ((when) < now + (interval)) +#define EXPIRES_SOON(cert, interval) \ + (!(cert) || HAPPENS_SOON((cert)->valid_until, (interval))) + + /* XXXX support encrypted identity keys fully */ + + /* First try to get the signing key to see how it is. */ + { + char *fname = + options_get_keydir_fname(options, "ed25519_signing"); + sign = ed_key_init_from_file( + fname, + INIT_ED_KEY_NEEDCERT| + INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT, + LOG_INFO, + NULL, 0, 0, CERT_TYPE_ID_SIGNING, &sign_cert); + tor_free(fname); + check_signing_cert = sign_cert; + use_signing = sign; + } + + if (use_signing) { + /* We loaded a signing key with its certificate. */ + if (! master_signing_key) { + /* We didn't know one before! */ + signing_key_changed = 1; + } else if (! ed25519_pubkey_eq(&use_signing->pubkey, + &master_signing_key->pubkey) || + ! tor_memeq(use_signing->seckey.seckey, + master_signing_key->seckey.seckey, + ED25519_SECKEY_LEN)) { + /* We loaded a different signing key than the one we knew before. */ + signing_key_changed = 1; + } + } + + if (!use_signing && master_signing_key) { + /* We couldn't load a signing key, but we already had one loaded */ + check_signing_cert = signing_key_cert; + use_signing = master_signing_key; + } + + const int offline_master = + options->OfflineMasterKey && options->command != CMD_KEYGEN; + const int need_new_signing_key = + NULL == use_signing || + EXPIRES_SOON(check_signing_cert, 0) || + (options->command == CMD_KEYGEN && ! options->change_key_passphrase); + const int want_new_signing_key = + need_new_signing_key || + EXPIRES_SOON(check_signing_cert, options->TestingSigningKeySlop); + + /* We can only create a master key if we haven't been told that the + * master key will always be offline. Also, if we have a signing key, + * then we shouldn't make a new master ID key. */ + const int can_make_master_id_key = !offline_master && + NULL == use_signing; + + if (need_new_signing_key) { + log_notice(LD_OR, "It looks like I need to generate and sign a new " + "medium-term signing key, because %s. To do that, I " + "need to load%s the permanent master identity key. " + "If the master identity key was not moved or encrypted " + "with a passphrase, this will be done automatically and " + "no further action is required. Otherwise, provide the " + "necessary data using 'tor --keygen' to do it manually.", + (NULL == use_signing) ? "I don't have one" : + EXPIRES_SOON(check_signing_cert, 0) ? "the one I have is expired" : + "you asked me to make one with --keygen", + can_make_master_id_key ? " (or create)" : ""); + } else if (want_new_signing_key && !offline_master) { + log_notice(LD_OR, "It looks like I should try to generate and sign a " + "new medium-term signing key, because the one I have is " + "going to expire soon. To do that, I'm going to have to " + "try to load the permanent master identity key. " + "If the master identity key was not moved or encrypted " + "with a passphrase, this will be done automatically and " + "no further action is required. Otherwise, provide the " + "necessary data using 'tor --keygen' to do it manually."); + } else if (want_new_signing_key) { + log_notice(LD_OR, "It looks like I should try to generate and sign a " + "new medium-term signing key, because the one I have is " + "going to expire soon. But OfflineMasterKey is set, so I " + "won't try to load a permanent master identity key. You " + "will need to use 'tor --keygen' to make a new signing " + "key and certificate."); + } + + { + uint32_t flags = + (INIT_ED_KEY_SPLIT| + INIT_ED_KEY_EXTRA_STRONG|INIT_ED_KEY_NO_REPAIR); + if (can_make_master_id_key) + flags |= INIT_ED_KEY_CREATE; + if (! need_new_signing_key) + flags |= INIT_ED_KEY_MISSING_SECRET_OK; + if (! want_new_signing_key || offline_master) + flags |= INIT_ED_KEY_OMIT_SECRET; + if (offline_master) + flags |= INIT_ED_KEY_OFFLINE_SECRET; + if (options->command == CMD_KEYGEN) + flags |= INIT_ED_KEY_TRY_ENCRYPTED; + + /* Check/Create the key directory */ + if (create_keys_directory(options) < 0) + return -1; + + char *fname; + if (options->master_key_fname) { + fname = tor_strdup(options->master_key_fname); + flags |= INIT_ED_KEY_EXPLICIT_FNAME; + } else { + fname = options_get_keydir_fname(options, "ed25519_master_id"); + } + id = ed_key_init_from_file( + fname, + flags, + LOG_WARN, NULL, 0, 0, 0, NULL); + tor_free(fname); + if (!id) { + if (need_new_signing_key) { + if (offline_master) + FAIL("Can't load master identity key; OfflineMasterKey is set."); + else + FAIL("Missing identity key"); + } else { + log_warn(LD_OR, "Master public key was absent; inferring from " + "public key in signing certificate and saving to disk."); + tor_assert(check_signing_cert); + id = tor_malloc_zero(sizeof(*id)); + memcpy(&id->pubkey, &check_signing_cert->signing_key, + sizeof(ed25519_public_key_t)); + fname = options_get_keydir_fname(options, + "ed25519_master_id_public_key"); + if (ed25519_pubkey_write_to_file(&id->pubkey, fname, "type0") < 0) { + log_warn(LD_OR, "Error while attempting to write master public key " + "to disk"); + tor_free(fname); + goto err; + } + tor_free(fname); + } + } + if (tor_mem_is_zero((char*)id->seckey.seckey, sizeof(id->seckey))) + sign_signing_key_with_id = NULL; + else + sign_signing_key_with_id = id; + } + + if (master_identity_key && + !ed25519_pubkey_eq(&id->pubkey, &master_identity_key->pubkey)) { + FAIL("Identity key on disk does not match key we loaded earlier!"); + } + + if (need_new_signing_key && NULL == sign_signing_key_with_id) + FAIL("Can't load master key make a new signing key."); + + if (sign_cert) { + if (! sign_cert->signing_key_included) + FAIL("Loaded a signing cert with no key included!"); + if (! ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey)) + FAIL("The signing cert we have was not signed with the master key " + "we loaded!"); + if (tor_cert_checksig(sign_cert, &id->pubkey, 0) < 0) { + log_warn(LD_OR, "The signing cert we loaded was not signed " + "correctly: %s!", + tor_cert_describe_signature_status(sign_cert)); + goto err; + } + } + + if (want_new_signing_key && sign_signing_key_with_id) { + uint32_t flags = (INIT_ED_KEY_CREATE| + INIT_ED_KEY_REPLACE| + INIT_ED_KEY_EXTRA_STRONG| + INIT_ED_KEY_NEEDCERT| + INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT); + char *fname = + options_get_keydir_fname(options, "ed25519_signing"); + ed25519_keypair_free(sign); + tor_cert_free(sign_cert); + sign = ed_key_init_from_file(fname, + flags, LOG_WARN, + sign_signing_key_with_id, now, + options->SigningKeyLifetime, + CERT_TYPE_ID_SIGNING, &sign_cert); + tor_free(fname); + if (!sign) + FAIL("Missing signing key"); + use_signing = sign; + signing_key_changed = 1; + + tor_assert(sign_cert->signing_key_included); + tor_assert(ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey)); + tor_assert(ed25519_pubkey_eq(&sign_cert->signed_key, &sign->pubkey)); + } else if (want_new_signing_key) { + static ratelim_t missing_master = RATELIM_INIT(3600); + log_fn_ratelim(&missing_master, LOG_WARN, LD_OR, + "Signing key will expire soon, but I can't load the " + "master key to sign a new one!"); + } + + tor_assert(use_signing); + + /* At this point we no longer need our secret identity key. So wipe + * it, if we loaded it in the first place. */ + memwipe(id->seckey.seckey, 0, sizeof(id->seckey)); + + if (options->command == CMD_KEYGEN) + goto end; + + if (server_mode(options) && + (!rsa_ed_crosscert || + HAPPENS_SOON(rsa_ed_crosscert_expiration, 30*86400))) { + uint8_t *crosscert; + time_t expiration = now+6*30*86400; /* 6 months in the future. */ + ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey, + get_server_identity_key(), + expiration, + &crosscert); + tor_free(rsa_ed_crosscert); + rsa_ed_crosscert_len = crosscert_len; + rsa_ed_crosscert = crosscert; + rsa_ed_crosscert_expiration = expiration; + } + + if (!current_auth_key || + signing_key_changed || + EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop)) { + auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT, + now, + options->TestingAuthKeyLifetime, + CERT_TYPE_SIGNING_AUTH, &auth_cert); + + if (!auth) + FAIL("Can't create auth key"); + } + + /* We've generated or loaded everything. Put them in memory. */ + + end: + if (! master_identity_key) { + SET_KEY(master_identity_key, id); + } else { + tor_free(id); + } + if (sign) { + SET_KEY(master_signing_key, sign); + SET_CERT(signing_key_cert, sign_cert); + } + if (auth) { + SET_KEY(current_auth_key, auth); + SET_CERT(auth_key_cert, auth_cert); + } + + return signing_key_changed; + err: + ed25519_keypair_free(id); + ed25519_keypair_free(sign); + ed25519_keypair_free(auth); + tor_cert_free(sign_cert); + tor_cert_free(auth_cert); + return -1; +} + +/** + * Retrieve our currently-in-use Ed25519 link certificate and id certificate, + * and, if they would expire soon (based on the time <b>now</b>, generate new + * certificates (without embedding the public part of the signing key inside). + * If <b>force</b> is true, always generate a new certificate. + * + * The signed_key from the current id->signing certificate will be used to + * sign the new key within newly generated X509 certificate. + * + * Returns -1 upon error. Otherwise, returns 0 upon success (either when the + * current certificate is still valid, or when a new certificate was + * successfully generated, or no certificate was needed). + */ +int +generate_ed_link_cert(const or_options_t *options, time_t now, + int force) +{ + const tor_x509_cert_t *link_ = NULL, *id = NULL; + tor_cert_t *link_cert = NULL; + + if (tor_tls_get_my_certs(1, &link_, &id) < 0 || link_ == NULL) { + if (!server_mode(options)) { + /* No need to make an Ed25519->Link cert: we are a client */ + return 0; + } + log_warn(LD_OR, "Can't get my x509 link cert."); + return -1; + } + + const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_); + + if (force == 0 && + link_cert_cert && + ! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) && + fast_memeq(digests->d[DIGEST_SHA256], link_cert_cert->signed_key.pubkey, + DIGEST256_LEN)) { + return 0; + } + + ed25519_public_key_t dummy_key; + memcpy(dummy_key.pubkey, digests->d[DIGEST_SHA256], DIGEST256_LEN); + + link_cert = tor_cert_create(get_master_signing_keypair(), + CERT_TYPE_SIGNING_LINK, + &dummy_key, + now, + options->TestingLinkCertLifetime, 0); + + if (link_cert) { + SET_CERT(link_cert_cert, link_cert); + } + return 0; +} + +#undef FAIL +#undef SET_KEY +#undef SET_CERT + +/** + * Return 1 if any of the following are true: + * + * - if one of our Ed25519 signing, auth, or link certificates would expire + * soon w.r.t. the time <b>now</b>, + * - if we do not currently have a link certificate, or + * - if our cached Ed25519 link certificate is not same as the one we're + * currently using. + * + * Otherwise, returns 0. + */ +int +should_make_new_ed_keys(const or_options_t *options, const time_t now) +{ + if (!master_identity_key || + !master_signing_key || + !current_auth_key || + !link_cert_cert || + EXPIRES_SOON(signing_key_cert, options->TestingSigningKeySlop) || + EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop) || + EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop)) + return 1; + + const tor_x509_cert_t *link_ = NULL, *id = NULL; + + if (tor_tls_get_my_certs(1, &link_, &id) < 0 || link_ == NULL) + return 1; + + const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_); + + if (!fast_memeq(digests->d[DIGEST_SHA256], + link_cert_cert->signed_key.pubkey, + DIGEST256_LEN)) { + return 1; + } + + return 0; +} + +#undef EXPIRES_SOON +#undef HAPPENS_SOON + +#ifdef TOR_UNIT_TESTS +/* Helper for unit tests: populate the ed25519 keys without saving or + * loading */ +void +init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) +{ + routerkeys_free_all(); + +#define MAKEKEY(k) \ + k = tor_malloc_zero(sizeof(*k)); \ + if (ed25519_keypair_generate(k, 0) < 0) { \ + log_warn(LD_BUG, "Couldn't make a keypair"); \ + goto err; \ + } + MAKEKEY(master_identity_key); + MAKEKEY(master_signing_key); + MAKEKEY(current_auth_key); +#define MAKECERT(cert, signing, signed_, type, flags) \ + cert = tor_cert_create(signing, \ + type, \ + &signed_->pubkey, \ + time(NULL), 86400, \ + flags); \ + if (!cert) { \ + log_warn(LD_BUG, "Couldn't make a %s certificate!", #cert); \ + goto err; \ + } + + MAKECERT(signing_key_cert, + master_identity_key, master_signing_key, CERT_TYPE_ID_SIGNING, + CERT_FLAG_INCLUDE_SIGNING_KEY); + MAKECERT(auth_key_cert, + master_signing_key, current_auth_key, CERT_TYPE_SIGNING_AUTH, 0); + + if (generate_ed_link_cert(get_options(), time(NULL), 0) < 0) { + log_warn(LD_BUG, "Couldn't make link certificate"); + goto err; + } + + rsa_ed_crosscert_len = tor_make_rsa_ed25519_crosscert( + &master_identity_key->pubkey, + rsa_identity_key, + time(NULL)+86400, + &rsa_ed_crosscert); + + return; + + err: + routerkeys_free_all(); + tor_assert_nonfatal_unreached(); +} +#undef MAKEKEY +#undef MAKECERT +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Print the ISO8601-formated <b>expiration</b> for a certificate with + * some <b>description</b> to stdout. + * + * For example, for a signing certificate, this might print out: + * signing-cert-expiry: 2017-07-25 08:30:15 UTC + */ +static void +print_cert_expiration(const char *expiration, + const char *description) +{ + fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration); +} + +/** + * Log when a certificate, <b>cert</b>, with some <b>description</b> and + * stored in a file named <b>fname</b>, is going to expire. + */ +static void +log_ed_cert_expiration(const tor_cert_t *cert, + const char *description, + const char *fname) { + char expiration[ISO_TIME_LEN+1]; + + if (BUG(!cert)) { /* If the specified key hasn't been loaded */ + log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.", + description); + } else { + format_local_iso_time(expiration, cert->valid_until); + log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.", + description, fname, expiration); + print_cert_expiration(expiration, description); + } +} + +/** + * Log when our master signing key certificate expires. Used when tor is given + * the --key-expiration command-line option. + * + * Returns 0 on success and 1 on failure. + */ +static int +log_master_signing_key_cert_expiration(const or_options_t *options) +{ + const tor_cert_t *signing_key; + char *fn = NULL; + int failed = 0; + time_t now = approx_time(); + + fn = options_get_keydir_fname(options, "ed25519_signing_cert"); + + /* Try to grab our cached copy of the key. */ + signing_key = get_master_signing_key_cert(); + + tor_assert(server_identity_key_is_set()); + + /* Load our keys from disk, if necessary. */ + if (!signing_key) { + failed = load_ed_keys(options, now) < 0; + signing_key = get_master_signing_key_cert(); + } + + /* If we do have a signing key, log the expiration time. */ + if (signing_key) { + log_ed_cert_expiration(signing_key, "signing", fn); + } else { + log_warn(LD_OR, "Could not load signing key certificate from %s, so " \ + "we couldn't learn anything about certificate expiration.", fn); + } + + tor_free(fn); + + return failed; +} + +/** + * Log when a key certificate expires. Used when tor is given the + * --key-expiration command-line option. + * + * If an command argument is given, which should specify the type of + * key to get expiry information about (currently supported arguments + * are "sign"), get info about that type of certificate. Otherwise, + * print info about the supported arguments. + * + * Returns 0 on success and -1 on failure. + */ +int +log_cert_expiration(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + + if (!strcmp(arg, "sign")) { + return log_master_signing_key_cert_expiration(options); + } else { + fprintf(stderr, "No valid argument to --key-expiration found!\n"); + fprintf(stderr, "Currently recognised arguments are: 'sign'\n"); + + return -1; + } +} + +const ed25519_public_key_t * +get_master_identity_key(void) +{ + if (!master_identity_key) + return NULL; + return &master_identity_key->pubkey; +} + +/** Return true iff <b>id</b> is our Ed25519 master identity key. */ +int +router_ed25519_id_is_me(const ed25519_public_key_t *id) +{ + return id && master_identity_key && + ed25519_pubkey_eq(id, &master_identity_key->pubkey); +} + +#ifdef TOR_UNIT_TESTS +/* only exists for the unit tests, since otherwise the identity key + * should be used to sign nothing but the signing key. */ +const ed25519_keypair_t * +get_master_identity_keypair(void) +{ + return master_identity_key; +} +#endif /* defined(TOR_UNIT_TESTS) */ + +const ed25519_keypair_t * +get_master_signing_keypair(void) +{ + return master_signing_key; +} + +const struct tor_cert_st * +get_master_signing_key_cert(void) +{ + return signing_key_cert; +} + +const ed25519_keypair_t * +get_current_auth_keypair(void) +{ + return current_auth_key; +} + +const tor_cert_t * +get_current_link_cert_cert(void) +{ + return link_cert_cert; +} + +const tor_cert_t * +get_current_auth_key_cert(void) +{ + return auth_key_cert; +} + +void +get_master_rsa_crosscert(const uint8_t **cert_out, + size_t *size_out) +{ + *cert_out = rsa_ed_crosscert; + *size_out = rsa_ed_crosscert_len; +} + +/** Construct cross-certification for the master identity key with + * the ntor onion key. Store the sign of the corresponding ed25519 public key + * in *<b>sign_out</b>. */ +tor_cert_t * +make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key, + const ed25519_public_key_t *master_id_key, time_t now, time_t lifetime, + int *sign_out) +{ + tor_cert_t *cert = NULL; + ed25519_keypair_t ed_onion_key; + + if (ed25519_keypair_from_curve25519_keypair(&ed_onion_key, sign_out, + onion_key) < 0) + goto end; + + cert = tor_cert_create(&ed_onion_key, CERT_TYPE_ONION_ID, master_id_key, + now, lifetime, 0); + + end: + memwipe(&ed_onion_key, 0, sizeof(ed_onion_key)); + return cert; +} + +/** Construct and return an RSA signature for the TAP onion key to + * cross-certify the RSA and Ed25519 identity keys. Set <b>len_out</b> to its + * length. */ +uint8_t * +make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, + const ed25519_public_key_t *master_id_key, + const crypto_pk_t *rsa_id_key, + int *len_out) +{ + uint8_t signature[PK_BYTES]; + uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN]; + + *len_out = 0; + if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) { + return NULL; + } + memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN); + + int r = crypto_pk_private_sign(onion_key, + (char*)signature, sizeof(signature), + (const char*)signed_data, sizeof(signed_data)); + if (r < 0) + return NULL; + + *len_out = r; + + return tor_memdup(signature, r); +} + +/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it + * is, -1 if it isn't. */ +MOCK_IMPL(int, +check_tap_onion_key_crosscert,(const uint8_t *crosscert, + int crosscert_len, + const crypto_pk_t *onion_pkey, + const ed25519_public_key_t *master_id_pkey, + const uint8_t *rsa_id_digest)) +{ + uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey)); + int cc_len = + crypto_pk_public_checksig(onion_pkey, + (char*)cc, + crypto_pk_keysize(onion_pkey), + (const char*)crosscert, + crosscert_len); + if (cc_len < 0) { + goto err; + } + if (cc_len < DIGEST_LEN + ED25519_PUBKEY_LEN) { + log_warn(LD_DIR, "Short signature on cross-certification with TAP key"); + goto err; + } + if (tor_memneq(cc, rsa_id_digest, DIGEST_LEN) || + tor_memneq(cc + DIGEST_LEN, master_id_pkey->pubkey, + ED25519_PUBKEY_LEN)) { + log_warn(LD_DIR, "Incorrect cross-certification with TAP key"); + goto err; + } + + tor_free(cc); + return 0; + err: + tor_free(cc); + return -1; +} + +void +routerkeys_free_all(void) +{ + ed25519_keypair_free(master_identity_key); + ed25519_keypair_free(master_signing_key); + ed25519_keypair_free(current_auth_key); + tor_cert_free(signing_key_cert); + tor_cert_free(link_cert_cert); + tor_cert_free(auth_key_cert); + tor_free(rsa_ed_crosscert); + + master_identity_key = master_signing_key = NULL; + current_auth_key = NULL; + signing_key_cert = link_cert_cert = auth_key_cert = NULL; + rsa_ed_crosscert = NULL; // redundant + rsa_ed_crosscert_len = 0; +} diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h new file mode 100644 index 0000000000..a6f06f6e20 --- /dev/null +++ b/src/feature/relay/routerkeys.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2014-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_ROUTERKEYS_H +#define TOR_ROUTERKEYS_H + +#include "lib/crypt_ops/crypto_ed25519.h" + +#define INIT_ED_KEY_CREATE (1u<<0) +#define INIT_ED_KEY_REPLACE (1u<<1) +#define INIT_ED_KEY_SPLIT (1u<<2) +#define INIT_ED_KEY_MISSING_SECRET_OK (1u<<3) +#define INIT_ED_KEY_NEEDCERT (1u<<4) +#define INIT_ED_KEY_EXTRA_STRONG (1u<<5) +#define INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT (1u<<6) +#define INIT_ED_KEY_OMIT_SECRET (1u<<7) +#define INIT_ED_KEY_TRY_ENCRYPTED (1u<<8) +#define INIT_ED_KEY_NO_REPAIR (1u<<9) +#define INIT_ED_KEY_SUGGEST_KEYGEN (1u<<10) +#define INIT_ED_KEY_OFFLINE_SECRET (1u<<11) +#define INIT_ED_KEY_EXPLICIT_FNAME (1u<<12) + +struct tor_cert_st; +ed25519_keypair_t *ed_key_init_from_file(const char *fname, uint32_t flags, + int severity, + const ed25519_keypair_t *signing_key, + time_t now, + time_t lifetime, + uint8_t cert_type, + struct tor_cert_st **cert_out); +ed25519_keypair_t *ed_key_new(const ed25519_keypair_t *signing_key, + uint32_t flags, + time_t now, + time_t lifetime, + uint8_t cert_type, + struct tor_cert_st **cert_out); +const ed25519_public_key_t *get_master_identity_key(void); +const ed25519_keypair_t *get_master_signing_keypair(void); +const struct tor_cert_st *get_master_signing_key_cert(void); + +const ed25519_keypair_t *get_current_auth_keypair(void); +const struct tor_cert_st *get_current_link_cert_cert(void); +const struct tor_cert_st *get_current_auth_key_cert(void); + +void get_master_rsa_crosscert(const uint8_t **cert_out, + size_t *size_out); + +int router_ed25519_id_is_me(const ed25519_public_key_t *id); + +struct tor_cert_st *make_ntor_onion_key_crosscert( + const curve25519_keypair_t *onion_key, + const ed25519_public_key_t *master_id_key, + time_t now, time_t lifetime, + int *sign_out); +uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, + const ed25519_public_key_t *master_id_key, + const crypto_pk_t *rsa_id_key, + int *len_out); + +MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert, + int crosscert_len, + const crypto_pk_t *onion_pkey, + const ed25519_public_key_t *master_id_pkey, + const uint8_t *rsa_id_digest)); + +int log_cert_expiration(void); +int load_ed_keys(const or_options_t *options, time_t now); +int should_make_new_ed_keys(const or_options_t *options, const time_t now); + +int generate_ed_link_cert(const or_options_t *options, time_t now, int force); + +int read_encrypted_secret_key(ed25519_secret_key_t *out, + const char *fname); +int write_encrypted_secret_key(const ed25519_secret_key_t *out, + const char *fname); + +void routerkeys_free_all(void); + +#ifdef TOR_UNIT_TESTS +const ed25519_keypair_t *get_master_identity_keypair(void); +void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key); +#endif + +#endif /* !defined(TOR_ROUTERKEYS_H) */ + |