diff options
Diffstat (limited to 'src/feature')
-rw-r--r-- | src/feature/control/control_events.c | 29 | ||||
-rw-r--r-- | src/feature/control/control_events.h | 7 | ||||
-rw-r--r-- | src/feature/control/control_hs.c | 21 | ||||
-rw-r--r-- | src/feature/dirauth/process_descs.c | 78 | ||||
-rw-r--r-- | src/feature/hs/hs_circuit.c | 109 | ||||
-rw-r--r-- | src/feature/hs/hs_circuit.h | 6 | ||||
-rw-r--r-- | src/feature/hs/hs_client.c | 346 | ||||
-rw-r--r-- | src/feature/hs/hs_client.h | 11 | ||||
-rw-r--r-- | src/feature/hs/hs_service.c | 48 | ||||
-rw-r--r-- | src/feature/hs/hs_service.h | 2 | ||||
-rw-r--r-- | src/feature/rend/rendclient.c | 63 | ||||
-rw-r--r-- | src/feature/rend/rendclient.h | 3 |
12 files changed, 524 insertions, 199 deletions
diff --git a/src/feature/control/control_events.c b/src/feature/control/control_events.c index f88bcfdb9f..ea07a896d4 100644 --- a/src/feature/control/control_events.c +++ b/src/feature/control/control_events.c @@ -38,6 +38,7 @@ #include "core/or/origin_circuit_st.h" #include "lib/evloop/compat_libevent.h" +#include "lib/encoding/confline.h" static void flush_queued_events_cb(mainloop_event_t *event, void *arg); static void control_get_bytes_rw_last_sec(uint64_t *r, uint64_t *w); @@ -1770,27 +1771,24 @@ control_event_guard(const char *nickname, const char *digest, } /** Called when a configuration option changes. This is generally triggered - * by SETCONF requests and RELOAD/SIGHUP signals. The <b>elements</b> is - * a smartlist_t containing (key, value, ...) pairs in sequence. - * <b>value</b> can be NULL. */ -int -control_event_conf_changed(const smartlist_t *elements) + * by SETCONF requests and RELOAD/SIGHUP signals. The <b>changes</b> are + * a linked list of configuration key-values. + * <b>changes</b> can be NULL, meaning "no changes". + */ +void +control_event_conf_changed(const config_line_t *changes) { - int i; char *result; smartlist_t *lines; - if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) || - smartlist_len(elements) == 0) { - return 0; + if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) || !changes) { + return; } lines = smartlist_new(); - for (i = 0; i < smartlist_len(elements); i += 2) { - char *k = smartlist_get(elements, i); - char *v = smartlist_get(elements, i+1); - if (v == NULL) { - smartlist_add_asprintf(lines, "650-%s", k); + for (const config_line_t *line = changes; line; line = line->next) { + if (line->value == NULL) { + smartlist_add_asprintf(lines, "650-%s", line->key); } else { - smartlist_add_asprintf(lines, "650-%s=%s", k, v); + smartlist_add_asprintf(lines, "650-%s=%s", line->key, line->value); } } result = smartlist_join_strings(lines, "\r\n", 0, NULL); @@ -1799,7 +1797,6 @@ control_event_conf_changed(const smartlist_t *elements) tor_free(result); SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); smartlist_free(lines); - return 0; } /** We just generated a new summary of which countries we've seen clients diff --git a/src/feature/control/control_events.h b/src/feature/control/control_events.h index 34986fdb89..edb0e90cd9 100644 --- a/src/feature/control/control_events.h +++ b/src/feature/control/control_events.h @@ -13,6 +13,9 @@ #define TOR_CONTROL_EVENTS_H #include "core/or/ocirc_event.h" +#include "core/or/orconn_event.h" + +struct config_line_t; /** Used to indicate the type of a CIRC_MINOR event passed to the controller. * The various types are defined in control-spec.txt . */ @@ -21,8 +24,6 @@ typedef enum circuit_status_minor_event_t { CIRC_MINOR_EVENT_CANNIBALIZED, } circuit_status_minor_event_t; -#include "core/or/orconn_event.h" - /** Used to indicate the type of a stream event passed to the controller. * The various types are defined in control-spec.txt */ typedef enum stream_status_event_t { @@ -157,7 +158,7 @@ int control_event_server_error(const char *format, ...) int control_event_guard(const char *nickname, const char *digest, const char *status); -int control_event_conf_changed(const smartlist_t *elements); +void control_event_conf_changed(const struct config_line_t *changes); int control_event_buildtimeout_set(buildtimeout_set_event_t type, const char *args); int control_event_signal(uintptr_t signal); diff --git a/src/feature/control/control_hs.c b/src/feature/control/control_hs.c index 4c1d16a8c8..128893bc6c 100644 --- a/src/feature/control/control_hs.c +++ b/src/feature/control/control_hs.c @@ -73,7 +73,6 @@ const control_cmd_syntax_t onion_client_auth_add_syntax = { * register the new client-side client auth credentials: * "ONION_CLIENT_AUTH_ADD" SP HSAddress * SP KeyType ":" PrivateKeyBlob - * [SP "ClientName=" Nickname] * [SP "Type=" TYPE] CRLF */ int @@ -112,14 +111,7 @@ handle_control_onion_client_auth_add(control_connection_t *conn, /* Now let's parse the remaining arguments (variable size) */ for (const config_line_t *line = args->kwargs; line; line = line->next) { - if (!strcasecmp(line->key, "ClientName")) { - if (strlen(line->value) > HS_CLIENT_AUTH_MAX_NICKNAME_LENGTH) { - control_write_endreply(conn, 512, "Too big 'ClientName' argument"); - goto err; - } - creds->nickname = tor_strdup(line->value); - - } else if (!strcasecmpstart(line->key, "Flags")) { + if (!strcasecmpstart(line->key, "Flags")) { smartlist_split_string(flags, line->value, ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { control_write_endreply(conn, 512, "Invalid 'Flags' argument"); @@ -145,6 +137,10 @@ handle_control_onion_client_auth_add(control_connection_t *conn, /* It's a bug because the service addr has already been validated above */ control_printf_endreply(conn, 512, "Invalid v3 address \"%s\"", hsaddress); break; + case REGISTER_FAIL_PERMANENT_STORAGE: + control_printf_endreply(conn, 553, "Unable to store creds for \"%s\"", + hsaddress); + break; case REGISTER_SUCCESS_ALREADY_EXISTS: control_printf_endreply(conn, 251,"Client for onion existed and replaced"); break; @@ -243,11 +239,8 @@ encode_client_auth_cred_for_control_port( goto err; } - smartlist_add_asprintf(control_line, "CLIENT x25519:%s", x25519_b64); - - if (cred->nickname) { /* nickname is optional */ - smartlist_add_asprintf(control_line, " ClientName=%s", cred->nickname); - } + smartlist_add_asprintf(control_line, "CLIENT %s x25519:%s", + cred->onion_address, x25519_b64); if (cred->flags) { /* flags are also optional */ if (cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { diff --git a/src/feature/dirauth/process_descs.c b/src/feature/dirauth/process_descs.c index 71e3195c01..8dae4e9335 100644 --- a/src/feature/dirauth/process_descs.c +++ b/src/feature/dirauth/process_descs.c @@ -53,24 +53,24 @@ dirserv_get_status_impl(const char *fp, const char *nickname, int severity); /* 1 Historically used to indicate Named */ -#define FP_INVALID 2 /**< Believed invalid. */ -#define FP_REJECT 4 /**< We will not publish this router. */ +#define RTR_INVALID 2 /**< Believed invalid. */ +#define RTR_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. */ +#define RTR_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; +typedef uint32_t rtr_flags_t; static void add_fingerprint_to_dir(const char *fp, struct authdir_config_t *list, - router_status_t add_status); + rtr_flags_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. */ + digestmap_t *status_by_digest; /**< Map from digest to rtr_flags_t. */ } authdir_config_t; /** Should be static; exposed for testing. */ @@ -92,11 +92,11 @@ authdir_config_new(void) */ /* static */ void add_fingerprint_to_dir(const char *fp, authdir_config_t *list, - router_status_t add_status) + rtr_flags_t add_status) { char *fingerprint; char d[DIGEST_LEN]; - router_status_t *status; + rtr_flags_t *status; tor_assert(fp); tor_assert(list); @@ -112,7 +112,7 @@ add_fingerprint_to_dir(const char *fp, authdir_config_t *list, status = digestmap_get(list->status_by_digest, d); if (!status) { - status = tor_malloc_zero(sizeof(router_status_t)); + status = tor_malloc_zero(sizeof(rtr_flags_t)); digestmap_set(list->status_by_digest, d, status); } @@ -175,7 +175,7 @@ dirserv_load_fingerprint_file(void) for (list=front; list; list=list->next) { char digest_tmp[DIGEST_LEN]; - router_status_t add_status = 0; + rtr_flags_t add_status = 0; nickname = list->key; fingerprint = list->value; tor_strstrip(fingerprint, " "); /* remove spaces */ if (strlen(fingerprint) != HEX_DIGEST_LEN || @@ -188,11 +188,11 @@ dirserv_load_fingerprint_file(void) continue; } if (!strcasecmp(nickname, "!reject")) { - add_status = FP_REJECT; + add_status = RTR_REJECT; } else if (!strcasecmp(nickname, "!badexit")) { - add_status = FP_BADEXIT; + add_status = RTR_BADEXIT; } else if (!strcasecmp(nickname, "!invalid")) { - add_status = FP_INVALID; + add_status = RTR_INVALID; } add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status); } @@ -225,7 +225,7 @@ dirserv_load_fingerprint_file(void) * * Return the appropriate router status. * - * If the status is 'FP_REJECT' and <b>msg</b> is provided, set + * If the status is 'RTR_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, @@ -238,7 +238,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, log_warn(LD_BUG,"Error computing fingerprint"); if (msg) *msg = "Bug: Error computing fingerprint"; - return FP_REJECT; + return RTR_REJECT; } /* Check for the more common reasons to reject a router first. */ @@ -259,7 +259,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, "key.", router_describe(router)); if (msg) *msg = "Missing ntor curve25519 onion key. Please upgrade!"; - return FP_REJECT; + return RTR_REJECT; } if (router->cache_info.signing_key_cert) { @@ -275,7 +275,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, if (msg) { *msg = "Ed25519 identity key or RSA identity key has changed."; } - return FP_REJECT; + return RTR_REJECT; } } } else { @@ -292,7 +292,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, if (msg) { *msg = "Ed25519 identity key has disappeared."; } - return FP_REJECT; + return RTR_REJECT; } #endif /* defined(DISABLE_DISABLING_ED25519) */ } @@ -312,7 +312,7 @@ dirserv_would_reject_router(const routerstatus_t *rs) rs->addr, rs->or_port, NULL, NULL, LOG_DEBUG); - return (res & FP_REJECT) != 0; + return (res & RTR_REJECT) != 0; } /** @@ -369,7 +369,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, const char *platform, const char **msg, int severity) { uint32_t result = 0; - router_status_t *status_by_digest; + rtr_flags_t *status_by_digest; if (!fingerprint_list) fingerprint_list = authdir_config_new(); @@ -384,13 +384,13 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, if (msg) { *msg = "Malformed platform string."; } - return FP_REJECT; + return RTR_REJECT; } } /* Check whether the version is obsolete, broken, insecure, etc... */ if (platform && dirserv_rejects_tor_version(platform, msg)) { - return FP_REJECT; + return RTR_REJECT; } status_by_digest = digestmap_get(fingerprint_list->status_by_digest, @@ -398,14 +398,14 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, if (status_by_digest) result |= *status_by_digest; - if (result & FP_REJECT) { + if (result & RTR_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) { + return RTR_REJECT; + } else if (result & RTR_INVALID) { if (msg) *msg = "Fingerprint is marked invalid"; } @@ -414,7 +414,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, log_fn(severity, LD_DIRSERV, "Marking '%s' as bad exit because of address '%s'", nickname, fmt_addr32(addr)); - result |= FP_BADEXIT; + result |= RTR_BADEXIT; } if (!authdir_policy_permits_address(addr, or_port)) { @@ -425,13 +425,13 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, "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; + return RTR_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; + result |= RTR_INVALID; } return result; @@ -501,7 +501,7 @@ authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, 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) + if (status & RTR_REJECT) return -1; /* msg is already set. */ /* Is there too much clock skew? */ @@ -537,7 +537,7 @@ authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, return -1; } - *valid_out = ! (status & FP_INVALID); + *valid_out = ! (status & RTR_INVALID); return 0; } @@ -549,8 +549,8 @@ 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; + node->is_valid = (authstatus & RTR_INVALID) ? 0 : 1; + node->is_bad_exit = (authstatus & RTR_BADEXIT) ? 1 : 0; } /** True iff <b>a</b> is more severe than <b>b</b>. */ @@ -864,21 +864,21 @@ directory_remove_invalid(void) continue; r = dirserv_router_get_status(ent, &msg, LOG_INFO); description = router_describe(ent); - if (r & FP_REJECT) { + if (r & RTR_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)) { + if (bool_neq((r & RTR_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; + (r&RTR_INVALID) ? "in" : ""); + node->is_valid = (r&RTR_INVALID)?0:1; } - if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) { + if (bool_neq((r & RTR_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; + (r & RTR_BADEXIT) ? "bad" : "good"); + node->is_bad_exit = (r&RTR_BADEXIT) ? 1: 0; } } SMARTLIST_FOREACH_END(node); diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index 9d21acf42f..a09e319729 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -20,11 +20,13 @@ #include "feature/hs/hs_cell.h" #include "feature/hs/hs_circuit.h" #include "feature/hs/hs_circuitmap.h" +#include "feature/hs/hs_client.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_service.h" #include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" #include "feature/rend/rendservice.h" +#include "feature/rend/rendclient.h" #include "feature/stats/rephist.h" #include "lib/crypt_ops/crypto_dh.h" #include "lib/crypt_ops/crypto_rand.h" @@ -618,6 +620,22 @@ setup_introduce1_data(const hs_desc_intro_point_t *ip, return ret; } +/** Helper: cleanup function for client circuit. This is for every HS version. + * It is called from hs_circ_cleanup_on_free() entry point. */ +static void +cleanup_on_free_client_circ(circuit_t *circ) +{ + tor_assert(circ); + + if (circuit_is_hs_v2(circ)) { + rend_client_circuit_cleanup_on_free(circ); + } else if (circuit_is_hs_v3(circ)) { + hs_client_circuit_cleanup_on_free(circ); + } + /* It is possible the circuit has an HS purpose but no identifier (rend_data + * or hs_ident). Thus possible that this passess through. */ +} + /* ========== */ /* Public API */ /* ========== */ @@ -655,7 +673,8 @@ hs_circ_service_get_established_intro_circ(const hs_service_intro_point_t *ip) } /* Only return circuit if it is established. */ - return (TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO) ? circ : NULL; + return (circ && TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO) ? + circ : NULL; } /** Called when we fail building a rendezvous circuit at some point other than @@ -1197,29 +1216,83 @@ hs_circ_send_establish_rendezvous(origin_circuit_t *circ) return -1; } -/** We are about to close or free this <b>circ</b>. Clean it up from any - * related HS data structures. This function can be called multiple times - * safely for the same circuit. */ +/** Circuit cleanup strategy: + * + * What follows is a series of functions that notifies the HS subsystem of 3 + * different circuit cleanup phase: close, free and repurpose. + * + * Tor can call any of those in any orders so they have to be safe between + * each other. In other words, the free should never depend on close to be + * called before. + * + * The "on_close()" is called from circuit_mark_for_close() which is + * considered the tor fast path and thus as little work as possible should + * done in that function. Currently, we only remove the circuit from the HS + * circuit map and move on. + * + * The "on_free()" is called from circuit circuit_free_() and it is very + * important that at the end of the function, no state or objects related to + * this circuit remains alive. + * + * The "on_repurpose()" is called from circuit_change_purpose() for which we + * simply remove it from the HS circuit map. We do not have other cleanup + * requirements after that. + * + * NOTE: The onion service code, specifically the service code, cleans up + * lingering objects or state if any of its circuit disappear which is why + * our cleanup strategy doesn't involve any service specific actions. As long + * as the circuit is removed from the HS circuit map, it won't be used. + */ + +/** We are about to close this <b>circ</b>. Clean it up from any related HS + * data structures. This function can be called multiple times safely for the + * same circuit. */ +void +hs_circ_cleanup_on_close(circuit_t *circ) +{ + tor_assert(circ); + + /* On close, we simply remove it from the circuit map. It can not be used + * anymore. We keep this code path fast and lean. */ + + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); + } +} + +/** We are about to free this <b>circ</b>. Clean it up from any related HS + * data structures. This function can be called multiple times safely for the + * same circuit. */ void -hs_circ_cleanup(circuit_t *circ) +hs_circ_cleanup_on_free(circuit_t *circ) { tor_assert(circ); - /* If it's a service-side intro circ, notify the HS subsystem for the intro - * point circuit closing so it can be dealt with cleanly. */ - if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || - circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { - hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + /* NOTE: Bulk of the work of cleaning up a circuit is done here. */ + + if (circuit_purpose_is_hs_client(circ->purpose)) { + cleanup_on_free_client_circ(circ); + } + + /* We have no assurance that the given HS circuit has been closed before and + * thus removed from the HS map. This actually happens in unit tests. */ + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); } +} + +/** We are about to repurpose this <b>circ</b>. Clean it up from any related + * HS data structures. This function can be called multiple times safely for + * the same circuit. */ +void +hs_circ_cleanup_on_repurpose(circuit_t *circ) +{ + tor_assert(circ); + + /* On repurpose, we simply remove it from the circuit map but we do not do + * the on_free actions since we don't treat a repurpose as something we need + * to report in the client cache failure. */ - /* Clear HS circuitmap token for this circ (if any). Very important to be - * done after the HS subsystem has been notified of the close else the - * circuit will not be found. - * - * We do this at the close if possible because from that point on, the - * circuit is good as dead. We can't rely on removing it in the circuit - * free() function because we open a race window between the close and free - * where we can't register a new circuit for the same intro point. */ if (circ->hs_token) { hs_circuitmap_remove_circuit(circ); } diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h index c817f3e37a..42e5ca1348 100644 --- a/src/feature/hs/hs_circuit.h +++ b/src/feature/hs/hs_circuit.h @@ -14,8 +14,10 @@ #include "feature/hs/hs_service.h" -/* Cleanup function when the circuit is closed or/and freed. */ -void hs_circ_cleanup(circuit_t *circ); +/* Cleanup function when the circuit is closed or freed. */ +void hs_circ_cleanup_on_close(circuit_t *circ); +void hs_circ_cleanup_on_free(circuit_t *circ); +void hs_circ_cleanup_on_repurpose(circuit_t *circ); /* Circuit API. */ int hs_circ_service_intro_has_opened(hs_service_t *service, diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index 787b29b576..b5030da473 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -1445,6 +1445,80 @@ client_dir_fetch_unexpected(dir_connection_t *dir_conn, const char *reason, NULL); } +/** Get the full filename for storing the client auth credentials for the + * service in <b>onion_address</b>. The base directory is <b>dir</b>. + * This function never returns NULL. */ +static char * +get_client_auth_creds_filename(const char *onion_address, + const char *dir) +{ + char *full_fname = NULL; + char *fname; + + tor_asprintf(&fname, "%s.auth_private", onion_address); + full_fname = hs_path_from_filename(dir, fname); + tor_free(fname); + + return full_fname; +} + +/** Permanently store the credentials in <b>creds</b> to disk. + * + * Return -1 if there was an error while storing the credentials, otherwise + * return 0. + */ +static int +store_permanent_client_auth_credentials( + const hs_client_service_authorization_t *creds) +{ + const or_options_t *options = get_options(); + char *full_fname = NULL; + char *file_contents = NULL; + char priv_key_b32[BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)+1]; + int retval = -1; + + tor_assert(creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT); + + /* We need ClientOnionAuthDir to be set, otherwise we can't proceed */ + if (!options->ClientOnionAuthDir) { + log_warn(LD_GENERAL, "Can't register permanent client auth credentials " + "for %s without ClientOnionAuthDir option. Discarding.", + creds->onion_address); + goto err; + } + + /* Make sure the directory exists and is private enough. */ + if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) { + goto err; + } + + /* Get filename that we should store the credentials */ + full_fname = get_client_auth_creds_filename(creds->onion_address, + options->ClientOnionAuthDir); + + /* Encode client private key */ + base32_encode(priv_key_b32, sizeof(priv_key_b32), + (char*)creds->enc_seckey.secret_key, + sizeof(creds->enc_seckey.secret_key)); + + /* Get the full file contents and write it to disk! */ + tor_asprintf(&file_contents, "%s:descriptor:x25519:%s", + creds->onion_address, priv_key_b32); + if (write_str_to_file(full_fname, file_contents, 0) < 0) { + log_warn(LD_GENERAL, "Failed to write client auth creds file for %s!", + creds->onion_address); + goto err; + } + + retval = 0; + + err: + tor_free(file_contents); + tor_free(full_fname); + + return retval; +} + /** Register the credential <b>creds</b> as part of the client auth subsystem. * * Takes ownership of <b>creds</b>. @@ -1468,6 +1542,15 @@ hs_client_register_auth_credentials(hs_client_service_authorization_t *creds) return REGISTER_FAIL_BAD_ADDRESS; } + /* If we reach this point, the credentials will be stored one way or another: + * Make them permanent if the user asked us to. */ + if (creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { + if (store_permanent_client_auth_credentials(creds) < 0) { + client_service_authorization_free(creds); + return REGISTER_FAIL_PERMANENT_STORAGE; + } + } + old_creds = digest256map_get(client_auths, service_identity_pk.pubkey); if (old_creds) { digest256map_remove(client_auths, service_identity_pk.pubkey); @@ -1486,6 +1569,128 @@ hs_client_register_auth_credentials(hs_client_service_authorization_t *creds) return retval; } +/** Load a client authorization file with <b>filename</b> that is stored under + * the global client auth directory, and return a newly-allocated credentials + * object if it parsed well. Otherwise, return NULL. + */ +static hs_client_service_authorization_t * +get_creds_from_client_auth_filename(const char *filename, + const or_options_t *options) +{ + hs_client_service_authorization_t *auth = NULL; + char *client_key_file_path = NULL; + char *client_key_str = NULL; + + log_info(LD_REND, "Loading a client authorization key file %s...", + filename); + + if (!auth_key_filename_is_valid(filename)) { + log_notice(LD_REND, "Client authorization unrecognized filename %s. " + "File must end in .auth_private. Ignoring.", + filename); + goto err; + } + + /* Create a full path for a file. */ + client_key_file_path = hs_path_from_filename(options->ClientOnionAuthDir, + filename); + + client_key_str = read_file_to_str(client_key_file_path, 0, NULL); + if (!client_key_str) { + log_warn(LD_REND, "The file %s cannot be read.", filename); + goto err; + } + + auth = parse_auth_file_content(client_key_str); + if (!auth) { + goto err; + } + + err: + tor_free(client_key_str); + tor_free(client_key_file_path); + + return auth; +} + +/* + * Remove the file in <b>filename</b> under the global client auth credential + * storage. + */ +static void +remove_client_auth_creds_file(const char *filename) +{ + char *creds_file_path = NULL; + const or_options_t *options = get_options(); + + creds_file_path = hs_path_from_filename(options->ClientOnionAuthDir, + filename); + if (tor_unlink(creds_file_path) != 0) { + log_warn(LD_REND, "Failed to remove client auth file (%s).", + creds_file_path); + goto end; + } + + log_warn(LD_REND, "Successfuly removed client auth file (%s).", + creds_file_path); + + end: + tor_free(creds_file_path); +} + +/** + * Find the filesystem file corresponding to the permanent client auth + * credentials in <b>cred</b> and remove it. + */ +static void +find_and_remove_client_auth_creds_file( + const hs_client_service_authorization_t *cred) +{ + smartlist_t *file_list = NULL; + const or_options_t *options = get_options(); + + tor_assert(cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT); + + if (!options->ClientOnionAuthDir) { + log_warn(LD_REND, "Found permanent credential but no ClientOnionAuthDir " + "configured. There is no file to be removed."); + goto end; + } + + file_list = tor_listdir(options->ClientOnionAuthDir); + if (file_list == NULL) { + log_warn(LD_REND, "Client authorization key directory %s can't be listed.", + options->ClientOnionAuthDir); + goto end; + } + + SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) { + hs_client_service_authorization_t *tmp_cred = NULL; + + tmp_cred = get_creds_from_client_auth_filename(filename, options); + if (!tmp_cred) { + continue; + } + + /* Find the right file for this credential */ + if (!strcmp(tmp_cred->onion_address, cred->onion_address)) { + /* Found it! Remove the file! */ + remove_client_auth_creds_file(filename); + /* cleanup and get out of here */ + client_service_authorization_free(tmp_cred); + break; + } + + client_service_authorization_free(tmp_cred); + } SMARTLIST_FOREACH_END(filename); + + end: + if (file_list) { + SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); + smartlist_free(file_list); + } +} + /** Remove client auth credentials for the service <b>hs_address</b>. */ hs_client_removal_auth_status_t hs_client_remove_auth_credentials(const char *hsaddress) @@ -1502,8 +1707,14 @@ hs_client_remove_auth_credentials(const char *hsaddress) hs_client_service_authorization_t *cred = NULL; cred = digest256map_remove(client_auths, service_identity_pk.pubkey); + /* digestmap_remove() returns the previously stored data if there were any */ if (cred) { + if (cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { + /* These creds are stored on disk: remove the corresponding file. */ + find_and_remove_client_auth_creds_file(cred); + } + client_service_authorization_free(cred); return REMOVAL_SUCCESS; } @@ -1522,6 +1733,56 @@ get_hs_client_auths_map(void) /* Public API */ /* ========== */ +/** Called when a circuit was just cleaned up. This is done right before the + * circuit is freed. */ +void +hs_client_circuit_cleanup_on_free(const circuit_t *circ) +{ + bool has_timed_out; + rend_intro_point_failure_t failure = INTRO_POINT_FAILURE_GENERIC; + const origin_circuit_t *orig_circ = NULL; + + tor_assert(circ); + tor_assert(CIRCUIT_IS_ORIGIN(circ)); + + orig_circ = CONST_TO_ORIGIN_CIRCUIT(circ); + tor_assert(orig_circ->hs_ident); + + has_timed_out = + (circ->marked_for_close_orig_reason == END_CIRC_REASON_TIMEOUT); + if (has_timed_out) { + failure = INTRO_POINT_FAILURE_TIMEOUT; + } + + switch (circ->purpose) { + case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: + log_info(LD_REND, "Failed v3 intro circ for service %s to intro point %s " + "(awaiting ACK). Failure code: %d", + safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)), + safe_str_client(build_state_get_exit_nickname(orig_circ->build_state)), + failure); + hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk, + &orig_circ->hs_ident->intro_auth_pk, + failure); + break; + case CIRCUIT_PURPOSE_C_INTRODUCING: + if (has_timed_out || !orig_circ->build_state) { + break; + } + failure = INTRO_POINT_FAILURE_UNREACHABLE; + log_info(LD_REND, "Failed v3 intro circ for service %s to intro point %s " + "(while building circuit). Marking as unreachable.", + safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)), + safe_str_client(build_state_get_exit_nickname(orig_circ->build_state))); + hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk, + &orig_circ->hs_ident->intro_auth_pk, + failure); + break; + default: + break; + } +} + /** A circuit just finished connecting to a hidden service that the stream * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */ void @@ -1749,10 +2010,6 @@ client_service_authorization_free_(hs_client_service_authorization_t *auth) return; } - if (auth->nickname) { - tor_free(auth->nickname); - } - memwipe(auth, 0, sizeof(*auth)); tor_free(auth); } @@ -1795,6 +2052,13 @@ auth_key_filename_is_valid(const char *filename) return ret; } +/** Parse the client auth credentials off a string in <b>client_key_str</b> + * based on the file format documented in the "Client side configuration" + * section of rend-spec-v3.txt. + * + * Return NULL if there was an error, otherwise return a newly allocated + * hs_client_service_authorization_t structure. + */ STATIC hs_client_service_authorization_t * parse_auth_file_content(const char *client_key_str) { @@ -1825,7 +2089,7 @@ parse_auth_file_content(const char *client_key_str) goto err; } - if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) { + if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_SECKEY_LEN)) { log_warn(LD_REND, "Client authorization encoded base32 private key " "length is invalid: %s", seckey_b32); goto err; @@ -1842,6 +2106,9 @@ parse_auth_file_content(const char *client_key_str) } strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32); + /* We are reading this from the disk, so set the permanent flag anyway. */ + auth->flags |= CLIENT_AUTH_FLAG_IS_PERMANENT; + /* Success. */ goto done; @@ -1868,10 +2135,7 @@ hs_config_client_authorization(const or_options_t *options, { int ret = -1; digest256map_t *auths = digest256map_new(); - char *key_dir = NULL; smartlist_t *file_list = NULL; - char *client_key_str = NULL; - char *client_key_file_path = NULL; tor_assert(options); @@ -1882,82 +2146,54 @@ hs_config_client_authorization(const or_options_t *options, goto end; } - key_dir = tor_strdup(options->ClientOnionAuthDir); - /* Make sure the directory exists and is private enough. */ - if (check_private_dir(key_dir, 0, options->User) < 0) { + if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) { goto end; } - file_list = tor_listdir(key_dir); + file_list = tor_listdir(options->ClientOnionAuthDir); if (file_list == NULL) { log_warn(LD_REND, "Client authorization key directory %s can't be listed.", - key_dir); + options->ClientOnionAuthDir); goto end; } - SMARTLIST_FOREACH_BEGIN(file_list, char *, filename) { - + SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) { hs_client_service_authorization_t *auth = NULL; ed25519_public_key_t identity_pk; - log_info(LD_REND, "Loading a client authorization key file %s...", - filename); - if (!auth_key_filename_is_valid(filename)) { - log_notice(LD_REND, "Client authorization unrecognized filename %s. " - "File must end in .auth_private. Ignoring.", - filename); + auth = get_creds_from_client_auth_filename(filename, options); + if (!auth) { continue; } - /* Create a full path for a file. */ - client_key_file_path = hs_path_from_filename(key_dir, filename); - client_key_str = read_file_to_str(client_key_file_path, 0, NULL); - /* Free the file path immediately after using it. */ - tor_free(client_key_file_path); - - /* If we cannot read the file, continue with the next file. */ - if (!client_key_str) { - log_warn(LD_REND, "The file %s cannot be read.", filename); + /* Parse the onion address to get an identity public key and use it + * as a key of global map in the future. */ + if (hs_parse_address(auth->onion_address, &identity_pk, + NULL, NULL) < 0) { + log_warn(LD_REND, "The onion address \"%s\" is invalid in " + "file %s", filename, auth->onion_address); + client_service_authorization_free(auth); continue; } - auth = parse_auth_file_content(client_key_str); - /* Free immediately after using it. */ - tor_free(client_key_str); - - if (auth) { - /* Parse the onion address to get an identity public key and use it - * as a key of global map in the future. */ - if (hs_parse_address(auth->onion_address, &identity_pk, - NULL, NULL) < 0) { - log_warn(LD_REND, "The onion address \"%s\" is invalid in " - "file %s", filename, auth->onion_address); - client_service_authorization_free(auth); - continue; - } - - if (digest256map_get(auths, identity_pk.pubkey)) { + if (digest256map_get(auths, identity_pk.pubkey)) { log_warn(LD_REND, "Duplicate authorization for the same hidden " - "service address %s.", + "service address %s.", safe_str_client_opts(options, auth->onion_address)); client_service_authorization_free(auth); goto end; - } - - digest256map_set(auths, identity_pk.pubkey, auth); - log_info(LD_REND, "Loaded a client authorization key file %s.", - filename); } + + digest256map_set(auths, identity_pk.pubkey, auth); + log_info(LD_REND, "Loaded a client authorization key file %s.", + filename); } SMARTLIST_FOREACH_END(filename); /* Success. */ ret = 0; end: - tor_free(key_dir); - tor_free(client_key_str); - tor_free(client_key_file_path); if (file_list) { SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); smartlist_free(file_list); diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 04827ea92a..959ba136cd 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -10,6 +10,8 @@ #define TOR_HS_CLIENT_H #include "lib/crypt_ops/crypto_ed25519.h" + +#include "feature/hs/hs_circuit.h" #include "feature/hs/hs_descriptor.h" #include "feature/hs/hs_ident.h" @@ -43,6 +45,8 @@ typedef enum { REGISTER_SUCCESS_AND_DECRYPTED, /* We failed to register these credentials, because of a bad HS address. */ REGISTER_FAIL_BAD_ADDRESS, + /* We failed to register these credentials, because of a bad HS address. */ + REGISTER_FAIL_PERMANENT_STORAGE, } hs_client_register_auth_status_t; /* Status code of client auth credential removal */ @@ -58,9 +62,6 @@ typedef enum { /** Flag to set when a client auth is permanent (saved on disk). */ #define CLIENT_AUTH_FLAG_IS_PERMANENT (1<<0) -/** Max length of a client auth nickname */ -#define HS_CLIENT_AUTH_MAX_NICKNAME_LENGTH 255 - /** Client-side configuration of client authorization */ typedef struct hs_client_service_authorization_t { /** An curve25519 secret key used to compute decryption keys that @@ -70,9 +71,6 @@ typedef struct hs_client_service_authorization_t { /** An onion address that is used to connect to the onion service. */ char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1]; - /* An optional nickname for this client */ - char *nickname; - /* Optional flags for this client. */ int flags; } hs_client_service_authorization_t; @@ -112,6 +110,7 @@ int hs_client_send_introduce1(origin_circuit_t *intro_circ, origin_circuit_t *rend_circ); void hs_client_circuit_has_opened(origin_circuit_t *circ); +void hs_client_circuit_cleanup_on_free(const circuit_t *circ); int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, const uint8_t *payload, diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index 5693cdb0f1..3d152afa16 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -2404,12 +2404,10 @@ static void cleanup_intro_points(hs_service_t *service, time_t now) { /* List of intro points to close. We can't mark the intro circuits for close - * in the modify loop because doing so calls - * hs_service_intro_circ_has_closed() which does a digest256map_get() on the - * intro points map (that we are iterating over). This can't be done in a - * single iteration after a MAP_DEL_CURRENT, the object will still be - * returned leading to a use-after-free. So, we close the circuits and free - * the intro points after the loop if any. */ + * in the modify loop because doing so calls back into the HS subsystem and + * we need to keep that code path outside of the service/desc loop so those + * maps don't get modified during the close making us in a possible + * use-after-free situation. */ smartlist_t *ips_to_free = smartlist_new(); tor_assert(service); @@ -3684,44 +3682,6 @@ hs_service_get_num_services,(void)) return HT_SIZE(hs_service_map); } -/** Called once an introduction circuit is closed. If the circuit doesn't have - * a v3 identifier, it is ignored. */ -void -hs_service_intro_circ_has_closed(origin_circuit_t *circ) -{ - hs_service_t *service = NULL; - hs_service_intro_point_t *ip = NULL; - hs_service_descriptor_t *desc = NULL; - - tor_assert(circ); - - if (circ->hs_ident == NULL) { - /* This is not a v3 circuit, ignore. */ - goto end; - } - - get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); - if (service == NULL) { - /* This is possible if the circuits are closed and the service is - * immediately deleted. */ - log_info(LD_REND, "Unable to find any hidden service associated " - "identity key %s on intro circuit %u.", - ed25519_fmt(&circ->hs_ident->identity_pk), - TO_CIRCUIT(circ)->n_circ_id); - goto end; - } - if (ip == NULL) { - /* The introduction point object has already been removed probably by our - * cleanup process so ignore. */ - goto end; - } - /* Can't have an intro point object without a descriptor. */ - tor_assert(desc); - - end: - return; -} - /** Given conn, a rendezvous edge connection acting as an exit stream, look up * the hidden service for the circuit circ, and look up the port and address * based on the connection port. Assign the actual connection address. diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 6c929c7ff1..1c2d9e97ca 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -344,8 +344,6 @@ int hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); -void hs_service_intro_circ_has_closed(origin_circuit_t *circ); - char *hs_service_lookup_current_desc(const ed25519_public_key_t *pk); hs_service_add_ephemeral_status_t diff --git a/src/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index 6f4ee8b8c1..14484f1ced 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -1250,3 +1250,66 @@ rend_parse_service_authorization(const or_options_t *options, } return res; } + +/** The given circuit is being freed. Take appropriate action if it is of + * interest to the client subsystem. */ +void +rend_client_circuit_cleanup_on_free(const circuit_t *circ) +{ + int reason, orig_reason; + bool has_timed_out, ip_is_redundant; + const origin_circuit_t *ocirc = NULL; + + tor_assert(circ); + tor_assert(CIRCUIT_IS_ORIGIN(circ)); + + reason = circ->marked_for_close_reason; + orig_reason = circ->marked_for_close_orig_reason; + ocirc = CONST_TO_ORIGIN_CIRCUIT(circ); + tor_assert(ocirc->rend_data); + + has_timed_out = (reason == END_CIRC_REASON_TIMEOUT); + ip_is_redundant = (orig_reason == END_CIRC_REASON_IP_NOW_REDUNDANT); + + switch (circ->purpose) { + case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: + { + if (ip_is_redundant) { + break; + } + tor_assert(circ->state == CIRCUIT_STATE_OPEN); + tor_assert(ocirc->build_state->chosen_exit); + /* Treat this like getting a nack from it */ + log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", + safe_str_client(rend_data_get_address(ocirc->rend_data)), + safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), + has_timed_out ? "Recording timeout." : "Removing from descriptor."); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + has_timed_out ? + INTRO_POINT_FAILURE_TIMEOUT : + INTRO_POINT_FAILURE_GENERIC); + break; + } + case CIRCUIT_PURPOSE_C_INTRODUCING: + { + /* Ignore if we were introducing and it timed out, we didn't pick an exit + * point yet (IP) or the reason indicate that it was a redundant IP. */ + if (has_timed_out || !ocirc->build_state->chosen_exit || ip_is_redundant) { + break; + } + log_info(LD_REND, "Failed intro circ %s to %s " + "(building circuit to intro point). " + "Marking intro point as possibly unreachable.", + safe_str_client(rend_data_get_address(ocirc->rend_data)), + safe_str_client(build_state_get_exit_nickname( + ocirc->build_state))); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + INTRO_POINT_FAILURE_UNREACHABLE); + break; + } + default: + break; + } +} diff --git a/src/feature/rend/rendclient.h b/src/feature/rend/rendclient.h index e5f333238e..63191737c4 100644 --- a/src/feature/rend/rendclient.h +++ b/src/feature/rend/rendclient.h @@ -12,6 +12,7 @@ #ifndef TOR_RENDCLIENT_H #define TOR_RENDCLIENT_H +#include "feature/hs/hs_circuit.h" #include "feature/rend/rendcache.h" void rend_client_purge_state(void); @@ -47,5 +48,7 @@ rend_service_authorization_t *rend_client_lookup_service_authorization( const char *onion_address); void rend_service_authorization_free_all(void); +void rend_client_circuit_cleanup_on_free(const circuit_t *circ); + #endif /* !defined(TOR_RENDCLIENT_H) */ |