|
@@ -0,0 +1,835 @@
|
|
|
+/* 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 process_descs.c
|
|
|
+ * \brief Make decisions about uploaded descriptors
|
|
|
+ *
|
|
|
+ * Authorities use the code in this module to decide what to do with just-
|
|
|
+ * uploaded descriptors, and to manage the fingerprint file that helps
|
|
|
+ * them make those decisions.
|
|
|
+ **/
|
|
|
+
|
|
|
+#include "core/or/or.h"
|
|
|
+#include "feature/dirauth/process_descs.h"
|
|
|
+
|
|
|
+#include "app/config/config.h"
|
|
|
+#include "core/or/policies.h"
|
|
|
+#include "feature/dirauth/keypin.h"
|
|
|
+#include "feature/dirauth/reachability.h"
|
|
|
+#include "feature/dircache/directory.h"
|
|
|
+#include "feature/nodelist/networkstatus.h"
|
|
|
+#include "feature/nodelist/nodelist.h"
|
|
|
+#include "feature/nodelist/routerlist.h"
|
|
|
+#include "feature/nodelist/routerparse.h"
|
|
|
+#include "feature/nodelist/torcert.h"
|
|
|
+#include "feature/relay/router.h"
|
|
|
+
|
|
|
+#include "core/or/tor_version_st.h"
|
|
|
+#include "feature/nodelist/extrainfo_st.h"
|
|
|
+#include "feature/nodelist/node_st.h"
|
|
|
+#include "feature/nodelist/routerinfo_st.h"
|
|
|
+#include "feature/nodelist/routerstatus_st.h"
|
|
|
+
|
|
|
+#include "lib/encoding/confline.h"
|
|
|
+
|
|
|
+/** How far in the future do we allow a router to get? (seconds) */
|
|
|
+#define ROUTER_ALLOW_SKEW (60*60*12)
|
|
|
+
|
|
|
+static void directory_remove_invalid(void);
|
|
|
+struct authdir_config_t;
|
|
|
+static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei,
|
|
|
+ const char **msg);
|
|
|
+static uint32_t
|
|
|
+dirserv_get_status_impl(const char *fp, const char *nickname,
|
|
|
+ uint32_t addr, uint16_t or_port,
|
|
|
+ const char *platform, const char **msg,
|
|
|
+ int severity);
|
|
|
+
|
|
|
+/* 1 Historically used to indicate Named */
|
|
|
+#define FP_INVALID 2 /**< Believed invalid. */
|
|
|
+#define FP_REJECT 4 /**< We will not publish this router. */
|
|
|
+/* 8 Historically used to avoid using this as a dir. */
|
|
|
+#define FP_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */
|
|
|
+/* 32 Historically used to indicade Unnamed */
|
|
|
+
|
|
|
+/** Target of status_by_digest map. */
|
|
|
+typedef uint32_t router_status_t;
|
|
|
+
|
|
|
+static void add_fingerprint_to_dir(const char *fp,
|
|
|
+ struct authdir_config_t *list,
|
|
|
+ router_status_t add_status);
|
|
|
+
|
|
|
+/** List of nickname-\>identity fingerprint mappings for all the routers
|
|
|
+ * that we name. Used to prevent router impersonation. */
|
|
|
+typedef struct authdir_config_t {
|
|
|
+ strmap_t *fp_by_name; /**< Map from lc nickname to fingerprint. */
|
|
|
+ digestmap_t *status_by_digest; /**< Map from digest to router_status_t. */
|
|
|
+} authdir_config_t;
|
|
|
+
|
|
|
+/** Should be static; exposed for testing. */
|
|
|
+static authdir_config_t *fingerprint_list = NULL;
|
|
|
+
|
|
|
+/** Allocate and return a new, empty, authdir_config_t. */
|
|
|
+static authdir_config_t *
|
|
|
+authdir_config_new(void)
|
|
|
+{
|
|
|
+ authdir_config_t *list = tor_malloc_zero(sizeof(authdir_config_t));
|
|
|
+ list->fp_by_name = strmap_new();
|
|
|
+ list->status_by_digest = digestmap_new();
|
|
|
+ return list;
|
|
|
+}
|
|
|
+
|
|
|
+/** Add the fingerprint <b>fp</b> to the smartlist of fingerprint_entry_t's
|
|
|
+ * <b>list</b>, or-ing the currently set status flags with
|
|
|
+ * <b>add_status</b>.
|
|
|
+ */
|
|
|
+/* static */ void
|
|
|
+add_fingerprint_to_dir(const char *fp, authdir_config_t *list,
|
|
|
+ router_status_t add_status)
|
|
|
+{
|
|
|
+ char *fingerprint;
|
|
|
+ char d[DIGEST_LEN];
|
|
|
+ router_status_t *status;
|
|
|
+ tor_assert(fp);
|
|
|
+ tor_assert(list);
|
|
|
+
|
|
|
+ fingerprint = tor_strdup(fp);
|
|
|
+ tor_strstrip(fingerprint, " ");
|
|
|
+ if (base16_decode(d, DIGEST_LEN,
|
|
|
+ fingerprint, strlen(fingerprint)) != DIGEST_LEN) {
|
|
|
+ log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"",
|
|
|
+ escaped(fp));
|
|
|
+ tor_free(fingerprint);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ status = digestmap_get(list->status_by_digest, d);
|
|
|
+ if (!status) {
|
|
|
+ status = tor_malloc_zero(sizeof(router_status_t));
|
|
|
+ digestmap_set(list->status_by_digest, d, status);
|
|
|
+ }
|
|
|
+
|
|
|
+ tor_free(fingerprint);
|
|
|
+ *status |= add_status;
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+/** Add the fingerprint for this OR to the global list of recognized
|
|
|
+ * identity key fingerprints. */
|
|
|
+int
|
|
|
+dirserv_add_own_fingerprint(crypto_pk_t *pk)
|
|
|
+{
|
|
|
+ char fp[FINGERPRINT_LEN+1];
|
|
|
+ if (crypto_pk_get_fingerprint(pk, fp, 0)<0) {
|
|
|
+ log_err(LD_BUG, "Error computing fingerprint");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (!fingerprint_list)
|
|
|
+ fingerprint_list = authdir_config_new();
|
|
|
+ add_fingerprint_to_dir(fp, fingerprint_list, 0);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** Load the nickname-\>fingerprint mappings stored in the approved-routers
|
|
|
+ * file. The file format is line-based, with each non-blank holding one
|
|
|
+ * nickname, some space, and a fingerprint for that nickname. On success,
|
|
|
+ * replace the current fingerprint list with the new list and return 0. On
|
|
|
+ * failure, leave the current fingerprint list untouched, and return -1. */
|
|
|
+int
|
|
|
+dirserv_load_fingerprint_file(void)
|
|
|
+{
|
|
|
+ char *fname;
|
|
|
+ char *cf;
|
|
|
+ char *nickname, *fingerprint;
|
|
|
+ authdir_config_t *fingerprint_list_new;
|
|
|
+ int result;
|
|
|
+ config_line_t *front=NULL, *list;
|
|
|
+
|
|
|
+ fname = get_datadir_fname("approved-routers");
|
|
|
+ log_info(LD_GENERAL,
|
|
|
+ "Reloading approved fingerprints from \"%s\"...", fname);
|
|
|
+
|
|
|
+ cf = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
|
|
|
+ if (!cf) {
|
|
|
+ log_warn(LD_FS, "Cannot open fingerprint file '%s'. That's ok.", fname);
|
|
|
+ tor_free(fname);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ tor_free(fname);
|
|
|
+
|
|
|
+ result = config_get_lines(cf, &front, 0);
|
|
|
+ tor_free(cf);
|
|
|
+ if (result < 0) {
|
|
|
+ log_warn(LD_CONFIG, "Error reading from fingerprint file");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ fingerprint_list_new = authdir_config_new();
|
|
|
+
|
|
|
+ for (list=front; list; list=list->next) {
|
|
|
+ char digest_tmp[DIGEST_LEN];
|
|
|
+ router_status_t add_status = 0;
|
|
|
+ nickname = list->key; fingerprint = list->value;
|
|
|
+ tor_strstrip(fingerprint, " "); /* remove spaces */
|
|
|
+ if (strlen(fingerprint) != HEX_DIGEST_LEN ||
|
|
|
+ base16_decode(digest_tmp, sizeof(digest_tmp),
|
|
|
+ fingerprint, HEX_DIGEST_LEN) != sizeof(digest_tmp)) {
|
|
|
+ log_notice(LD_CONFIG,
|
|
|
+ "Invalid fingerprint (nickname '%s', "
|
|
|
+ "fingerprint %s). Skipping.",
|
|
|
+ nickname, fingerprint);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!strcasecmp(nickname, "!reject")) {
|
|
|
+ add_status = FP_REJECT;
|
|
|
+ } else if (!strcasecmp(nickname, "!badexit")) {
|
|
|
+ add_status = FP_BADEXIT;
|
|
|
+ } else if (!strcasecmp(nickname, "!invalid")) {
|
|
|
+ add_status = FP_INVALID;
|
|
|
+ }
|
|
|
+ add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status);
|
|
|
+ }
|
|
|
+
|
|
|
+ config_free_lines(front);
|
|
|
+ dirserv_free_fingerprint_list();
|
|
|
+ fingerprint_list = fingerprint_list_new;
|
|
|
+ /* Delete any routers whose fingerprints we no longer recognize */
|
|
|
+ directory_remove_invalid();
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* If this is set, then we don't allow routers that have advertised an Ed25519
|
|
|
+ * identity to stop doing so. This is going to be essential for good identity
|
|
|
+ * security: otherwise anybody who can attack RSA-1024 but not Ed25519 could
|
|
|
+ * just sign fake descriptors missing the Ed25519 key. But we won't actually
|
|
|
+ * be able to prevent that kind of thing until we're confident that there isn't
|
|
|
+ * actually a legit reason to downgrade to 0.2.5. Now we are not recommending
|
|
|
+ * 0.2.5 anymore so there is no reason to keep the #undef.
|
|
|
+ */
|
|
|
+
|
|
|
+#define DISABLE_DISABLING_ED25519
|
|
|
+
|
|
|
+/** Check whether <b>router</b> has a nickname/identity key combination that
|
|
|
+ * we recognize from the fingerprint list, or an IP we automatically act on
|
|
|
+ * according to our configuration. Return the appropriate router status.
|
|
|
+ *
|
|
|
+ * If the status is 'FP_REJECT' and <b>msg</b> is provided, set
|
|
|
+ * *<b>msg</b> to an explanation of why. */
|
|
|
+uint32_t
|
|
|
+dirserv_router_get_status(const routerinfo_t *router, const char **msg,
|
|
|
+ int severity)
|
|
|
+{
|
|
|
+ char d[DIGEST_LEN];
|
|
|
+ const int key_pinning = get_options()->AuthDirPinKeys;
|
|
|
+
|
|
|
+ if (crypto_pk_get_digest(router->identity_pkey, d)) {
|
|
|
+ log_warn(LD_BUG,"Error computing fingerprint");
|
|
|
+ if (msg)
|
|
|
+ *msg = "Bug: Error computing fingerprint";
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check for the more usual versions to reject a router first. */
|
|
|
+ const uint32_t r = dirserv_get_status_impl(d, router->nickname,
|
|
|
+ router->addr, router->or_port,
|
|
|
+ router->platform, msg, severity);
|
|
|
+ if (r)
|
|
|
+ return r;
|
|
|
+
|
|
|
+ /* dirserv_get_status_impl already rejects versions older than 0.2.4.18-rc,
|
|
|
+ * and onion_curve25519_pkey was introduced in 0.2.4.8-alpha.
|
|
|
+ * But just in case a relay doesn't provide or lies about its version, or
|
|
|
+ * doesn't include an ntor key in its descriptor, check that it exists,
|
|
|
+ * and is non-zero (clients check that it's non-zero before using it). */
|
|
|
+ if (!routerinfo_has_curve25519_onion_key(router)) {
|
|
|
+ log_fn(severity, LD_DIR,
|
|
|
+ "Descriptor from router %s is missing an ntor curve25519 onion "
|
|
|
+ "key.", router_describe(router));
|
|
|
+ if (msg)
|
|
|
+ *msg = "Missing ntor curve25519 onion key. Please upgrade!";
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (router->cache_info.signing_key_cert) {
|
|
|
+ /* This has an ed25519 identity key. */
|
|
|
+ if (KEYPIN_MISMATCH ==
|
|
|
+ keypin_check((const uint8_t*)router->cache_info.identity_digest,
|
|
|
+ router->cache_info.signing_key_cert->signing_key.pubkey)) {
|
|
|
+ log_fn(severity, LD_DIR,
|
|
|
+ "Descriptor from router %s has an Ed25519 key, "
|
|
|
+ "but the <rsa,ed25519> keys don't match what they were before.",
|
|
|
+ router_describe(router));
|
|
|
+ if (key_pinning) {
|
|
|
+ if (msg) {
|
|
|
+ *msg = "Ed25519 identity key or RSA identity key has changed.";
|
|
|
+ }
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* No ed25519 key */
|
|
|
+ if (KEYPIN_MISMATCH == keypin_check_lone_rsa(
|
|
|
+ (const uint8_t*)router->cache_info.identity_digest)) {
|
|
|
+ log_fn(severity, LD_DIR,
|
|
|
+ "Descriptor from router %s has no Ed25519 key, "
|
|
|
+ "when we previously knew an Ed25519 for it. Ignoring for now, "
|
|
|
+ "since Ed25519 keys are fairly new.",
|
|
|
+ router_describe(router));
|
|
|
+#ifdef DISABLE_DISABLING_ED25519
|
|
|
+ if (key_pinning) {
|
|
|
+ if (msg) {
|
|
|
+ *msg = "Ed25519 identity key has disappeared.";
|
|
|
+ }
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+#endif /* defined(DISABLE_DISABLING_ED25519) */
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** Return true if there is no point in downloading the router described by
|
|
|
+ * <b>rs</b> because this directory would reject it. */
|
|
|
+int
|
|
|
+dirserv_would_reject_router(const routerstatus_t *rs)
|
|
|
+{
|
|
|
+ uint32_t res;
|
|
|
+
|
|
|
+ res = dirserv_get_status_impl(rs->identity_digest, rs->nickname,
|
|
|
+ rs->addr, rs->or_port,
|
|
|
+ NULL, NULL, LOG_DEBUG);
|
|
|
+
|
|
|
+ return (res & FP_REJECT) != 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** Helper: As dirserv_router_get_status, but takes the router fingerprint
|
|
|
+ * (hex, no spaces), nickname, address (used for logging only), IP address, OR
|
|
|
+ * port and platform (logging only) as arguments.
|
|
|
+ *
|
|
|
+ * Log messages at 'severity'. (There's not much point in
|
|
|
+ * logging that we're rejecting servers we'll not download.)
|
|
|
+ */
|
|
|
+static uint32_t
|
|
|
+dirserv_get_status_impl(const char *id_digest, const char *nickname,
|
|
|
+ uint32_t addr, uint16_t or_port,
|
|
|
+ const char *platform, const char **msg, int severity)
|
|
|
+{
|
|
|
+ uint32_t result = 0;
|
|
|
+ router_status_t *status_by_digest;
|
|
|
+
|
|
|
+ if (!fingerprint_list)
|
|
|
+ fingerprint_list = authdir_config_new();
|
|
|
+
|
|
|
+ log_debug(LD_DIRSERV, "%d fingerprints, %d digests known.",
|
|
|
+ strmap_size(fingerprint_list->fp_by_name),
|
|
|
+ digestmap_size(fingerprint_list->status_by_digest));
|
|
|
+
|
|
|
+ if (platform) {
|
|
|
+ tor_version_t ver_tmp;
|
|
|
+ if (tor_version_parse_platform(platform, &ver_tmp, 1) < 0) {
|
|
|
+ if (msg) {
|
|
|
+ *msg = "Malformed platform string.";
|
|
|
+ }
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Versions before Tor 0.2.4.18-rc are too old to support, and are
|
|
|
+ * missing some important security fixes too. Disable them. */
|
|
|
+ if (platform && !tor_version_as_new_as(platform,"0.2.4.18-rc")) {
|
|
|
+ if (msg)
|
|
|
+ *msg = "Tor version is insecure or unsupported. Please upgrade!";
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Tor 0.2.9.x where x<5 suffers from bug #20499, where relays don't
|
|
|
+ * keep their consensus up to date so they make bad guards.
|
|
|
+ * The simple fix is to just drop them from the network. */
|
|
|
+ if (platform &&
|
|
|
+ tor_version_as_new_as(platform,"0.2.9.0-alpha") &&
|
|
|
+ !tor_version_as_new_as(platform,"0.2.9.5-alpha")) {
|
|
|
+ if (msg)
|
|
|
+ *msg = "Tor version contains bug 20499. Please upgrade!";
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+
|
|
|
+ status_by_digest = digestmap_get(fingerprint_list->status_by_digest,
|
|
|
+ id_digest);
|
|
|
+ if (status_by_digest)
|
|
|
+ result |= *status_by_digest;
|
|
|
+
|
|
|
+ if (result & FP_REJECT) {
|
|
|
+ if (msg)
|
|
|
+ *msg = "Fingerprint is marked rejected -- if you think this is a "
|
|
|
+ "mistake please set a valid email address in ContactInfo and "
|
|
|
+ "send an email to bad-relays@lists.torproject.org mentioning "
|
|
|
+ "your fingerprint(s)?";
|
|
|
+ return FP_REJECT;
|
|
|
+ } else if (result & FP_INVALID) {
|
|
|
+ if (msg)
|
|
|
+ *msg = "Fingerprint is marked invalid";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (authdir_policy_badexit_address(addr, or_port)) {
|
|
|
+ log_fn(severity, LD_DIRSERV,
|
|
|
+ "Marking '%s' as bad exit because of address '%s'",
|
|
|
+ nickname, fmt_addr32(addr));
|
|
|
+ result |= FP_BADEXIT;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!authdir_policy_permits_address(addr, or_port)) {
|
|
|
+ log_fn(severity, LD_DIRSERV, "Rejecting '%s' because of address '%s'",
|
|
|
+ nickname, fmt_addr32(addr));
|
|
|
+ if (msg)
|
|
|
+ *msg = "Suspicious relay address range -- if you think this is a "
|
|
|
+ "mistake please set a valid email address in ContactInfo and "
|
|
|
+ "send an email to bad-relays@lists.torproject.org mentioning "
|
|
|
+ "your address(es) and fingerprint(s)?";
|
|
|
+ return FP_REJECT;
|
|
|
+ }
|
|
|
+ if (!authdir_policy_valid_address(addr, or_port)) {
|
|
|
+ log_fn(severity, LD_DIRSERV,
|
|
|
+ "Not marking '%s' valid because of address '%s'",
|
|
|
+ nickname, fmt_addr32(addr));
|
|
|
+ result |= FP_INVALID;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/** Clear the current fingerprint list. */
|
|
|
+void
|
|
|
+dirserv_free_fingerprint_list(void)
|
|
|
+{
|
|
|
+ if (!fingerprint_list)
|
|
|
+ return;
|
|
|
+
|
|
|
+ strmap_free(fingerprint_list->fp_by_name, tor_free_);
|
|
|
+ digestmap_free(fingerprint_list->status_by_digest, tor_free_);
|
|
|
+ tor_free(fingerprint_list);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Descriptor list
|
|
|
+ */
|
|
|
+
|
|
|
+/** Return -1 if <b>ri</b> has a private or otherwise bad address,
|
|
|
+ * unless we're configured to not care. Return 0 if all ok. */
|
|
|
+static int
|
|
|
+dirserv_router_has_valid_address(routerinfo_t *ri)
|
|
|
+{
|
|
|
+ tor_addr_t addr;
|
|
|
+ if (get_options()->DirAllowPrivateAddresses)
|
|
|
+ return 0; /* whatever it is, we're fine with it */
|
|
|
+ tor_addr_from_ipv4h(&addr, ri->addr);
|
|
|
+
|
|
|
+ if (tor_addr_is_internal(&addr, 0)) {
|
|
|
+ log_info(LD_DIRSERV,
|
|
|
+ "Router %s published internal IP address. Refusing.",
|
|
|
+ router_describe(ri));
|
|
|
+ return -1; /* it's a private IP, we should reject it */
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** Check whether we, as a directory server, want to accept <b>ri</b>. If so,
|
|
|
+ * set its is_valid,running fields and return 0. Otherwise, return -1.
|
|
|
+ *
|
|
|
+ * If the router is rejected, set *<b>msg</b> to an explanation of why.
|
|
|
+ *
|
|
|
+ * If <b>complain</b> then explain at log-level 'notice' why we refused
|
|
|
+ * a descriptor; else explain at log-level 'info'.
|
|
|
+ */
|
|
|
+int
|
|
|
+authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
|
|
|
+ int complain, int *valid_out)
|
|
|
+{
|
|
|
+ /* Okay. Now check whether the fingerprint is recognized. */
|
|
|
+ time_t now;
|
|
|
+ int severity = (complain && ri->contact_info) ? LOG_NOTICE : LOG_INFO;
|
|
|
+ uint32_t status = dirserv_router_get_status(ri, msg, severity);
|
|
|
+ tor_assert(msg);
|
|
|
+ if (status & FP_REJECT)
|
|
|
+ return -1; /* msg is already set. */
|
|
|
+
|
|
|
+ /* Is there too much clock skew? */
|
|
|
+ now = time(NULL);
|
|
|
+ if (ri->cache_info.published_on > now+ROUTER_ALLOW_SKEW) {
|
|
|
+ log_fn(severity, LD_DIRSERV, "Publication time for %s is too "
|
|
|
+ "far (%d minutes) in the future; possible clock skew. Not adding "
|
|
|
+ "(%s)",
|
|
|
+ router_describe(ri),
|
|
|
+ (int)((ri->cache_info.published_on-now)/60),
|
|
|
+ esc_router_info(ri));
|
|
|
+ *msg = "Rejected: Your clock is set too far in the future, or your "
|
|
|
+ "timezone is not correct.";
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (ri->cache_info.published_on < now-ROUTER_MAX_AGE_TO_PUBLISH) {
|
|
|
+ log_fn(severity, LD_DIRSERV,
|
|
|
+ "Publication time for %s is too far "
|
|
|
+ "(%d minutes) in the past. Not adding (%s)",
|
|
|
+ router_describe(ri),
|
|
|
+ (int)((now-ri->cache_info.published_on)/60),
|
|
|
+ esc_router_info(ri));
|
|
|
+ *msg = "Rejected: Server is expired, or your clock is too far in the past,"
|
|
|
+ " or your timezone is not correct.";
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (dirserv_router_has_valid_address(ri) < 0) {
|
|
|
+ log_fn(severity, LD_DIRSERV,
|
|
|
+ "Router %s has invalid address. Not adding (%s).",
|
|
|
+ router_describe(ri),
|
|
|
+ esc_router_info(ri));
|
|
|
+ *msg = "Rejected: Address is a private address.";
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ *valid_out = ! (status & FP_INVALID);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** Update the relevant flags of <b>node</b> based on our opinion as a
|
|
|
+ * directory authority in <b>authstatus</b>, as returned by
|
|
|
+ * dirserv_router_get_status or equivalent. */
|
|
|
+void
|
|
|
+dirserv_set_node_flags_from_authoritative_status(node_t *node,
|
|
|
+ uint32_t authstatus)
|
|
|
+{
|
|
|
+ node->is_valid = (authstatus & FP_INVALID) ? 0 : 1;
|
|
|
+ node->is_bad_exit = (authstatus & FP_BADEXIT) ? 1 : 0;
|
|
|
+}
|
|
|
+
|
|
|
+/** True iff <b>a</b> is more severe than <b>b</b>. */
|
|
|
+static int
|
|
|
+WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t b)
|
|
|
+{
|
|
|
+ return a < b;
|
|
|
+}
|
|
|
+
|
|
|
+/** As for dirserv_add_descriptor(), but accepts multiple documents, and
|
|
|
+ * returns the most severe error that occurred for any one of them. */
|
|
|
+was_router_added_t
|
|
|
+dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose,
|
|
|
+ const char *source,
|
|
|
+ const char **msg)
|
|
|
+{
|
|
|
+ was_router_added_t r, r_tmp;
|
|
|
+ const char *msg_out;
|
|
|
+ smartlist_t *list;
|
|
|
+ const char *s;
|
|
|
+ int n_parsed = 0;
|
|
|
+ time_t now = time(NULL);
|
|
|
+ char annotation_buf[ROUTER_ANNOTATION_BUF_LEN];
|
|
|
+ char time_buf[ISO_TIME_LEN+1];
|
|
|
+ int general = purpose == ROUTER_PURPOSE_GENERAL;
|
|
|
+ tor_assert(msg);
|
|
|
+
|
|
|
+ r=ROUTER_ADDED_SUCCESSFULLY; /*Least severe return value. */
|
|
|
+
|
|
|
+ format_iso_time(time_buf, now);
|
|
|
+ if (tor_snprintf(annotation_buf, sizeof(annotation_buf),
|
|
|
+ "@uploaded-at %s\n"
|
|
|
+ "@source %s\n"
|
|
|
+ "%s%s%s", time_buf, escaped(source),
|
|
|
+ !general ? "@purpose " : "",
|
|
|
+ !general ? router_purpose_to_string(purpose) : "",
|
|
|
+ !general ? "\n" : "")<0) {
|
|
|
+ *msg = "Couldn't format annotations";
|
|
|
+ /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is
|
|
|
+ * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ s = desc;
|
|
|
+ list = smartlist_new();
|
|
|
+ if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 0, 0,
|
|
|
+ annotation_buf, NULL)) {
|
|
|
+ SMARTLIST_FOREACH(list, routerinfo_t *, ri, {
|
|
|
+ msg_out = NULL;
|
|
|
+ tor_assert(ri->purpose == purpose);
|
|
|
+ r_tmp = dirserv_add_descriptor(ri, &msg_out, source);
|
|
|
+ if (WRA_MORE_SEVERE(r_tmp, r)) {
|
|
|
+ r = r_tmp;
|
|
|
+ *msg = msg_out;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ n_parsed += smartlist_len(list);
|
|
|
+ smartlist_clear(list);
|
|
|
+
|
|
|
+ s = desc;
|
|
|
+ if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 1, 0,
|
|
|
+ NULL, NULL)) {
|
|
|
+ SMARTLIST_FOREACH(list, extrainfo_t *, ei, {
|
|
|
+ msg_out = NULL;
|
|
|
+
|
|
|
+ r_tmp = dirserv_add_extrainfo(ei, &msg_out);
|
|
|
+ if (WRA_MORE_SEVERE(r_tmp, r)) {
|
|
|
+ r = r_tmp;
|
|
|
+ *msg = msg_out;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ n_parsed += smartlist_len(list);
|
|
|
+ smartlist_free(list);
|
|
|
+
|
|
|
+ if (! *msg) {
|
|
|
+ if (!n_parsed) {
|
|
|
+ *msg = "No descriptors found in your POST.";
|
|
|
+ if (WRA_WAS_ADDED(r))
|
|
|
+ r = ROUTER_IS_ALREADY_KNOWN;
|
|
|
+ } else {
|
|
|
+ *msg = "(no message)";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
+/** Examine the parsed server descriptor in <b>ri</b> and maybe insert it into
|
|
|
+ * the list of server descriptors. Set *<b>msg</b> to a message that should be
|
|
|
+ * passed back to the origin of this descriptor, or NULL if there is no such
|
|
|
+ * message. Use <b>source</b> to produce better log messages.
|
|
|
+ *
|
|
|
+ * If <b>ri</b> is not added to the list of server descriptors, free it.
|
|
|
+ * That means the caller must not access <b>ri</b> after this function
|
|
|
+ * returns, since it might have been freed.
|
|
|
+ *
|
|
|
+ * Return the status of the operation.
|
|
|
+ *
|
|
|
+ * This function is only called when fresh descriptors are posted, not when
|
|
|
+ * we re-load the cache.
|
|
|
+ */
|
|
|
+was_router_added_t
|
|
|
+dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
|
|
|
+{
|
|
|
+ was_router_added_t r;
|
|
|
+ routerinfo_t *ri_old;
|
|
|
+ char *desc, *nickname;
|
|
|
+ const size_t desclen = ri->cache_info.signed_descriptor_len +
|
|
|
+ ri->cache_info.annotations_len;
|
|
|
+ const int key_pinning = get_options()->AuthDirPinKeys;
|
|
|
+ *msg = NULL;
|
|
|
+
|
|
|
+ /* If it's too big, refuse it now. Otherwise we'll cache it all over the
|
|
|
+ * network and it'll clog everything up. */
|
|
|
+ if (ri->cache_info.signed_descriptor_len > MAX_DESCRIPTOR_UPLOAD_SIZE) {
|
|
|
+ log_notice(LD_DIR, "Somebody attempted to publish a router descriptor '%s'"
|
|
|
+ " (source: %s) with size %d. Either this is an attack, or the "
|
|
|
+ "MAX_DESCRIPTOR_UPLOAD_SIZE (%d) constant is too low.",
|
|
|
+ ri->nickname, source, (int)ri->cache_info.signed_descriptor_len,
|
|
|
+ MAX_DESCRIPTOR_UPLOAD_SIZE);
|
|
|
+ *msg = "Router descriptor was too large.";
|
|
|
+ r = ROUTER_AUTHDIR_REJECTS;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check whether this descriptor is semantically identical to the last one
|
|
|
+ * from this server. (We do this here and not in router_add_to_routerlist
|
|
|
+ * because we want to be able to accept the newest router descriptor that
|
|
|
+ * another authority has, so we all converge on the same one.) */
|
|
|
+ ri_old = router_get_mutable_by_digest(ri->cache_info.identity_digest);
|
|
|
+ if (ri_old && ri_old->cache_info.published_on < ri->cache_info.published_on
|
|
|
+ && router_differences_are_cosmetic(ri_old, ri)
|
|
|
+ && !router_is_me(ri)) {
|
|
|
+ log_info(LD_DIRSERV,
|
|
|
+ "Not replacing descriptor from %s (source: %s); "
|
|
|
+ "differences are cosmetic.",
|
|
|
+ router_describe(ri), source);
|
|
|
+ *msg = "Not replacing router descriptor; no information has changed since "
|
|
|
+ "the last one with this identity.";
|
|
|
+ r = ROUTER_IS_ALREADY_KNOWN;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Do keypinning again ... this time, to add the pin if appropriate */
|
|
|
+ int keypin_status;
|
|
|
+ if (ri->cache_info.signing_key_cert) {
|
|
|
+ ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
|
|
|
+ /* First let's validate this pubkey before pinning it */
|
|
|
+ if (ed25519_validate_pubkey(pkey) < 0) {
|
|
|
+ log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
|
|
|
+ router_describe(ri), source);
|
|
|
+ routerinfo_free(ri);
|
|
|
+ return ROUTER_AUTHDIR_REJECTS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Now pin it! */
|
|
|
+ keypin_status = keypin_check_and_add(
|
|
|
+ (const uint8_t*)ri->cache_info.identity_digest,
|
|
|
+ pkey->pubkey, ! key_pinning);
|
|
|
+ } else {
|
|
|
+ keypin_status = keypin_check_lone_rsa(
|
|
|
+ (const uint8_t*)ri->cache_info.identity_digest);
|
|
|
+#ifndef DISABLE_DISABLING_ED25519
|
|
|
+ if (keypin_status == KEYPIN_MISMATCH)
|
|
|
+ keypin_status = KEYPIN_NOT_FOUND;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ if (keypin_status == KEYPIN_MISMATCH && key_pinning) {
|
|
|
+ log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because "
|
|
|
+ "its key did not match an older RSA/Ed25519 keypair",
|
|
|
+ router_describe(ri), source);
|
|
|
+ *msg = "Looks like your keypair has changed? This authority previously "
|
|
|
+ "recorded a different RSA identity for this Ed25519 identity (or vice "
|
|
|
+ "versa.) Did you replace or copy some of your key files, but not "
|
|
|
+ "the others? You should either restore the expected keypair, or "
|
|
|
+ "delete your keys and restart Tor to start your relay with a new "
|
|
|
+ "identity.";
|
|
|
+ r = ROUTER_AUTHDIR_REJECTS;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Make a copy of desc, since router_add_to_routerlist might free
|
|
|
+ * ri and its associated signed_descriptor_t. */
|
|
|
+ desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen);
|
|
|
+ nickname = tor_strdup(ri->nickname);
|
|
|
+
|
|
|
+ /* Tell if we're about to need to launch a test if we add this. */
|
|
|
+ ri->needs_retest_if_added =
|
|
|
+ dirserv_should_launch_reachability_test(ri, ri_old);
|
|
|
+
|
|
|
+ r = router_add_to_routerlist(ri, msg, 0, 0);
|
|
|
+ if (!WRA_WAS_ADDED(r)) {
|
|
|
+ /* unless the routerinfo was fine, just out-of-date */
|
|
|
+ log_info(LD_DIRSERV,
|
|
|
+ "Did not add descriptor from '%s' (source: %s): %s.",
|
|
|
+ nickname, source, *msg ? *msg : "(no message)");
|
|
|
+ } else {
|
|
|
+ smartlist_t *changed;
|
|
|
+
|
|
|
+ changed = smartlist_new();
|
|
|
+ smartlist_add(changed, ri);
|
|
|
+ routerlist_descriptors_added(changed, 0);
|
|
|
+ smartlist_free(changed);
|
|
|
+ if (!*msg) {
|
|
|
+ *msg = "Descriptor accepted";
|
|
|
+ }
|
|
|
+ log_info(LD_DIRSERV,
|
|
|
+ "Added descriptor from '%s' (source: %s): %s.",
|
|
|
+ nickname, source, *msg);
|
|
|
+ }
|
|
|
+ tor_free(desc);
|
|
|
+ tor_free(nickname);
|
|
|
+ return r;
|
|
|
+ fail:
|
|
|
+ {
|
|
|
+ const char *desc_digest = ri->cache_info.signed_descriptor_digest;
|
|
|
+ download_status_t *dls =
|
|
|
+ router_get_dl_status_by_descriptor_digest(desc_digest);
|
|
|
+ if (dls) {
|
|
|
+ log_info(LD_GENERAL, "Marking router with descriptor %s as rejected, "
|
|
|
+ "and therefore undownloadable",
|
|
|
+ hex_str(desc_digest, DIGEST_LEN));
|
|
|
+ download_status_mark_impossible(dls);
|
|
|
+ }
|
|
|
+ routerinfo_free(ri);
|
|
|
+ }
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
+/** As dirserv_add_descriptor, but for an extrainfo_t <b>ei</b>. */
|
|
|
+static was_router_added_t
|
|
|
+dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
|
|
|
+{
|
|
|
+ routerinfo_t *ri;
|
|
|
+ int r;
|
|
|
+ was_router_added_t rv;
|
|
|
+ tor_assert(msg);
|
|
|
+ *msg = NULL;
|
|
|
+
|
|
|
+ /* Needs to be mutable so routerinfo_incompatible_with_extrainfo
|
|
|
+ * can mess with some of the flags in ri->cache_info. */
|
|
|
+ ri = router_get_mutable_by_digest(ei->cache_info.identity_digest);
|
|
|
+ if (!ri) {
|
|
|
+ *msg = "No corresponding router descriptor for extra-info descriptor";
|
|
|
+ rv = ROUTER_BAD_EI;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If it's too big, refuse it now. Otherwise we'll cache it all over the
|
|
|
+ * network and it'll clog everything up. */
|
|
|
+ if (ei->cache_info.signed_descriptor_len > MAX_EXTRAINFO_UPLOAD_SIZE) {
|
|
|
+ log_notice(LD_DIR, "Somebody attempted to publish an extrainfo "
|
|
|
+ "with size %d. Either this is an attack, or the "
|
|
|
+ "MAX_EXTRAINFO_UPLOAD_SIZE (%d) constant is too low.",
|
|
|
+ (int)ei->cache_info.signed_descriptor_len,
|
|
|
+ MAX_EXTRAINFO_UPLOAD_SIZE);
|
|
|
+ *msg = "Extrainfo document was too large";
|
|
|
+ rv = ROUTER_BAD_EI;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((r = routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
|
|
|
+ &ri->cache_info, msg))) {
|
|
|
+ if (r<0) {
|
|
|
+ extrainfo_free(ei);
|
|
|
+ return ROUTER_IS_ALREADY_KNOWN;
|
|
|
+ }
|
|
|
+ rv = ROUTER_BAD_EI;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ router_add_extrainfo_to_routerlist(ei, msg, 0, 0);
|
|
|
+ return ROUTER_ADDED_SUCCESSFULLY;
|
|
|
+ fail:
|
|
|
+ {
|
|
|
+ const char *d = ei->cache_info.signed_descriptor_digest;
|
|
|
+ signed_descriptor_t *sd = router_get_by_extrainfo_digest((char*)d);
|
|
|
+ if (sd) {
|
|
|
+ log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as "
|
|
|
+ "rejected, and therefore undownloadable",
|
|
|
+ hex_str((char*)d,DIGEST_LEN));
|
|
|
+ download_status_mark_impossible(&sd->ei_dl_status);
|
|
|
+ }
|
|
|
+ extrainfo_free(ei);
|
|
|
+ }
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+/** Remove all descriptors whose nicknames or fingerprints no longer
|
|
|
+ * are allowed by our fingerprint list. (Descriptors that used to be
|
|
|
+ * good can become bad when we reload the fingerprint list.)
|
|
|
+ */
|
|
|
+static void
|
|
|
+directory_remove_invalid(void)
|
|
|
+{
|
|
|
+ routerlist_t *rl = router_get_routerlist();
|
|
|
+ smartlist_t *nodes = smartlist_new();
|
|
|
+ smartlist_add_all(nodes, nodelist_get_list());
|
|
|
+
|
|
|
+ SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) {
|
|
|
+ const char *msg = NULL;
|
|
|
+ const char *description;
|
|
|
+ routerinfo_t *ent = node->ri;
|
|
|
+ uint32_t r;
|
|
|
+ if (!ent)
|
|
|
+ continue;
|
|
|
+ r = dirserv_router_get_status(ent, &msg, LOG_INFO);
|
|
|
+ description = router_describe(ent);
|
|
|
+ if (r & FP_REJECT) {
|
|
|
+ log_info(LD_DIRSERV, "Router %s is now rejected: %s",
|
|
|
+ description, msg?msg:"");
|
|
|
+ routerlist_remove(rl, ent, 0, time(NULL));
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (bool_neq((r & FP_INVALID), !node->is_valid)) {
|
|
|
+ log_info(LD_DIRSERV, "Router '%s' is now %svalid.", description,
|
|
|
+ (r&FP_INVALID) ? "in" : "");
|
|
|
+ node->is_valid = (r&FP_INVALID)?0:1;
|
|
|
+ }
|
|
|
+ if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) {
|
|
|
+ log_info(LD_DIRSERV, "Router '%s' is now a %s exit", description,
|
|
|
+ (r & FP_BADEXIT) ? "bad" : "good");
|
|
|
+ node->is_bad_exit = (r&FP_BADEXIT) ? 1: 0;
|
|
|
+ }
|
|
|
+ } SMARTLIST_FOREACH_END(node);
|
|
|
+
|
|
|
+ routerlist_assert_ok(rl);
|
|
|
+ smartlist_free(nodes);
|
|
|
+}
|