diff options
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) */ + |