diff options
Diffstat (limited to 'src/feature')
312 files changed, 22679 insertions, 11810 deletions
diff --git a/src/feature/api/.may_include b/src/feature/api/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/api/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/api/feature_api.md b/src/feature/api/feature_api.md new file mode 100644 index 0000000000..3065c000aa --- /dev/null +++ b/src/feature/api/feature_api.md @@ -0,0 +1,2 @@ +@dir /feature/api +@brief feature/api: In-process interface to starting/stopping Tor. diff --git a/src/feature/api/include.am b/src/feature/api/include.am new file mode 100644 index 0000000000..8d490458d4 --- /dev/null +++ b/src/feature/api/include.am @@ -0,0 +1,11 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/api/tor_api.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/api/tor_api_internal.h + +# This may someday want to be an installed file? +noinst_HEADERS += src/feature/api/tor_api.h diff --git a/src/feature/api/tor_api.c b/src/feature/api/tor_api.c index 697397d46b..531793301e 100644 --- a/src/feature/api/tor_api.c +++ b/src/feature/api/tor_api.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -40,10 +40,10 @@ #define raw_socketpair tor_ersatz_socketpair #define raw_closesocket closesocket #define snprintf _snprintf -#else +#else /* !defined(_WIN32) */ #define raw_socketpair socketpair #define raw_closesocket close -#endif +#endif /* defined(_WIN32) */ #ifdef HAVE_UNISTD_H #include <unistd.h> diff --git a/src/feature/api/tor_api.h b/src/feature/api/tor_api.h index 2bf130c376..e9993bb0d5 100644 --- a/src/feature/api/tor_api.h +++ b/src/feature/api/tor_api.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -55,7 +55,7 @@ typedef SOCKET tor_control_socket_t; #else typedef int tor_control_socket_t; #define INVALID_TOR_CONTROL_SOCKET (-1) -#endif +#endif /* defined(_WIN32) */ /** DOCDOC */ tor_control_socket_t tor_main_configuration_setup_control_socket( diff --git a/src/feature/api/tor_api_internal.h b/src/feature/api/tor_api_internal.h index 60e0f3aa59..d52b2caf44 100644 --- a/src/feature/api/tor_api_internal.h +++ b/src/feature/api/tor_api_internal.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file tor_api_internal.h + * @brief Internal declarations for in-process Tor API. + **/ + #ifndef TOR_API_INTERNAL_H #define TOR_API_INTERNAL_H diff --git a/src/feature/client/.may_include b/src/feature/client/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/client/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/client/addressmap.c b/src/feature/client/addressmap.c index bbe786a6a2..1a6958d38c 100644 --- a/src/feature/client/addressmap.c +++ b/src/feature/client/addressmap.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,7 +22,7 @@ #include "core/or/circuituse.h" #include "app/config/config.h" #include "core/or/connection_edge.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/relay/dns.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerset.h" diff --git a/src/feature/client/addressmap.h b/src/feature/client/addressmap.h index 9179aef1d0..7f1024e09a 100644 --- a/src/feature/client/addressmap.h +++ b/src/feature/client/addressmap.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file addressmap.h + * @brief Header for addressmap.c + **/ + #ifndef TOR_ADDRESSMAP_H #define TOR_ADDRESSMAP_H @@ -62,4 +67,3 @@ STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, #endif /* defined(ADDRESSMAP_PRIVATE) */ #endif /* !defined(TOR_ADDRESSMAP_H) */ - diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c index 626c5efcae..2b52a1173d 100644 --- a/src/feature/client/bridges.c +++ b/src/feature/client/bridges.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -348,7 +348,7 @@ int node_is_a_configured_bridge(const node_t *node) { /* First, let's try searching for a bridge with matching identity. */ - if (BUG(tor_digest_is_zero(node->identity))) + if (BUG(fast_mem_is_zero(node->identity, DIGEST_LEN))) return 0; if (find_bridge_by_digest(node->identity) != NULL) @@ -844,7 +844,8 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } } - if (options->ClientPreferIPv6ORPort == -1) { + if (options->ClientPreferIPv6ORPort == -1 || + options->ClientAutoIPv6ORPort == 0) { /* Mark which address to use based on which bridge_t we got. */ node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && !tor_addr_is_null(&node->ri->ipv6_addr)); diff --git a/src/feature/client/bridges.h b/src/feature/client/bridges.h index 27b2750a45..174149cf97 100644 --- a/src/feature/client/bridges.h +++ b/src/feature/client/bridges.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c index 1743ab5a81..4ac5cb8fc9 100644 --- a/src/feature/client/circpathbias.c +++ b/src/feature/client/circpathbias.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -176,6 +176,7 @@ pathbias_get_scale_threshold(const or_options_t *options) static double pathbias_get_scale_ratio(const or_options_t *options) { + (void) options; /* * The scale factor is the denominator for our scaling * of circuit counts for our path bias window. @@ -185,7 +186,8 @@ pathbias_get_scale_ratio(const or_options_t *options) */ int denominator = networkstatus_get_param(NULL, "pb_scalefactor", 2, 2, INT32_MAX); - (void) options; + tor_assert(denominator > 0); + /** * The mult factor is the numerator for our scaling * of circuit counts for our path bias window. It @@ -301,7 +303,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) return circ->cpath && circ->cpath->next != circ->cpath && circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else /* !(defined(N2N_TAGGING_IS_POSSIBLE)) */ +#else /* !defined(N2N_TAGGING_IS_POSSIBLE) */ /* If tagging attacks are no longer possible, we probably want to * count bias from the first hop. However, one could argue that * timing-based tagging is still more useful than per-hop failure. @@ -369,8 +371,9 @@ pathbias_should_count(origin_circuit_t *circ) !circ->build_state->onehop_tunnel) { if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { log_info(LD_BUG, - "One-hop circuit has length %d. Path state is %s. " + "One-hop circuit %d has length %d. Path state is %s. " "Circuit is a %s currently %s.%s", + circ->global_identifier, circ->build_state->desired_path_len, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), @@ -398,12 +401,13 @@ pathbias_should_count(origin_circuit_t *circ) /* Check to see if the shouldcount result has changed due to a * unexpected purpose change that would affect our results */ if (circ->pathbias_shouldcount == PATHBIAS_SHOULDCOUNT_IGNORED) { - log_info(LD_BUG, - "Circuit %d is now being counted despite being ignored " - "in the past. Purpose is %s, path state is %s", - circ->global_identifier, - circuit_purpose_to_string(circ->base_.purpose), - pathbias_state_to_string(circ->path_state)); + log_info(LD_CIRC, + "Circuit %d is not being counted by pathbias because it was " + "ignored in the past. Purpose is %s, path state is %s", + circ->global_identifier, + circuit_purpose_to_string(circ->base_.purpose), + pathbias_state_to_string(circ->path_state)); + return 0; } circ->pathbias_shouldcount = PATHBIAS_SHOULDCOUNT_COUNTED; @@ -434,8 +438,9 @@ pathbias_count_build_attempt(origin_circuit_t *circ) if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, approx_time()))) { log_info(LD_BUG, - "Opened circuit is in strange path state %s. " + "Opened circuit %d is in strange path state %s. " "Circuit is a %s currently %s.%s", + circ->global_identifier, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state), @@ -468,8 +473,9 @@ pathbias_count_build_attempt(origin_circuit_t *circ) if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, approx_time()))) { log_info(LD_BUG, - "Unopened circuit has strange path state %s. " + "Unopened circuit %d has strange path state %s. " "Circuit is a %s currently %s.%s", + circ->global_identifier, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state), @@ -538,8 +544,9 @@ pathbias_count_build_success(origin_circuit_t *circ) if ((rate_msg = rate_limit_log(&success_notice_limit, approx_time()))) { log_info(LD_BUG, - "Succeeded circuit is in strange path state %s. " + "Succeeded circuit %d is in strange path state %s. " "Circuit is a %s currently %s.%s", + circ->global_identifier, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state), @@ -574,8 +581,9 @@ pathbias_count_build_success(origin_circuit_t *circ) if ((rate_msg = rate_limit_log(&success_notice_limit, approx_time()))) { log_info(LD_BUG, - "Opened circuit is in strange path state %s. " + "Opened circuit %d is in strange path state %s. " "Circuit is a %s currently %s.%s", + circ->global_identifier, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state), @@ -601,8 +609,9 @@ pathbias_count_use_attempt(origin_circuit_t *circ) if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { log_notice(LD_BUG, - "Used circuit is in strange path state %s. " + "Used circuit %d is in strange path state %s. " "Circuit is a %s currently %s.", + circ->global_identifier, pathbias_state_to_string(circ->path_state), circuit_purpose_to_string(circ->base_.purpose), circuit_state_to_string(circ->base_.state)); diff --git a/src/feature/client/circpathbias.h b/src/feature/client/circpathbias.h index a9a8d18df2..88cc982dd4 100644 --- a/src/feature/client/circpathbias.h +++ b/src/feature/client/circpathbias.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/client/dnsserv.c b/src/feature/client/dnsserv.c index cf593cfb68..c1981ecde0 100644 --- a/src/feature/client/dnsserv.c +++ b/src/feature/client/dnsserv.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -26,8 +26,9 @@ #include "app/config/config.h" #include "core/mainloop/connection.h" #include "core/or/connection_edge.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" #include "core/or/policies.h" #include "feature/control/control_connection_st.h" @@ -213,6 +214,9 @@ dnsserv_launch_request(const char *name, int reverse, edge_connection_t *conn; char *q_name; + /* Launching a request for a user counts as user activity. */ + note_user_activity(approx_time()); + /* Make a new dummy AP connection, and attach the request to it. */ entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); entry_conn->entry_cfg.dns_request = 1; @@ -234,7 +238,7 @@ dnsserv_launch_request(const char *name, int reverse, TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); } -#else /* !(defined(AF_UNIX)) */ +#else /* !defined(AF_UNIX) */ TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); #endif /* defined(AF_UNIX) */ diff --git a/src/feature/client/dnsserv.h b/src/feature/client/dnsserv.h index fff1ed2adb..4011cb4e02 100644 --- a/src/feature/client/dnsserv.h +++ b/src/feature/client/dnsserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index 6c7f8057bd..c4d3d17c62 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -114,7 +114,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confmgt.h" #include "app/config/statefile.h" #include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" @@ -128,7 +128,7 @@ #include "feature/client/circpathbias.h" #include "feature/client/entrynodes.h" #include "feature/client/transports.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/describe.h" #include "feature/nodelist/microdesc.h" @@ -1038,7 +1038,7 @@ get_max_sample_size(guard_selection_t *gs, * Return a smartlist of the all the guards that are not currently * members of the sample (GUARDS - SAMPLED_GUARDS). The elements of * this list are node_t pointers in the non-bridge case, and - * bridge_info_t pointers in the bridge case. Set *<b>n_guards_out/b> + * bridge_info_t pointers in the bridge case. Set *<b>n_guards_out</b> * to the number of guards that we found in GUARDS, including those * that were already sampled. */ diff --git a/src/feature/client/entrynodes.h b/src/feature/client/entrynodes.h index 4e5eb4e960..6eede2c8d4 100644 --- a/src/feature/client/entrynodes.h +++ b/src/feature/client/entrynodes.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,7 +15,7 @@ #include "lib/container/handles.h" /* Forward declare for guard_selection_t; entrynodes.c has the real struct */ -typedef struct guard_selection_s guard_selection_t; +typedef struct guard_selection_t guard_selection_t; /* Forward declare for entry_guard_t; the real declaration is private. */ typedef struct entry_guard_t entry_guard_t; @@ -28,7 +28,7 @@ typedef struct circuit_guard_state_t circuit_guard_state_t; private. */ typedef struct entry_guard_restriction_t entry_guard_restriction_t; -/* Information about a guard's pathbias status. +/** Information about a guard's pathbias status. * These fields are used in circpathbias.c to try to detect entry * nodes that are failing circuits at a suspicious frequency. */ @@ -210,7 +210,7 @@ typedef enum guard_selection_type_t { * See the module documentation for entrynodes.c for more information * about guard selection algorithms. */ -struct guard_selection_s { +struct guard_selection_t { /** * The name for this guard-selection object. (Must not contain spaces). */ diff --git a/src/feature/client/feature_client.md b/src/feature/client/feature_client.md new file mode 100644 index 0000000000..dd4bf78ec8 --- /dev/null +++ b/src/feature/client/feature_client.md @@ -0,0 +1,5 @@ +@dir /feature/client +@brief feature/client: Client-specific code + +(There is also a bunch of client-specific code in other modules.) + diff --git a/src/feature/client/include.am b/src/feature/client/include.am new file mode 100644 index 0000000000..53c9f047d4 --- /dev/null +++ b/src/feature/client/include.am @@ -0,0 +1,20 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/client/addressmap.c \ + src/feature/client/bridges.c \ + src/feature/client/circpathbias.c \ + src/feature/client/dnsserv.c \ + src/feature/client/entrynodes.c \ + src/feature/client/proxymode.c \ + src/feature/client/transports.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/client/addressmap.h \ + src/feature/client/bridges.h \ + src/feature/client/circpathbias.h \ + src/feature/client/dnsserv.h \ + src/feature/client/entrynodes.h \ + src/feature/client/proxymode.h \ + src/feature/client/transports.h diff --git a/src/feature/client/proxymode.c b/src/feature/client/proxymode.c new file mode 100644 index 0000000000..aa269ec7fb --- /dev/null +++ b/src/feature/client/proxymode.c @@ -0,0 +1,32 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file proxymode.c + * @brief Determine whether we are trying to be a proxy. + **/ + +#include "core/or/or.h" + +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "core/or/port_cfg_st.h" +#include "feature/client/proxymode.h" + +/** 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; +} diff --git a/src/feature/client/proxymode.h b/src/feature/client/proxymode.h new file mode 100644 index 0000000000..30be08ff78 --- /dev/null +++ b/src/feature/client/proxymode.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file proxymode.h + * \brief Header file for proxymode.c. + **/ + +#ifndef TOR_PROXYMODE_H +#define TOR_PROXYMODE_H + +int proxy_mode(const or_options_t *options); + +#endif /* !defined(TOR_PROXYMODE_H) */ diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c index 0d1342c87b..a8ea9781a4 100644 --- a/src/feature/client/transports.c +++ b/src/feature/client/transports.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2019, The Tor Project, Inc. */ +/* Copyright (c) 2011-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -97,15 +97,19 @@ #include "core/or/circuitbuild.h" #include "feature/client/transports.h" #include "feature/relay/router.h" +/* 31851: split the server transport code out of the client module */ +#include "feature/relay/transport_config.h" #include "app/config/statefile.h" #include "core/or/connection_or.h" #include "feature/relay/ext_orport.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" +#include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" +#include "lib/process/process.h" #include "lib/process/env.h" -#include "lib/process/subprocess.h" -static process_environment_t * +static smartlist_t * create_managed_proxy_environment(const managed_proxy_t *mp); static inline int proxy_configuration_finished(const managed_proxy_t *mp); @@ -127,6 +131,8 @@ static void parse_method_error(const char *line, int is_server_method); #define PROTO_SMETHODS_DONE "SMETHODS DONE" #define PROTO_PROXY_DONE "PROXY DONE" #define PROTO_PROXY_ERROR "PROXY-ERROR" +#define PROTO_LOG "LOG" +#define PROTO_STATUS "STATUS" /** The first and only supported - at the moment - configuration protocol version. */ @@ -490,8 +496,8 @@ proxy_prepare_for_restart(managed_proxy_t *mp) tor_assert(mp->conf_state == PT_PROTO_COMPLETED); /* destroy the process handle and terminate the process. */ - tor_process_handle_destroy(mp->process_handle, 1); - mp->process_handle = NULL; + process_set_data(mp->process, NULL); + process_terminate(mp->process); /* destroy all its registered transports, since we will no longer use them. */ @@ -520,34 +526,35 @@ proxy_prepare_for_restart(managed_proxy_t *mp) static int launch_managed_proxy(managed_proxy_t *mp) { - int retval; - - process_environment_t *env = create_managed_proxy_environment(mp); - -#ifdef _WIN32 - /* Passing NULL as lpApplicationName makes Windows search for the .exe */ - retval = tor_spawn_background(NULL, - (const char **)mp->argv, - env, - &mp->process_handle); -#else /* !(defined(_WIN32)) */ - retval = tor_spawn_background(mp->argv[0], - (const char **)mp->argv, - env, - &mp->process_handle); -#endif /* defined(_WIN32) */ - - process_environment_free(env); - - if (retval == PROCESS_STATUS_ERROR) { - log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.", + tor_assert(mp); + + smartlist_t *env = create_managed_proxy_environment(mp); + + /* Configure our process. */ + process_set_data(mp->process, mp); + process_set_stdout_read_callback(mp->process, managed_proxy_stdout_callback); + process_set_stderr_read_callback(mp->process, managed_proxy_stderr_callback); + process_set_exit_callback(mp->process, managed_proxy_exit_callback); + process_set_protocol(mp->process, PROCESS_PROTOCOL_LINE); + process_reset_environment(mp->process, env); + + /* Cleanup our env. */ + SMARTLIST_FOREACH(env, char *, x, tor_free(x)); + smartlist_free(env); + + /* Skip the argv[0] as we get that from process_new(argv[0]). */ + for (int i = 1; mp->argv[i] != NULL; ++i) + process_append_argument(mp->process, mp->argv[i]); + + if (process_exec(mp->process) != PROCESS_STATUS_RUNNING) { + log_warn(LD_CONFIG, "Managed proxy at '%s' failed at launch.", mp->argv[0]); return -1; } - log_info(LD_CONFIG, "Managed proxy at '%s' has spawned with PID '%d'.", - mp->argv[0], tor_process_get_pid(mp->process_handle)); - + log_info(LD_CONFIG, + "Managed proxy at '%s' has spawned with PID '%" PRIu64 "'.", + mp->argv[0], process_get_pid(mp->process)); mp->conf_state = PT_PROTO_LAUNCHED; return 0; @@ -615,10 +622,6 @@ pt_configure_remaining_proxies(void) STATIC int configure_proxy(managed_proxy_t *mp) { - int configuration_finished = 0; - smartlist_t *proxy_output = NULL; - enum stream_status stream_status = 0; - /* if we haven't launched the proxy yet, do it now */ if (mp->conf_state == PT_PROTO_INFANT) { if (launch_managed_proxy(mp) < 0) { /* launch fail */ @@ -629,45 +632,8 @@ configure_proxy(managed_proxy_t *mp) } tor_assert(mp->conf_state != PT_PROTO_INFANT); - tor_assert(mp->process_handle); - - proxy_output = - tor_get_lines_from_handle(tor_process_get_stdout_pipe(mp->process_handle), - &stream_status); - if (!proxy_output) { /* failed to get input from proxy */ - if (stream_status != IO_STREAM_EAGAIN) { /* bad stream status! */ - mp->conf_state = PT_PROTO_BROKEN; - log_warn(LD_GENERAL, "The communication stream of managed proxy '%s' " - "is '%s'. Most probably the managed proxy stopped running. " - "This might be a bug of the managed proxy, a bug of Tor, or " - "a misconfiguration. Please enable logging on your managed " - "proxy and check the logs for errors.", - mp->argv[0], stream_status_to_string(stream_status)); - } - - goto done; - } - - /* Handle lines. */ - SMARTLIST_FOREACH_BEGIN(proxy_output, const char *, line) { - handle_proxy_line(line, mp); - if (proxy_configuration_finished(mp)) - goto done; - } SMARTLIST_FOREACH_END(line); - - done: - /* if the proxy finished configuring, exit the loop. */ - if (proxy_configuration_finished(mp)) { - handle_finished_proxy(mp); - configuration_finished = 1; - } - - if (proxy_output) { - SMARTLIST_FOREACH(proxy_output, char *, cp, tor_free(cp)); - smartlist_free(proxy_output); - } - - return configuration_finished; + tor_assert(mp->process); + return mp->conf_state == PT_PROTO_COMPLETED; } /** Register server managed proxy <b>mp</b> transports to state */ @@ -748,8 +714,14 @@ managed_proxy_destroy(managed_proxy_t *mp, /* free the outgoing proxy URI */ tor_free(mp->proxy_uri); - tor_process_handle_destroy(mp->process_handle, also_terminate_process); - mp->process_handle = NULL; + /* do we want to terminate our process if it's still running? */ + if (also_terminate_process && mp->process) { + /* Note that we do not call process_free(mp->process) here because we let + * the exit handler in managed_proxy_exit_callback() return `true` which + * makes the process subsystem deallocate the process_t. */ + process_set_data(mp->process, NULL); + process_terminate(mp->process); + } tor_free(mp); } @@ -763,6 +735,9 @@ get_pt_proxy_uri(void) const or_options_t *options = get_options(); char *uri = NULL; + /* XXX: Currently TCPProxy is not supported in TOR_PT_PROXY because + * there isn't a standard URI scheme for some proxy protocols, such as + * haproxy. */ if (options->Socks4Proxy || options->Socks5Proxy || options->HTTPSProxy) { char addr[TOR_ADDR_BUF_LEN+1]; @@ -945,21 +920,16 @@ handle_proxy_line(const char *line, managed_proxy_t *mp) parse_proxy_error(line); goto err; - } else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) { - /* managed proxy launch failed: parse error message to learn why. */ - int retval, child_state, saved_errno; - retval = tor_sscanf(line, SPAWN_ERROR_MESSAGE "%x/%x", - &child_state, &saved_errno); - if (retval == 2) { - log_warn(LD_GENERAL, - "Could not launch managed proxy executable at '%s' ('%s').", - mp->argv[0], strerror(saved_errno)); - } else { /* failed to parse error message */ - log_warn(LD_GENERAL,"Could not launch managed proxy executable at '%s'.", - mp->argv[0]); - } - mp->conf_state = PT_PROTO_FAILED_LAUNCH; + /* We check for the additional " " after the PROTO_LOG * PROTO_STATUS + * string to make sure we can later extend this big if/else-if table with + * something that begins with "LOG" without having to get the order right. + * */ + } else if (!strcmpstart(line, PROTO_LOG " ")) { + parse_log_line(line, mp); + return; + } else if (!strcmpstart(line, PROTO_STATUS " ")) { + parse_status_line(line, mp); return; } @@ -1182,6 +1152,121 @@ parse_proxy_error(const char *line) line+strlen(PROTO_PROXY_ERROR)+1); } +/** Parses a LOG <b>line</b> and emit log events accordingly. */ +STATIC void +parse_log_line(const char *line, managed_proxy_t *mp) +{ + tor_assert(line); + tor_assert(mp); + + config_line_t *values = NULL; + char *log_message = NULL; + + if (strlen(line) < (strlen(PROTO_LOG) + 1)) { + log_warn(LD_PT, "Managed proxy sent us a %s line " + "with missing argument.", PROTO_LOG); + goto done; + } + + const char *data = line + strlen(PROTO_LOG) + 1; + values = kvline_parse(data, KV_QUOTED); + + if (! values) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote an invalid LOG message: %s", + mp->argv[0], data); + goto done; + } + + const config_line_t *severity = config_line_find(values, "SEVERITY"); + const config_line_t *message = config_line_find(values, "MESSAGE"); + + /* Check if we got a message. */ + if (! message) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line without " + "MESSAGE: %s", mp->argv[0], escaped(data)); + goto done; + } + + /* Check if severity is there and whether it's valid. */ + if (! severity) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line without " + "SEVERITY: %s", mp->argv[0], escaped(data)); + goto done; + } + + int log_severity = managed_proxy_severity_parse(severity->value); + + if (log_severity == -1) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line with an " + "invalid severity level: %s", + mp->argv[0], severity->value); + goto done; + } + + tor_log(log_severity, LD_PT, "Managed proxy \"%s\": %s", + mp->argv[0], message->value); + + /* Prepend the PT name. */ + config_line_prepend(&values, "PT", mp->argv[0]); + log_message = kvline_encode(values, KV_QUOTED); + + /* Emit control port event. */ + control_event_pt_log(log_message); + + done: + config_free_lines(values); + tor_free(log_message); +} + +/** Parses a STATUS <b>line</b> and emit control events accordingly. */ +STATIC void +parse_status_line(const char *line, managed_proxy_t *mp) +{ + tor_assert(line); + tor_assert(mp); + + config_line_t *values = NULL; + char *status_message = NULL; + + if (strlen(line) < (strlen(PROTO_STATUS) + 1)) { + log_warn(LD_PT, "Managed proxy sent us a %s line " + "with missing argument.", PROTO_STATUS); + goto done; + } + + const char *data = line + strlen(PROTO_STATUS) + 1; + + values = kvline_parse(data, KV_QUOTED); + + if (! values) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote an invalid " + "STATUS message: %s", mp->argv[0], escaped(data)); + goto done; + } + + /* We check if we received the TRANSPORT parameter, which is the only + * *required* value. */ + const config_line_t *type = config_line_find(values, "TRANSPORT"); + + if (! type) { + log_warn(LD_PT, "Managed proxy \"%s\" wrote a STATUS line without " + "TRANSPORT: %s", mp->argv[0], escaped(data)); + goto done; + } + + /* Prepend the PT name. */ + config_line_prepend(&values, "PT", mp->argv[0]); + status_message = kvline_encode(values, KV_QUOTED); + + /* We have checked that TRANSPORT is there, we can now emit the STATUS event + * via the control port. */ + control_event_pt_status(status_message); + + done: + config_free_lines(values); + tor_free(status_message); +} + /** Return a newly allocated string that tor should place in * TOR_PT_SERVER_TRANSPORT_OPTIONS while configuring the server * manged proxy in <b>mp</b>. Return NULL if no such options are found. */ @@ -1199,7 +1284,7 @@ get_transport_options_for_server_proxy(const managed_proxy_t *mp) string. */ SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, transport) { smartlist_t *options_tmp_sl = NULL; - options_tmp_sl = get_options_for_server_transport(transport); + options_tmp_sl = pt_get_options_for_server_transport(transport); if (!options_tmp_sl) continue; @@ -1257,7 +1342,7 @@ get_bindaddr_for_server_proxy(const managed_proxy_t *mp) /** Return a newly allocated process_environment_t * for <b>mp</b>'s * process. */ -static process_environment_t * +static smartlist_t * create_managed_proxy_environment(const managed_proxy_t *mp) { const or_options_t *options = get_options(); @@ -1272,8 +1357,6 @@ create_managed_proxy_environment(const managed_proxy_t *mp) /* The final environment to be passed to mp. */ smartlist_t *merged_env_vars = get_current_process_environment_variables(); - process_environment_t *env; - { char *state_tmp = get_datadir_fname("pt_state/"); /* XXX temp */ smartlist_add_asprintf(envs, "TOR_PT_STATE_LOCATION=%s", state_tmp); @@ -1346,11 +1429,6 @@ create_managed_proxy_environment(const managed_proxy_t *mp) } else { smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT="); } - - /* All new versions of tor will keep stdin open, so PTs can use it - * as a reliable termination detection mechanism. - */ - smartlist_add_asprintf(envs, "TOR_PT_EXIT_ON_STDIN_CLOSE=1"); } else { /* If ClientTransportPlugin has a HTTPS/SOCKS proxy configured, set the * TOR_PT_PROXY line. @@ -1361,19 +1439,19 @@ create_managed_proxy_environment(const managed_proxy_t *mp) } } + /* All new versions of tor will keep stdin open, so PTs can use it + * as a reliable termination detection mechanism. + */ + smartlist_add_asprintf(envs, "TOR_PT_EXIT_ON_STDIN_CLOSE=1"); + SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) { set_environment_variable_in_smartlist(merged_env_vars, env_var, tor_free_, 1); } SMARTLIST_FOREACH_END(env_var); - env = process_environment_make(merged_env_vars); - smartlist_free(envs); - SMARTLIST_FOREACH(merged_env_vars, void *, x, tor_free(x)); - smartlist_free(merged_env_vars); - - return env; + return merged_env_vars; } /** Create and return a new managed proxy for <b>transport</b> using @@ -1392,6 +1470,7 @@ managed_proxy_create(const smartlist_t *with_transport_list, mp->argv = proxy_argv; mp->transports = smartlist_new(); mp->proxy_uri = get_pt_proxy_uri(); + mp->process = process_new(proxy_argv[0]); mp->transports_to_launch = smartlist_new(); SMARTLIST_FOREACH(with_transport_list, const char *, transport, @@ -1736,3 +1815,95 @@ tor_escape_str_for_pt_args(const char *string, const char *chars_to_escape) return new_string; } + +/** Callback function that is called when our PT process have data on its + * stdout. Our process can be found in <b>process</b>, the data can be found in + * <b>line</b> and the length of our line is given in <b>size</b>. */ +STATIC void +managed_proxy_stdout_callback(process_t *process, + const char *line, + size_t size) +{ + tor_assert(process); + tor_assert(line); + + (void)size; + + managed_proxy_t *mp = process_get_data(process); + + if (mp == NULL) + return; + + handle_proxy_line(line, mp); + + if (proxy_configuration_finished(mp)) + handle_finished_proxy(mp); +} + +/** Callback function that is called when our PT process have data on its + * stderr. Our process can be found in <b>process</b>, the data can be found in + * <b>line</b> and the length of our line is given in <b>size</b>. */ +STATIC void +managed_proxy_stderr_callback(process_t *process, + const char *line, + size_t size) +{ + tor_assert(process); + tor_assert(line); + + (void)size; + + managed_proxy_t *mp = process_get_data(process); + + if (BUG(mp == NULL)) + return; + + log_info(LD_PT, + "Managed proxy at '%s' reported via standard error: %s", + mp->argv[0], line); +} + +/** Callback function that is called when our PT process terminates. The + * process exit code can be found in <b>exit_code</b> and our process can be + * found in <b>process</b>. Returns true iff we want the process subsystem to + * free our process_t handle for us. */ +STATIC bool +managed_proxy_exit_callback(process_t *process, process_exit_code_t exit_code) +{ + tor_assert(process); + + log_warn(LD_PT, + "Pluggable Transport process terminated with status code %" PRIu64, + exit_code); + + /* Returning true here means that the process subsystem will take care of + * calling process_free() on our process_t. */ + return true; +} + +/** Returns a valid integer log severity level from <b>severity</b> that + * is compatible with Tor's logging functions. Returns <b>-1</b> on + * error. */ +STATIC int +managed_proxy_severity_parse(const char *severity) +{ + tor_assert(severity); + + /* Slightly different than log.c's parse_log_level :-( */ + if (! strcmp(severity, "debug")) + return LOG_DEBUG; + + if (! strcmp(severity, "info")) + return LOG_INFO; + + if (! strcmp(severity, "notice")) + return LOG_NOTICE; + + if (! strcmp(severity, "warning")) + return LOG_WARN; + + if (! strcmp(severity, "error")) + return LOG_ERR; + + return -1; +} diff --git a/src/feature/client/transports.h b/src/feature/client/transports.h index e2fa45828f..1ed942c175 100644 --- a/src/feature/client/transports.h +++ b/src/feature/client/transports.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -11,6 +11,8 @@ #ifndef TOR_TRANSPORTS_H #define TOR_TRANSPORTS_H +#include "lib/process/process.h" + /** Represents a pluggable transport used by a bridge. */ typedef struct transport_t { /** SOCKS version: One of PROXY_SOCKS4, PROXY_SOCKS5. */ @@ -81,7 +83,7 @@ enum pt_proto_state { PT_PROTO_FAILED_LAUNCH /* failed while launching */ }; -struct process_handle_t; +struct process_t; /** Structure containing information of a managed proxy. */ typedef struct { @@ -94,10 +96,8 @@ typedef struct { int is_server; /* is it a server proxy? */ - /* A pointer to the process handle of this managed proxy. */ - struct process_handle_t *process_handle; - - int pid; /* The Process ID this managed proxy is using. */ + /* A pointer to the process of this managed proxy. */ + struct process_t *process; /** Boolean: We are re-parsing our config, and we are going to * remove this managed proxy if we don't find it any transport @@ -128,6 +128,8 @@ STATIC int parse_version(const char *line, managed_proxy_t *mp); STATIC void parse_env_error(const char *line); STATIC void parse_proxy_error(const char *line); STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp); +STATIC void parse_log_line(const char *line, managed_proxy_t *mp); +STATIC void parse_status_line(const char *line, managed_proxy_t *mp); STATIC char *get_transport_options_for_server_proxy(const managed_proxy_t *mp); STATIC void managed_proxy_destroy(managed_proxy_t *mp, @@ -142,6 +144,12 @@ STATIC char* get_pt_proxy_uri(void); STATIC void free_execve_args(char **arg); +STATIC void managed_proxy_stdout_callback(process_t *, const char *, size_t); +STATIC void managed_proxy_stderr_callback(process_t *, const char *, size_t); +STATIC bool managed_proxy_exit_callback(process_t *, process_exit_code_t); + +STATIC int managed_proxy_severity_parse(const char *); + #endif /* defined(PT_PRIVATE) */ #endif /* !defined(TOR_TRANSPORTS_H) */ diff --git a/src/feature/control/.may_include b/src/feature/control/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/control/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/control/btrack.c b/src/feature/control/btrack.c new file mode 100644 index 0000000000..874150ee13 --- /dev/null +++ b/src/feature/control/btrack.c @@ -0,0 +1,64 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack.c + * \brief Bootstrap trackers + * + * Initializes and shuts down the specific bootstrap trackers. These + * trackers help the reporting of bootstrap progress by maintaining + * state information about various subsystems within tor. When the + * correct state changes happen, these trackers emit controller + * events. + * + * These trackers avoid referring directly to the internals of state + * objects of other subsystems. + * + * btrack_circuit.c contains the tracker for origin circuits. + * + * btrack_orconn.c contains the tracker for OR connections. + * + * Eventually there will be a tracker for directory downloads as well. + **/ + +#include "feature/control/btrack_circuit.h" +#include "feature/control/btrack_orconn.h" +#include "feature/control/btrack_sys.h" +#include "lib/pubsub/pubsub.h" +#include "lib/subsys/subsys.h" + +static int +btrack_init(void) +{ + if (btrack_orconn_init()) + return -1; + + return 0; +} + +static void +btrack_fini(void) +{ + btrack_orconn_fini(); + btrack_circ_fini(); +} + +static int +btrack_add_pubsub(pubsub_connector_t *connector) +{ + if (btrack_orconn_add_pubsub(connector)) + return -1; + if (btrack_circ_add_pubsub(connector)) + return -1; + + return 0; +} + +const subsys_fns_t sys_btrack = { + .name = "btrack", + .supported = true, + .level = -30, + .initialize = btrack_init, + .shutdown = btrack_fini, + .add_pubsub = btrack_add_pubsub, +}; diff --git a/src/feature/control/btrack_circuit.c b/src/feature/control/btrack_circuit.c new file mode 100644 index 0000000000..be51b51046 --- /dev/null +++ b/src/feature/control/btrack_circuit.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_circuit.c + * \brief Bootstrap tracker for origin circuits + * + * Track state changes of origin circuits, as published by the circuit + * subsystem. + **/ + +#include "core/or/or.h" + +#include "core/or/ocirc_event.h" + +#include "feature/control/btrack_circuit.h" +#include "feature/control/control.h" +#include "lib/log/log.h" + +/** Pair of a best origin circuit GID with its state or status */ +typedef struct btc_best_t { + uint32_t gid; + int val; +} btc_best_t; + +/** GID and state of the best origin circuit we've seen so far */ +static btc_best_t best_any_state = { 0, -1 }; +/** GID and state of the best application circuit we've seen so far */ +static btc_best_t best_ap_state = { 0, -1 }; +/** GID and status of the best origin circuit we've seen so far */ +static btc_best_t best_any_evtype = { 0, -1 }; +/** GID and status of the best application circuit we've seen so far */ +static btc_best_t best_ap_evtype = { 0, -1 }; + +/** Reset cached "best" values */ +static void +btc_reset_bests(void) +{ + best_any_state.gid = best_ap_state.gid = 0; + best_any_state.val = best_ap_state.val = -1; + best_any_evtype.gid = best_ap_state.gid = 0; + best_any_evtype.val = best_ap_evtype.val = -1; +} + +/** True if @a state is a "better" origin circuit state than @a best->val */ +static bool +btc_state_better(int state, const btc_best_t *best) +{ + return state > best->val; +} + +/** + * Definine an ordering on circuit status events + * + * The CIRC_EVENT_ constants aren't sorted in a useful order, so this + * array helps to decode them. This approach depends on the statuses + * being nonnegative and dense. + **/ +static int circ_event_order[] = { + [CIRC_EVENT_FAILED] = -1, + [CIRC_EVENT_CLOSED] = -1, + [CIRC_EVENT_LAUNCHED] = 1, + [CIRC_EVENT_EXTENDED] = 2, + [CIRC_EVENT_BUILT] = 3, +}; +#define N_CIRC_EVENT_ORDER \ + (sizeof(circ_event_order) / sizeof(circ_event_order[0])) + +/** True if @a state is a "better" origin circuit event status than @a + best->val */ +static bool +btc_evtype_better(int state, const btc_best_t *best) +{ + if (state < 0) + return false; + if (best->val < 0) + return true; + + tor_assert(state >= 0 && (unsigned)state < N_CIRC_EVENT_ORDER); + tor_assert(best->val >= 0 && (unsigned)best->val < N_CIRC_EVENT_ORDER); + return circ_event_order[state] > circ_event_order[best->val]; +} + +static bool +btc_update_state(const ocirc_state_msg_t *msg, btc_best_t *best, + const char *type) +{ + if (btc_state_better(msg->state, best)) { + log_info(LD_BTRACK, "CIRC BEST_%s state %d->%d gid=%"PRIu32, type, + best->val, msg->state, msg->gid); + best->gid = msg->gid; + best->val = msg->state; + return true; + } + return false; +} + +static bool +btc_update_evtype(const ocirc_cevent_msg_t *msg, btc_best_t *best, + const char *type) +{ + if (btc_evtype_better(msg->evtype, best)) { + log_info(LD_BTRACK, "CIRC BEST_%s evtype %d->%d gid=%"PRIu32, type, + best->val, msg->evtype, msg->gid); + best->gid = msg->gid; + best->val = msg->evtype; + return true; + } + return false; +} + +DECLARE_SUBSCRIBE(ocirc_state, btc_state_rcvr); +DECLARE_SUBSCRIBE(ocirc_cevent, btc_cevent_rcvr); +DECLARE_SUBSCRIBE(ocirc_chan, btc_chan_rcvr); + +static void +btc_state_rcvr(const msg_t *msg, const ocirc_state_msg_t *arg) +{ + (void)msg; + log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" state=%d onehop=%d", + arg->gid, arg->state, arg->onehop); + + btc_update_state(arg, &best_any_state, "ANY"); + if (arg->onehop) + return; + btc_update_state(arg, &best_ap_state, "AP"); +} + +static void +btc_cevent_rcvr(const msg_t *msg, const ocirc_cevent_msg_t *arg) +{ + (void)msg; + log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" evtype=%d reason=%d onehop=%d", + arg->gid, arg->evtype, arg->reason, arg->onehop); + + btc_update_evtype(arg, &best_any_evtype, "ANY"); + if (arg->onehop) + return; + btc_update_evtype(arg, &best_ap_evtype, "AP"); +} + +static void +btc_chan_rcvr(const msg_t *msg, const ocirc_chan_msg_t *arg) +{ + (void)msg; + log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" chan=%"PRIu64" onehop=%d", + arg->gid, arg->chan, arg->onehop); +} + +int +btrack_circ_add_pubsub(pubsub_connector_t *connector) +{ + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_chan)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_cevent)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_state)) + return -1; + return 0; +} + +void +btrack_circ_fini(void) +{ + btc_reset_bests(); +} diff --git a/src/feature/control/btrack_circuit.h b/src/feature/control/btrack_circuit.h new file mode 100644 index 0000000000..75699450c3 --- /dev/null +++ b/src/feature/control/btrack_circuit.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_circuit.h + * \brief Header file for btrack_circuit.c + **/ + +#ifndef TOR_BTRACK_CIRCUIT_H +#define TOR_BTRACK_CIRCUIT_H + +#include "lib/pubsub/pubsub.h" + +int btrack_circ_init(void); +void btrack_circ_fini(void); +int btrack_circ_add_pubsub(pubsub_connector_t *); + +#endif /* !defined(TOR_BTRACK_CIRCUIT_H) */ diff --git a/src/feature/control/btrack_orconn.c b/src/feature/control/btrack_orconn.c new file mode 100644 index 0000000000..104c8af230 --- /dev/null +++ b/src/feature/control/btrack_orconn.c @@ -0,0 +1,206 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn.c + * \brief Bootstrap tracker for OR connections + * + * Track state changes of OR connections, as published by the + * connection subsystem. Also track circuit launch events, because + * they're one of the few ways to discover the association between a + * channel (and OR connection) and a circuit. + * + * We track all OR connections that we receive events for, whether or + * not they're carrying origin circuits. (An OR connection might + * carry origin circuits only after we first find out about that + * connection.) + * + * All origin ORCONN events update the "any" state variables, while + * only application ORCONN events update the "ap" state variables (and + * also update the "any") variables. + * + * We do this because we want to report the first increments of + * connection progress as the earliest bootstrap phases. This results + * in a better user experience because failures here translate into + * zero or very small amounts of displayed progress, instead of + * progress stuck near completion. The first connection to a relay + * might be a one-hop circuit for directory lookups, or it might be a + * connection for an application circuit because we already have + * enough directory info to build an application circuit. + * + * We call functions in btrack_orconn_cevent.c to generate the actual + * controller events, because some of the state decoding we need to do + * is complicated. + **/ + +#include <stdbool.h> + +#include "core/or/or.h" + +#define BTRACK_ORCONN_PRIVATE + +#include "core/or/ocirc_event.h" +#include "core/or/orconn_event.h" +#include "feature/control/btrack_orconn.h" +#include "feature/control/btrack_orconn_cevent.h" +#include "feature/control/btrack_orconn_maps.h" +#include "lib/log/log.h" +#include "lib/pubsub/pubsub.h" + +DECLARE_SUBSCRIBE(orconn_state, bto_state_rcvr); +DECLARE_SUBSCRIBE(orconn_status, bto_status_rcvr); +DECLARE_SUBSCRIBE(ocirc_chan, bto_chan_rcvr); + +/** Pair of a best ORCONN GID and with its state */ +typedef struct bto_best_t { + uint64_t gid; + int state; +} bto_best_t; + +/** GID and state of the best ORCONN we've seen so far */ +static bto_best_t best_any = { 0, -1 }; +/** GID and state of the best application circuit ORCONN we've seen so far */ +static bto_best_t best_ap = { 0, -1 }; + +/** + * Update a cached state of a best ORCONN progress we've seen so far. + * + * Return true if the new state is better than the old. + **/ +static bool +bto_update_best(const bt_orconn_t *bto, bto_best_t *best, const char *type) +{ + if (bto->state < best->state) + return false; + /* Update even if we won't change best->state, because it's more + * recent information that a particular connection transitioned to + * that state. */ + best->gid = bto->gid; + if (bto->state > best->state) { + log_info(LD_BTRACK, "ORCONN BEST_%s state %d->%d gid=%"PRIu64, type, + best->state, bto->state, bto->gid); + best->state = bto->state; + return true; + } + return false; +} + +/** + * Update cached states of best ORCONN progress we've seen + * + * Only update the application ORCONN state if we know it's carrying + * an application circuit. + **/ +static void +bto_update_bests(const bt_orconn_t *bto) +{ + tor_assert(bto->is_orig); + + if (bto_update_best(bto, &best_any, "ANY")) + bto_cevent_anyconn(bto); + if (!bto->is_onehop && bto_update_best(bto, &best_ap, "AP")) + bto_cevent_apconn(bto); +} + +/** Reset cached "best" values */ +static void +bto_reset_bests(void) +{ + best_any.gid = best_ap.gid = 0; + best_any.state = best_ap.state = -1; +} + +/** + * Update cached states of ORCONNs from the incoming message. This + * message comes from code in connection_or.c. + **/ +static void +bto_state_rcvr(const msg_t *msg, const orconn_state_msg_t *arg) +{ + bt_orconn_t *bto; + + (void)msg; + bto = bto_find_or_new(arg->gid, arg->chan); + log_debug(LD_BTRACK, "ORCONN gid=%"PRIu64" chan=%"PRIu64 + " proxy_type=%d state=%d", + arg->gid, arg->chan, arg->proxy_type, arg->state); + bto->proxy_type = arg->proxy_type; + bto->state = arg->state; + if (bto->is_orig) + bto_update_bests(bto); +} + +/** + * Delete a cached ORCONN state if we get an incoming message saying + * the ORCONN is failed or closed. This message comes from code in + * control.c. + **/ +static void +bto_status_rcvr(const msg_t *msg, const orconn_status_msg_t *arg) +{ + (void)msg; + switch (arg->status) { + case OR_CONN_EVENT_FAILED: + case OR_CONN_EVENT_CLOSED: + log_info(LD_BTRACK, "ORCONN DELETE gid=%"PRIu64" status=%d reason=%d", + arg->gid, arg->status, arg->reason); + return bto_delete(arg->gid); + default: + break; + } +} + +/** + * Create or update a cached ORCONN state for a newly launched + * connection, including whether it's launched by an origin circuit + * and whether it's a one-hop circuit. + **/ +static void +bto_chan_rcvr(const msg_t *msg, const ocirc_chan_msg_t *arg) +{ + bt_orconn_t *bto; + + (void)msg; + bto = bto_find_or_new(0, arg->chan); + if (!bto->is_orig || (bto->is_onehop && !arg->onehop)) { + log_debug(LD_BTRACK, "ORCONN LAUNCH chan=%"PRIu64" onehop=%d", + arg->chan, arg->onehop); + } + bto->is_orig = true; + if (!arg->onehop) + bto->is_onehop = false; + bto_update_bests(bto); +} + +/** + * Initialize the hash maps and subscribe to ORCONN and origin + * circuit events. + **/ +int +btrack_orconn_init(void) +{ + bto_init_maps(); + + return 0; +} + +int +btrack_orconn_add_pubsub(pubsub_connector_t *connector) +{ + if (DISPATCH_ADD_SUB(connector, orconn, orconn_state)) + return -1; + if (DISPATCH_ADD_SUB(connector, orconn, orconn_status)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_chan)) + return -1; + return 0; +} + +/** Clear the hash maps and reset the "best" states */ +void +btrack_orconn_fini(void) +{ + bto_clear_maps(); + bto_reset_bests(); + bto_cevent_reset(); +} diff --git a/src/feature/control/btrack_orconn.h b/src/feature/control/btrack_orconn.h new file mode 100644 index 0000000000..8b3d8be37d --- /dev/null +++ b/src/feature/control/btrack_orconn.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn.h + * \brief Header file for btrack_orconn.c + **/ + +#ifndef TOR_BTRACK_ORCONN_H +#define TOR_BTRACK_ORCONN_H + +#include "lib/pubsub/pubsub.h" + +#ifdef BTRACK_ORCONN_PRIVATE + +#include "ht.h" + +/** + * Structure for tracking OR connection states + * + * This gets linked into two hash maps: one with connection IDs, and + * another with channel IDs. + **/ +typedef struct bt_orconn_t { + HT_ENTRY(bt_orconn_t) node; /**< Hash map entry indexed by gid */ + HT_ENTRY(bt_orconn_t) chan_node; /**< Hash map entry indexed by channel ID */ + uint64_t gid; /**< Global ID of this ORCONN */ + uint64_t chan; /**< Channel ID, if known */ + int proxy_type; /**< Proxy type */ + uint8_t state; /**< State of this ORCONN */ + bool is_orig; /**< Does this carry an origin circuit? */ + bool is_onehop; /**< Is this for a one-hop circuit? */ +} bt_orconn_t; + +#endif /* defined(BTRACK_ORCONN_PRIVATE) */ + +int btrack_orconn_init(void); +int btrack_orconn_add_pubsub(pubsub_connector_t *); +void btrack_orconn_fini(void); + +#endif /* !defined(TOR_BTRACK_ORCONN_H) */ diff --git a/src/feature/control/btrack_orconn_cevent.c b/src/feature/control/btrack_orconn_cevent.c new file mode 100644 index 0000000000..d11be59280 --- /dev/null +++ b/src/feature/control/btrack_orconn_cevent.c @@ -0,0 +1,161 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn_cevent.c + * \brief Emit bootstrap status events for OR connections + * + * We do some decoding of the raw OR_CONN_STATE_* values. For + * example, OR_CONN_STATE_CONNECTING means the first TCP connect() + * completing, regardless of whether it's directly to a relay instead + * of a proxy or a PT. + **/ + +#include <stdbool.h> + +#include "core/or/or.h" + +#define BTRACK_ORCONN_PRIVATE + +#include "core/or/orconn_event.h" +#include "feature/control/btrack_orconn.h" +#include "feature/control/btrack_orconn_cevent.h" +#include "feature/control/control_events.h" + +/** + * Have we completed our first OR connection? + * + * Block display of application circuit progress until we do, to avoid + * some misleading behavior of jumping to high progress. + **/ +static bool bto_first_orconn = false; + +/** Is the ORCONN using a pluggable transport? */ +static bool +using_pt(const bt_orconn_t *bto) +{ + return bto->proxy_type == PROXY_PLUGGABLE; +} + +/** Is the ORCONN using a non-PT proxy? */ +static bool +using_proxy(const bt_orconn_t *bto) +{ + switch (bto->proxy_type) { + case PROXY_CONNECT: + case PROXY_SOCKS4: + case PROXY_SOCKS5: + case PROXY_HAPROXY: + return true; + default: + return false; + } +} + +/** + * Emit control events when we have updated our idea of the best state + * that any OR connection has reached. + * + * Do some decoding of the ORCONN states depending on whether a PT or + * a proxy is in use. + **/ +void +bto_cevent_anyconn(const bt_orconn_t *bto) +{ + switch (bto->state) { + case OR_CONN_STATE_CONNECTING: + /* Exactly what kind of thing we're connecting to isn't + * information we directly get from the states in connection_or.c, + * so decode it here. */ + if (using_pt(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_PT, 0); + else if (using_proxy(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_PROXY, 0); + else + control_event_bootstrap(BOOTSTRAP_STATUS_CONN, 0); + break; + case OR_CONN_STATE_PROXY_HANDSHAKING: + /* Similarly, starting a proxy handshake means the TCP connect() + * succeeded to the proxy. Let's be specific about what kind of + * proxy. */ + if (using_pt(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DONE_PT, 0); + else if (using_proxy(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DONE_PROXY, 0); + break; + case OR_CONN_STATE_TLS_HANDSHAKING: + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DONE, 0); + break; + case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING: + case OR_CONN_STATE_OR_HANDSHAKING_V2: + case OR_CONN_STATE_OR_HANDSHAKING_V3: + control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0); + break; + case OR_CONN_STATE_OPEN: + control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE_DONE, 0); + /* Unblock directory progress display */ + control_event_boot_first_orconn(); + /* Unblock apconn progress display */ + bto_first_orconn = true; + break; + default: + break; + } +} + +/** + * Emit control events when we have updated our idea of the best state + * that any application circuit OR connection has reached. + * + * Do some decoding of the ORCONN states depending on whether a PT or + * a proxy is in use. + **/ +void +bto_cevent_apconn(const bt_orconn_t *bto) +{ + if (!bto_first_orconn) + return; + + switch (bto->state) { + case OR_CONN_STATE_CONNECTING: + /* Exactly what kind of thing we're connecting to isn't + * information we directly get from the states in connection_or.c, + * so decode it here. */ + if (using_pt(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN_PT, 0); + else if (using_proxy(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN_PROXY, 0); + else + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN, 0); + break; + case OR_CONN_STATE_PROXY_HANDSHAKING: + /* Similarly, starting a proxy handshake means the TCP connect() + * succeeded to the proxy. Let's be specific about what kind of + * proxy. */ + if (using_pt(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN_DONE_PT, 0); + else if (using_proxy(bto)) + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN_DONE_PROXY, 0); + break; + case OR_CONN_STATE_TLS_HANDSHAKING: + control_event_bootstrap(BOOTSTRAP_STATUS_AP_CONN_DONE, 0); + break; + case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING: + case OR_CONN_STATE_OR_HANDSHAKING_V2: + case OR_CONN_STATE_OR_HANDSHAKING_V3: + control_event_bootstrap(BOOTSTRAP_STATUS_AP_HANDSHAKE, 0); + break; + case OR_CONN_STATE_OPEN: + control_event_bootstrap(BOOTSTRAP_STATUS_AP_HANDSHAKE_DONE, 0); + break; + default: + break; + } +} + +/** Forget that we completed our first OR connection */ +void +bto_cevent_reset(void) +{ + bto_first_orconn = false; +} diff --git a/src/feature/control/btrack_orconn_cevent.h b/src/feature/control/btrack_orconn_cevent.h new file mode 100644 index 0000000000..8b2207721e --- /dev/null +++ b/src/feature/control/btrack_orconn_cevent.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn_cevent.h + * \brief Header file for btrack_orconn_cevent.c + **/ + +#ifndef TOR_BTRACK_ORCONN_CEVENT_H +#define TOR_BTRACK_ORCONN_CEVENT_H + +#include "feature/control/btrack_orconn.h" + +void bto_cevent_anyconn(const bt_orconn_t *); +void bto_cevent_apconn(const bt_orconn_t *); +void bto_cevent_reset(void); + +#endif /* !defined(TOR_BTRACK_ORCONN_CEVENT_H) */ diff --git a/src/feature/control/btrack_orconn_maps.c b/src/feature/control/btrack_orconn_maps.c new file mode 100644 index 0000000000..0ef54237a8 --- /dev/null +++ b/src/feature/control/btrack_orconn_maps.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn_maps.c + * \brief Hash map implementation for btrack_orconn.c + * + * These functions manipulate the hash maps that contain bt_orconn + * objects. + **/ + +#include <stdbool.h> + +#include "core/or/or.h" + +#include "ht.h" +#include "siphash.h" + +#define BTRACK_ORCONN_PRIVATE + +#include "feature/control/btrack_orconn.h" +#include "feature/control/btrack_orconn_maps.h" +#include "lib/log/log.h" + +static inline unsigned int +bto_gid_hash_(bt_orconn_t *elm) +{ + return (unsigned)siphash24g(&elm->gid, sizeof(elm->gid)); +} + +static inline int +bto_gid_eq_(bt_orconn_t *a, bt_orconn_t *b) +{ + return a->gid == b->gid; +} + +static inline unsigned int +bto_chan_hash_(bt_orconn_t *elm) +{ + return (unsigned)siphash24g(&elm->chan, sizeof(elm->chan)); +} + +static inline int +bto_chan_eq_(bt_orconn_t *a, bt_orconn_t *b) +{ + return a->chan == b->chan; +} + +HT_HEAD(bto_gid_ht, bt_orconn_t); +HT_PROTOTYPE(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_) +HT_GENERATE2(bto_gid_ht, bt_orconn_t, node, + bto_gid_hash_, bto_gid_eq_, 0.6, + tor_reallocarray_, tor_free_) +static struct bto_gid_ht *bto_gid_map; + +HT_HEAD(bto_chan_ht, bt_orconn_t); +HT_PROTOTYPE(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_, bto_chan_eq_) +HT_GENERATE2(bto_chan_ht, bt_orconn_t, chan_node, + bto_chan_hash_, bto_chan_eq_, 0.6, + tor_reallocarray_, tor_free_) +static struct bto_chan_ht *bto_chan_map; + +/** Clear the GID hash map, freeing any bt_orconn_t objects that become + * unreferenced */ +static void +bto_gid_clear_map(void) +{ + bt_orconn_t **elt, **next, *c; + + for (elt = HT_START(bto_gid_ht, bto_gid_map); + elt; + elt = next) { + c = *elt; + next = HT_NEXT_RMV(bto_gid_ht, bto_gid_map, elt); + + c->gid = 0; + /* Don't delete if chan ID isn't zero: it's still in the chan hash map */ + if (!c->chan) + tor_free(c); + } + HT_CLEAR(bto_gid_ht, bto_gid_map); + tor_free(bto_gid_map); +} + +/** Clear the chan ID hash map, freeing any bt_orconn_t objects that + * become unreferenced */ +static void +bto_chan_clear_map(void) +{ + bt_orconn_t **elt, **next, *c; + + for (elt = HT_START(bto_chan_ht, bto_chan_map); + elt; + elt = next) { + c = *elt; + next = HT_NEXT_RMV(bto_chan_ht, bto_chan_map, elt); + + c->chan = 0; + /* Don't delete if GID isn't zero, it's still in the GID hash map */ + if (!c->gid) + tor_free(c); + } + HT_CLEAR(bto_chan_ht, bto_chan_map); + tor_free(bto_chan_map); +} + +/** Delete a bt_orconn from the hash maps by GID */ +void +bto_delete(uint64_t gid) +{ + bt_orconn_t key, *bto; + + key.gid = gid; + key.chan = 0; + bto = HT_FIND(bto_gid_ht, bto_gid_map, &key); + if (!bto) { + /* The orconn might be unregistered because it's an EXT_OR_CONN? */ + log_debug(LD_BTRACK, "tried to delete unregistered ORCONN gid=%"PRIu64, + gid); + return; + } + HT_REMOVE(bto_gid_ht, bto_gid_map, &key); + if (bto->chan) { + key.chan = bto->chan; + HT_REMOVE(bto_chan_ht, bto_chan_map, &key); + } + tor_free(bto); +} + +/** + * Helper for bto_find_or_new(). + * + * Update GID and chan ID of an existing bt_orconn object if needed, + * given a search key previously used within bto_find_or_new(). + **/ +static bt_orconn_t * +bto_update(bt_orconn_t *bto, const bt_orconn_t *key) +{ + /* ORCONN GIDs shouldn't change once assigned */ + tor_assert(!bto->gid || !key->gid || bto->gid == key->gid); + if (!bto->gid && key->gid) { + /* Got a gid when we didn't already have one; insert into gid map */ + log_debug(LD_BTRACK, "ORCONN chan=%"PRIu64" newgid=%"PRIu64, key->chan, + key->gid); + bto->gid = key->gid; + HT_INSERT(bto_gid_ht, bto_gid_map, bto); + } + /* association of ORCONN with channel shouldn't change */ + tor_assert(!bto->chan || !key->chan || bto->chan == key->chan); + if (!bto->chan && key->chan) { + /* Got a chan when we didn't already have one; insert into chan map */ + log_debug(LD_BTRACK, "ORCONN gid=%"PRIu64" newchan=%"PRIu64, + bto->gid, key->chan); + bto->chan = key->chan; + HT_INSERT(bto_chan_ht, bto_chan_map, bto); + } + return bto; +} + +/** Helper for bto_find_or_new() */ +static bt_orconn_t * +bto_new(const bt_orconn_t *key) +{ + struct bt_orconn_t *bto = tor_malloc(sizeof(*bto)); + + bto->gid = key->gid; + bto->chan = key->chan; + bto->state = 0; + bto->proxy_type = 0; + bto->is_orig = false; + bto->is_onehop = true; + + if (bto->gid) + HT_INSERT(bto_gid_ht, bto_gid_map, bto); + if (bto->chan) + HT_INSERT(bto_chan_ht, bto_chan_map, bto); + + return bto; +} + +/** + * Insert a new bt_orconn with the given GID and chan ID, or update + * the GID and chan ID if one already exists. + * + * Return the found or allocated bt_orconn. + **/ +bt_orconn_t * +bto_find_or_new(uint64_t gid, uint64_t chan) +{ + bt_orconn_t key, *bto = NULL; + + tor_assert(gid || chan); + key.gid = gid; + key.chan = chan; + if (key.gid) + bto = HT_FIND(bto_gid_ht, bto_gid_map, &key); + if (!bto && key.chan) { + /* Not found by GID; look up by chan ID */ + bto = HT_FIND(bto_chan_ht, bto_chan_map, &key); + } + if (bto) + return bto_update(bto, &key); + else + return bto_new(&key); +} + +/** Initialize the hash maps */ +void +bto_init_maps(void) +{ + bto_gid_map = tor_malloc(sizeof(*bto_gid_map)); + HT_INIT(bto_gid_ht, bto_gid_map); + bto_chan_map = tor_malloc(sizeof(*bto_chan_map)); + HT_INIT(bto_chan_ht, bto_chan_map); +} + +/** Clear the hash maps, freeing all associated storage */ +void +bto_clear_maps(void) +{ + bto_gid_clear_map(); + bto_chan_clear_map(); +} diff --git a/src/feature/control/btrack_orconn_maps.h b/src/feature/control/btrack_orconn_maps.h new file mode 100644 index 0000000000..c83b22b1e8 --- /dev/null +++ b/src/feature/control/btrack_orconn_maps.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_orconn_maps.h + * \brief Header file for btrack_orconn_maps.c + **/ + +#ifndef TOR_BTRACK_ORCONN_MAPS_H +#define TOR_BTRACK_ORCONN_MAPS_H + +void bto_delete(uint64_t); +bt_orconn_t *bto_find_or_new(uint64_t, uint64_t); + +void bto_init_maps(void); +void bto_clear_maps(void); + +#endif /* !defined(TOR_BTRACK_ORCONN_MAPS_H) */ diff --git a/src/feature/control/btrack_sys.h b/src/feature/control/btrack_sys.h new file mode 100644 index 0000000000..5a157b7b54 --- /dev/null +++ b/src/feature/control/btrack_sys.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file btrack_sys.h + * \brief Declare subsystem object for the bootstrap tracker susbystem. + **/ + +#ifndef TOR_BTRACK_SYS_H +#define TOR_BTRACK_SYS_H + +extern const struct subsys_fns_t sys_btrack; + +#endif /* !defined(TOR_BTRACK_SYS_H) */ diff --git a/src/feature/control/control.c b/src/feature/control/control.c index 26ac12d307..ee1026359d 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -1,6 +1,5 @@ - /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -33,83 +32,27 @@ * stack. **/ +#define CONTROL_MODULE_PRIVATE #define CONTROL_PRIVATE #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" #include "app/main/main.h" #include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" -#include "core/or/channel.h" -#include "core/or/channeltls.h" -#include "core/or/circuitbuild.h" -#include "core/or/circuitlist.h" -#include "core/or/circuitstats.h" -#include "core/or/circuituse.h" -#include "core/or/command.h" -#include "core/or/connection_edge.h" #include "core/or/connection_or.h" -#include "core/or/policies.h" -#include "core/or/reasons.h" -#include "core/or/versions.h" #include "core/proto/proto_control0.h" #include "core/proto/proto_http.h" -#include "feature/client/addressmap.h" -#include "feature/client/bridges.h" -#include "feature/client/dnsserv.h" -#include "feature/client/entrynodes.h" #include "feature/control/control.h" -#include "feature/control/fmt_serverstatus.h" -#include "feature/control/getinfo_geoip.h" -#include "feature/dircache/dirserv.h" -#include "feature/dirclient/dirclient.h" -#include "feature/dirclient/dlstatus.h" -#include "feature/dircommon/directory.h" -#include "feature/hibernate/hibernate.h" -#include "feature/hs/hs_cache.h" -#include "feature/hs/hs_common.h" -#include "feature/hs/hs_control.h" -#include "feature/hs_common/shared_random_client.h" -#include "feature/nodelist/authcert.h" -#include "feature/nodelist/dirlist.h" -#include "feature/nodelist/microdesc.h" -#include "feature/nodelist/networkstatus.h" -#include "feature/nodelist/nodelist.h" -#include "feature/nodelist/routerinfo.h" -#include "feature/nodelist/routerlist.h" -#include "feature/relay/router.h" -#include "feature/relay/routermode.h" -#include "feature/relay/selftest.h" -#include "feature/rend/rendclient.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_events.h" +#include "feature/control/control_proto.h" #include "feature/rend/rendcommon.h" -#include "feature/rend/rendparse.h" #include "feature/rend/rendservice.h" -#include "feature/stats/geoip_stats.h" -#include "feature/stats/predict_ports.h" -#include "lib/container/buffers.h" -#include "lib/crypt_ops/crypto_rand.h" -#include "lib/crypt_ops/crypto_util.h" -#include "lib/encoding/confline.h" -#include "lib/evloop/compat_libevent.h" +#include "lib/evloop/procmon.h" -#include "feature/dircache/cached_dir_st.h" #include "feature/control/control_connection_st.h" -#include "core/or/cpath_build_state_st.h" -#include "core/or/entry_connection_st.h" -#include "feature/nodelist/extrainfo_st.h" -#include "feature/nodelist/networkstatus_st.h" -#include "feature/nodelist/node_st.h" -#include "core/or/or_connection_st.h" -#include "core/or/or_circuit_st.h" -#include "core/or/origin_circuit_st.h" -#include "feature/nodelist/microdesc_st.h" -#include "feature/rend/rend_authorized_client_st.h" -#include "feature/rend/rend_encoded_v2_service_descriptor_st.h" -#include "feature/rend/rend_service_descriptor_st.h" -#include "feature/nodelist/routerinfo_st.h" -#include "feature/nodelist/routerlist_st.h" -#include "core/or/socks_request_st.h" #ifdef HAVE_UNISTD_H #include <unistd.h> @@ -118,152 +61,6 @@ #include <sys/stat.h> #endif -#ifndef _WIN32 -#include <pwd.h> -#include <sys/resource.h> -#endif - -#include "lib/crypt_ops/crypto_s2k.h" -#include "lib/evloop/procmon.h" -#include "lib/evloop/compat_libevent.h" - -/** Yield true iff <b>s</b> is the state of a control_connection_t that has - * finished authentication and is accepting commands. */ -#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) - -/** Bitfield: The bit 1<<e is set if <b>any</b> open control - * connection is interested in events of type <b>e</b>. We use this - * so that we can decide to skip generating event messages that nobody - * has interest in without having to walk over the global connection - * list to find out. - **/ -typedef uint64_t event_mask_t; - -/** An event mask of all the events that any controller is interested in - * receiving. */ -static event_mask_t global_event_mask = 0; - -/** True iff we have disabled log messages from being sent to the controller */ -static int disable_log_messages = 0; - -/** Macro: true if any control connection is interested in events of type - * <b>e</b>. */ -#define EVENT_IS_INTERESTING(e) \ - (!! (global_event_mask & EVENT_MASK_(e))) - -/** Macro: true if any event from the bitfield 'e' is interesting. */ -#define ANY_EVENT_IS_INTERESTING(e) \ - (!! (global_event_mask & (e))) - -/** If we're using cookie-type authentication, how long should our cookies be? - */ -#define AUTHENTICATION_COOKIE_LEN 32 - -/** If true, we've set authentication_cookie to a secret code and - * stored it to disk. */ -static int authentication_cookie_is_set = 0; -/** If authentication_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 *authentication_cookie = NULL; - -#define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ - "Tor safe cookie authentication server-to-controller hash" -#define SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT \ - "Tor safe cookie authentication controller-to-server hash" -#define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN - -/** The list of onion services that have been added via ADD_ONION that do not - * belong to any particular control connection. - */ -static smartlist_t *detached_onion_services = NULL; - -/** A sufficiently large size to record the last bootstrap phase string. */ -#define BOOTSTRAP_MSG_LEN 1024 - -/** What was the last bootstrap phase message we sent? We keep track - * of this so we can respond to getinfo status/bootstrap-phase queries. */ -static char last_sent_bootstrap_message[BOOTSTRAP_MSG_LEN]; - -static void connection_printf_to_buf(control_connection_t *conn, - const char *format, ...) - CHECK_PRINTF(2,3); -static void send_control_event_impl(uint16_t event, - const char *format, va_list ap) - CHECK_PRINTF(2,0); -static int control_event_status(int type, int severity, const char *format, - va_list args) - CHECK_PRINTF(3,0); - -static void send_control_done(control_connection_t *conn); -static void send_control_event(uint16_t event, - const char *format, ...) - CHECK_PRINTF(2,3); -static int handle_control_setconf(control_connection_t *conn, uint32_t len, - char *body); -static int handle_control_resetconf(control_connection_t *conn, uint32_t len, - char *body); -static int handle_control_getconf(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_loadconf(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_setevents(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_authenticate(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_signal(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_mapaddress(control_connection_t *conn, uint32_t len, - const char *body); -static char *list_getinfo_options(void); -static int handle_control_getinfo(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_extendcircuit(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_setcircuitpurpose(control_connection_t *conn, - uint32_t len, const char *body); -static int handle_control_attachstream(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_postdescriptor(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_redirectstream(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_closestream(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_closecircuit(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_resolve(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_usefeature(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_hspost(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_add_onion(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_del_onion(control_connection_t *conn, uint32_t len, - const char *body); -static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, - size_t len); -static void orconn_target_get_name(char *buf, size_t len, - or_connection_t *conn); - -static int get_cached_network_liveness(void); -static void set_cached_network_liveness(int liveness); - -static void flush_queued_events_cb(mainloop_event_t *event, void *arg); - -static char * download_status_to_string(const download_status_t *dl); -static void control_get_bytes_rw_last_sec(uint64_t *r, uint64_t *w); - /** Convert a connection_t* to an control_connection_t*; assert if the cast is * invalid. */ control_connection_t * @@ -273,410 +70,6 @@ TO_CONTROL_CONN(connection_t *c) return DOWNCAST(control_connection_t, c); } -/** Given a control event code for a message event, return the corresponding - * log severity. */ -static inline int -event_to_log_severity(int event) -{ - switch (event) { - case EVENT_DEBUG_MSG: return LOG_DEBUG; - case EVENT_INFO_MSG: return LOG_INFO; - case EVENT_NOTICE_MSG: return LOG_NOTICE; - case EVENT_WARN_MSG: return LOG_WARN; - case EVENT_ERR_MSG: return LOG_ERR; - default: return -1; - } -} - -/** Given a log severity, return the corresponding control event code. */ -static inline int -log_severity_to_event(int severity) -{ - switch (severity) { - case LOG_DEBUG: return EVENT_DEBUG_MSG; - case LOG_INFO: return EVENT_INFO_MSG; - case LOG_NOTICE: return EVENT_NOTICE_MSG; - case LOG_WARN: return EVENT_WARN_MSG; - case LOG_ERR: return EVENT_ERR_MSG; - default: return -1; - } -} - -/** Helper: clear bandwidth counters of all origin circuits. */ -static void -clear_circ_bw_fields(void) -{ - origin_circuit_t *ocirc; - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { - if (!CIRCUIT_IS_ORIGIN(circ)) - continue; - ocirc = TO_ORIGIN_CIRCUIT(circ); - ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; - ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0; - ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0; - } - SMARTLIST_FOREACH_END(circ); -} - -/** Set <b>global_event_mask*</b> to the bitwise OR of each live control - * connection's event_mask field. */ -void -control_update_global_event_mask(void) -{ - smartlist_t *conns = get_connection_array(); - event_mask_t old_mask, new_mask; - old_mask = global_event_mask; - int any_old_per_sec_events = control_any_per_second_event_enabled(); - - global_event_mask = 0; - SMARTLIST_FOREACH(conns, connection_t *, _conn, - { - if (_conn->type == CONN_TYPE_CONTROL && - STATE_IS_OPEN(_conn->state)) { - control_connection_t *conn = TO_CONTROL_CONN(_conn); - global_event_mask |= conn->event_mask; - } - }); - - new_mask = global_event_mask; - - /* Handle the aftermath. Set up the log callback to tell us only what - * we want to hear...*/ - control_adjust_event_log_severity(); - - /* Macro: true if ev was false before and is true now. */ -#define NEWLY_ENABLED(ev) \ - (! (old_mask & (ev)) && (new_mask & (ev))) - - /* ...then, if we've started logging stream or circ bw, clear the - * appropriate fields. */ - if (NEWLY_ENABLED(EVENT_STREAM_BANDWIDTH_USED)) { - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == CONN_TYPE_AP) { - edge_connection_t *edge_conn = TO_EDGE_CONN(conn); - edge_conn->n_written = edge_conn->n_read = 0; - } - }); - } - if (NEWLY_ENABLED(EVENT_CIRC_BANDWIDTH_USED)) { - clear_circ_bw_fields(); - } - if (NEWLY_ENABLED(EVENT_BANDWIDTH_USED)) { - uint64_t r, w; - control_get_bytes_rw_last_sec(&r, &w); - } - if (any_old_per_sec_events != control_any_per_second_event_enabled()) { - reschedule_per_second_timer(); - } - -#undef NEWLY_ENABLED -} - -/** Adjust the log severities that result in control_event_logmsg being called - * to match the severity of log messages that any controllers are interested - * in. */ -void -control_adjust_event_log_severity(void) -{ - int i; - int min_log_event=EVENT_ERR_MSG, max_log_event=EVENT_DEBUG_MSG; - - for (i = EVENT_DEBUG_MSG; i <= EVENT_ERR_MSG; ++i) { - if (EVENT_IS_INTERESTING(i)) { - min_log_event = i; - break; - } - } - for (i = EVENT_ERR_MSG; i >= EVENT_DEBUG_MSG; --i) { - if (EVENT_IS_INTERESTING(i)) { - max_log_event = i; - break; - } - } - if (EVENT_IS_INTERESTING(EVENT_STATUS_GENERAL)) { - if (min_log_event > EVENT_NOTICE_MSG) - min_log_event = EVENT_NOTICE_MSG; - if (max_log_event < EVENT_ERR_MSG) - max_log_event = EVENT_ERR_MSG; - } - if (min_log_event <= max_log_event) - change_callback_log_severity(event_to_log_severity(min_log_event), - event_to_log_severity(max_log_event), - control_event_logmsg); - else - change_callback_log_severity(LOG_ERR, LOG_ERR, - control_event_logmsg); -} - -/** Return true iff the event with code <b>c</b> is being sent to any current - * control connection. This is useful if the amount of work needed to prepare - * to call the appropriate control_event_...() function is high. - */ -int -control_event_is_interesting(int event) -{ - return EVENT_IS_INTERESTING(event); -} - -/** Return true if any event that needs to fire once a second is enabled. */ -int -control_any_per_second_event_enabled(void) -{ - return ANY_EVENT_IS_INTERESTING( - EVENT_MASK_(EVENT_BANDWIDTH_USED) | - EVENT_MASK_(EVENT_CELL_STATS) | - EVENT_MASK_(EVENT_CIRC_BANDWIDTH_USED) | - EVENT_MASK_(EVENT_CONN_BW) | - EVENT_MASK_(EVENT_STREAM_BANDWIDTH_USED) - ); -} - -/* The value of 'get_bytes_read()' the previous time that - * control_get_bytes_rw_last_sec() as called. */ -static uint64_t stats_prev_n_read = 0; -/* The value of 'get_bytes_written()' the previous time that - * control_get_bytes_rw_last_sec() as called. */ -static uint64_t stats_prev_n_written = 0; - -/** - * Set <b>n_read</b> and <b>n_written</b> to the total number of bytes read - * and written by Tor since the last call to this function. - * - * Call this only from the main thread. - */ -static void -control_get_bytes_rw_last_sec(uint64_t *n_read, - uint64_t *n_written) -{ - const uint64_t stats_n_bytes_read = get_bytes_read(); - const uint64_t stats_n_bytes_written = get_bytes_written(); - - *n_read = stats_n_bytes_read - stats_prev_n_read; - *n_written = stats_n_bytes_written - stats_prev_n_written; - stats_prev_n_read = stats_n_bytes_read; - stats_prev_n_written = stats_n_bytes_written; -} - -/** - * Run all the controller events (if any) that are scheduled to trigger once - * per second. - */ -void -control_per_second_events(void) -{ - if (!control_any_per_second_event_enabled()) - return; - - uint64_t bytes_read, bytes_written; - control_get_bytes_rw_last_sec(&bytes_read, &bytes_written); - control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); - - control_event_stream_bandwidth_used(); - control_event_conn_bandwidth_used(); - control_event_circ_bandwidth_used(); - control_event_circuit_cell_stats(); -} - -/** Append a NUL-terminated string <b>s</b> to the end of - * <b>conn</b>-\>outbuf. - */ -static inline void -connection_write_str_to_buf(const char *s, control_connection_t *conn) -{ - size_t len = strlen(s); - connection_buf_add(s, len, TO_CONN(conn)); -} - -/** Given a <b>len</b>-character string in <b>data</b>, made of lines - * terminated by CRLF, allocate a new string in *<b>out</b>, and copy the - * contents of <b>data</b> into *<b>out</b>, adding a period before any period - * that appears at the start of a line, and adding a period-CRLF line at - * the end. Replace all LF characters sequences with CRLF. Return the number - * of bytes in *<b>out</b>. - */ -STATIC size_t -write_escaped_data(const char *data, size_t len, char **out) -{ - tor_assert(len < SIZE_MAX - 9); - size_t sz_out = len+8+1; - char *outp; - const char *start = data, *end; - size_t i; - int start_of_line; - for (i=0; i < len; ++i) { - if (data[i] == '\n') { - sz_out += 2; /* Maybe add a CR; maybe add a dot. */ - if (sz_out >= SIZE_T_CEILING) { - log_warn(LD_BUG, "Input to write_escaped_data was too long"); - *out = tor_strdup(".\r\n"); - return 3; - } - } - } - *out = outp = tor_malloc(sz_out); - end = data+len; - start_of_line = 1; - while (data < end) { - if (*data == '\n') { - if (data > start && data[-1] != '\r') - *outp++ = '\r'; - start_of_line = 1; - } else if (*data == '.') { - if (start_of_line) { - start_of_line = 0; - *outp++ = '.'; - } - } else { - start_of_line = 0; - } - *outp++ = *data++; - } - if (outp < *out+2 || fast_memcmp(outp-2, "\r\n", 2)) { - *outp++ = '\r'; - *outp++ = '\n'; - } - *outp++ = '.'; - *outp++ = '\r'; - *outp++ = '\n'; - *outp = '\0'; /* NUL-terminate just in case. */ - tor_assert(outp >= *out); - tor_assert((size_t)(outp - *out) <= sz_out); - return outp - *out; -} - -/** Given a <b>len</b>-character string in <b>data</b>, made of lines - * terminated by CRLF, allocate a new string in *<b>out</b>, and copy - * the contents of <b>data</b> into *<b>out</b>, removing any period - * that appears at the start of a line, and replacing all CRLF sequences - * with LF. Return the number of - * bytes in *<b>out</b>. */ -STATIC size_t -read_escaped_data(const char *data, size_t len, char **out) -{ - char *outp; - const char *next; - const char *end; - - *out = outp = tor_malloc(len+1); - - end = data+len; - - while (data < end) { - /* we're at the start of a line. */ - if (*data == '.') - ++data; - next = memchr(data, '\n', end-data); - if (next) { - size_t n_to_copy = next-data; - /* Don't copy a CR that precedes this LF. */ - if (n_to_copy && *(next-1) == '\r') - --n_to_copy; - memcpy(outp, data, n_to_copy); - outp += n_to_copy; - data = next+1; /* This will point at the start of the next line, - * or the end of the string, or a period. */ - } else { - memcpy(outp, data, end-data); - outp += (end-data); - *outp = '\0'; - return outp - *out; - } - *outp++ = '\n'; - } - - *outp = '\0'; - return outp - *out; -} - -/** If the first <b>in_len_max</b> characters in <b>start</b> contain a - * double-quoted string with escaped characters, return the length of that - * string (as encoded, including quotes). Otherwise return -1. */ -static inline int -get_escaped_string_length(const char *start, size_t in_len_max, - int *chars_out) -{ - const char *cp, *end; - int chars = 0; - - if (*start != '\"') - return -1; - - cp = start+1; - end = start+in_len_max; - - /* Calculate length. */ - while (1) { - if (cp >= end) { - return -1; /* Too long. */ - } else if (*cp == '\\') { - if (++cp == end) - return -1; /* Can't escape EOS. */ - ++cp; - ++chars; - } else if (*cp == '\"') { - break; - } else { - ++cp; - ++chars; - } - } - if (chars_out) - *chars_out = chars; - return (int)(cp - start+1); -} - -/** As decode_escaped_string, but does not decode the string: copies the - * entire thing, including quotation marks. */ -static const char * -extract_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - int length = get_escaped_string_length(start, in_len_max, NULL); - if (length<0) - return NULL; - *out_len = length; - *out = tor_strndup(start, *out_len); - return start+length; -} - -/** Given a pointer to a string starting at <b>start</b> containing - * <b>in_len_max</b> characters, decode a string beginning with one double - * quote, containing any number of non-quote characters or characters escaped - * with a backslash, and ending with a final double quote. Place the resulting - * string (unquoted, unescaped) into a newly allocated string in *<b>out</b>; - * store its length in <b>out_len</b>. On success, return a pointer to the - * character immediately following the escaped string. On failure, return - * NULL. */ -static const char * -decode_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - const char *cp, *end; - char *outp; - int len, n_chars = 0; - - len = get_escaped_string_length(start, in_len_max, &n_chars); - if (len<0) - return NULL; - - end = start+len-1; /* Index of last quote. */ - tor_assert(*end == '\"'); - outp = *out = tor_malloc(len+1); - *out_len = n_chars; - - cp = start+1; - while (cp < end) { - if (*cp == '\\') - ++cp; - *outp++ = *cp++; - } - *outp = '\0'; - tor_assert((outp - *out) == (int)*out_len); - - return end+1; -} - /** Create and add a new controller connection on <b>sock</b>. If * <b>CC_LOCAL_FD_IS_OWNER</b> is set in <b>flags</b>, this Tor process should * exit when the connection closes. If <b>CC_LOCAL_FD_IS_AUTHENTICATED</b> @@ -720,29 +113,6 @@ control_connection_add_local_fd(tor_socket_t sock, unsigned flags) return 0; } -/** Acts like sprintf, but writes its formatted string to the end of - * <b>conn</b>-\>outbuf. */ -static void -connection_printf_to_buf(control_connection_t *conn, const char *format, ...) -{ - va_list ap; - char *buf = NULL; - int len; - - va_start(ap,format); - len = tor_vasprintf(&buf, format, ap); - va_end(ap); - - if (len < 0) { - log_err(LD_BUG, "Unable to format string for controller."); - tor_assert(0); - } - - connection_buf_add(buf, (size_t)len, TO_CONN(conn)); - - tor_free(buf); -} - /** Write all of the open control ports to ControlPortWriteToFile */ void control_ports_write_to_file(void) @@ -787,886 +157,11 @@ control_ports_write_to_file(void) smartlist_free(lines); } -/** Send a "DONE" message down the control connection <b>conn</b>. */ -static void -send_control_done(control_connection_t *conn) -{ - connection_write_str_to_buf("250 OK\r\n", conn); -} - -/** Represents an event that's queued to be sent to one or more - * controllers. */ -typedef struct queued_event_s { - uint16_t event; - char *msg; -} queued_event_t; - -/** Pointer to int. If this is greater than 0, we don't allow new events to be - * queued. */ -static tor_threadlocal_t block_event_queue_flag; - -/** Holds a smartlist of queued_event_t objects that may need to be sent - * to one or more controllers */ -static smartlist_t *queued_control_events = NULL; - -/** True if the flush_queued_events_event is pending. */ -static int flush_queued_event_pending = 0; - -/** Lock to protect the above fields. */ -static tor_mutex_t *queued_control_events_lock = NULL; - -/** An event that should fire in order to flush the contents of - * queued_control_events. */ -static mainloop_event_t *flush_queued_events_event = NULL; - -void -control_initialize_event_queue(void) -{ - if (queued_control_events == NULL) { - queued_control_events = smartlist_new(); - } - - if (flush_queued_events_event == NULL) { - struct event_base *b = tor_libevent_get_base(); - if (b) { - flush_queued_events_event = - mainloop_event_new(flush_queued_events_cb, NULL); - tor_assert(flush_queued_events_event); - } - } - - if (queued_control_events_lock == NULL) { - queued_control_events_lock = tor_mutex_new(); - tor_threadlocal_init(&block_event_queue_flag); - } -} - -static int * -get_block_event_queue(void) -{ - int *val = tor_threadlocal_get(&block_event_queue_flag); - if (PREDICT_UNLIKELY(val == NULL)) { - val = tor_malloc_zero(sizeof(int)); - tor_threadlocal_set(&block_event_queue_flag, val); - } - return val; -} - -/** Helper: inserts an event on the list of events queued to be sent to - * one or more controllers, and schedules the events to be flushed if needed. - * - * This function takes ownership of <b>msg</b>, and may free it. - * - * We queue these events rather than send them immediately in order to break - * the dependency in our callgraph from code that generates events for the - * controller, and the network layer at large. Otherwise, nearly every - * interesting part of Tor would potentially call every other interesting part - * of Tor. - */ -MOCK_IMPL(STATIC void, -queue_control_event_string,(uint16_t event, char *msg)) -{ - /* This is redundant with checks done elsewhere, but it's a last-ditch - * attempt to avoid queueing something we shouldn't have to queue. */ - if (PREDICT_UNLIKELY( ! EVENT_IS_INTERESTING(event) )) { - tor_free(msg); - return; - } - - int *block_event_queue = get_block_event_queue(); - if (*block_event_queue) { - tor_free(msg); - return; - } - - queued_event_t *ev = tor_malloc(sizeof(*ev)); - ev->event = event; - ev->msg = msg; - - /* No queueing an event while queueing an event */ - ++*block_event_queue; - - tor_mutex_acquire(queued_control_events_lock); - tor_assert(queued_control_events); - smartlist_add(queued_control_events, ev); - - int activate_event = 0; - if (! flush_queued_event_pending && in_main_thread()) { - activate_event = 1; - flush_queued_event_pending = 1; - } - - tor_mutex_release(queued_control_events_lock); - - --*block_event_queue; - - /* We just put an event on the queue; mark the queue to be - * flushed. We only do this from the main thread for now; otherwise, - * we'd need to incur locking overhead in Libevent or use a socket. - */ - if (activate_event) { - tor_assert(flush_queued_events_event); - mainloop_event_activate(flush_queued_events_event); - } -} - -#define queued_event_free(ev) \ - FREE_AND_NULL(queued_event_t, queued_event_free_, (ev)) - -/** Release all storage held by <b>ev</b>. */ -static void -queued_event_free_(queued_event_t *ev) -{ - if (ev == NULL) - return; - - tor_free(ev->msg); - tor_free(ev); -} - -/** Send every queued event to every controller that's interested in it, - * and remove the events from the queue. If <b>force</b> is true, - * then make all controllers send their data out immediately, since we - * may be about to shut down. */ -static void -queued_events_flush_all(int force) -{ - /* Make sure that we get all the pending log events, if there are any. */ - flush_pending_log_callbacks(); - - if (PREDICT_UNLIKELY(queued_control_events == NULL)) { - return; - } - smartlist_t *all_conns = get_connection_array(); - smartlist_t *controllers = smartlist_new(); - smartlist_t *queued_events; - - int *block_event_queue = get_block_event_queue(); - ++*block_event_queue; - - tor_mutex_acquire(queued_control_events_lock); - /* No queueing an event while flushing events. */ - flush_queued_event_pending = 0; - queued_events = queued_control_events; - queued_control_events = smartlist_new(); - tor_mutex_release(queued_control_events_lock); - - /* Gather all the controllers that will care... */ - SMARTLIST_FOREACH_BEGIN(all_conns, connection_t *, conn) { - if (conn->type == CONN_TYPE_CONTROL && - !conn->marked_for_close && - conn->state == CONTROL_CONN_STATE_OPEN) { - control_connection_t *control_conn = TO_CONTROL_CONN(conn); - - smartlist_add(controllers, control_conn); - } - } SMARTLIST_FOREACH_END(conn); - - SMARTLIST_FOREACH_BEGIN(queued_events, queued_event_t *, ev) { - const event_mask_t bit = ((event_mask_t)1) << ev->event; - const size_t msg_len = strlen(ev->msg); - SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, - control_conn) { - if (control_conn->event_mask & bit) { - connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); - } - } SMARTLIST_FOREACH_END(control_conn); - - queued_event_free(ev); - } SMARTLIST_FOREACH_END(ev); - - if (force) { - SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, - control_conn) { - connection_flush(TO_CONN(control_conn)); - } SMARTLIST_FOREACH_END(control_conn); - } - - smartlist_free(queued_events); - smartlist_free(controllers); - - --*block_event_queue; -} - -/** Libevent callback: Flushes pending events to controllers that are - * interested in them. */ -static void -flush_queued_events_cb(mainloop_event_t *event, void *arg) -{ - (void) event; - (void) arg; - queued_events_flush_all(0); -} - -/** Send an event to all v1 controllers that are listening for code - * <b>event</b>. The event's body is given by <b>msg</b>. - * - * The EXTENDED_FORMAT and NONEXTENDED_FORMAT flags behave similarly with - * respect to the EXTENDED_EVENTS feature. */ -MOCK_IMPL(STATIC void, -send_control_event_string,(uint16_t event, - const char *msg)) -{ - tor_assert(event >= EVENT_MIN_ && event <= EVENT_MAX_); - queue_control_event_string(event, tor_strdup(msg)); -} - -/** Helper for send_control_event and control_event_status: - * Send an event to all v1 controllers that are listening for code - * <b>event</b>. The event's body is created by the printf-style format in - * <b>format</b>, and other arguments as provided. */ -static void -send_control_event_impl(uint16_t event, - const char *format, va_list ap) -{ - char *buf = NULL; - int len; - - len = tor_vasprintf(&buf, format, ap); - if (len < 0) { - log_warn(LD_BUG, "Unable to format event for controller."); - return; - } - - queue_control_event_string(event, buf); -} - -/** Send an event to all v1 controllers that are listening for code - * <b>event</b>. The event's body is created by the printf-style format in - * <b>format</b>, and other arguments as provided. */ -static void -send_control_event(uint16_t event, - const char *format, ...) -{ - va_list ap; - va_start(ap, format); - send_control_event_impl(event, format, ap); - va_end(ap); -} - -/** Given a text circuit <b>id</b>, return the corresponding circuit. */ -static origin_circuit_t * -get_circ(const char *id) -{ - uint32_t n_id; - int ok; - n_id = (uint32_t) tor_parse_ulong(id, 10, 0, UINT32_MAX, &ok, NULL); - if (!ok) - return NULL; - return circuit_get_by_global_id(n_id); -} - -/** Given a text stream <b>id</b>, return the corresponding AP connection. */ -static entry_connection_t * -get_stream(const char *id) -{ - uint64_t n_id; - int ok; - connection_t *conn; - n_id = tor_parse_uint64(id, 10, 0, UINT64_MAX, &ok, NULL); - if (!ok) - return NULL; - conn = connection_get_by_global_id(n_id); - if (!conn || conn->type != CONN_TYPE_AP || conn->marked_for_close) - return NULL; - return TO_ENTRY_CONN(conn); -} - -/** Helper for setconf and resetconf. Acts like setconf, except - * it passes <b>use_defaults</b> on to options_trial_assign(). Modifies the - * contents of body. - */ -static int -control_setconf_helper(control_connection_t *conn, uint32_t len, char *body, - int use_defaults) -{ - setopt_err_t opt_err; - config_line_t *lines=NULL; - char *start = body; - char *errstring = NULL; - const unsigned flags = - CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0); - - char *config; - smartlist_t *entries = smartlist_new(); - - /* We have a string, "body", of the format '(key(=val|="val")?)' entries - * separated by space. break it into a list of configuration entries. */ - while (*body) { - char *eq = body; - char *key; - char *entry; - while (!TOR_ISSPACE(*eq) && *eq != '=') - ++eq; - key = tor_strndup(body, eq-body); - body = eq+1; - if (*eq == '=') { - char *val=NULL; - size_t val_len=0; - if (*body != '\"') { - char *val_start = body; - while (!TOR_ISSPACE(*body)) - body++; - val = tor_strndup(val_start, body-val_start); - val_len = strlen(val); - } else { - body = (char*)extract_escaped_string(body, (len - (body-start)), - &val, &val_len); - if (!body) { - connection_write_str_to_buf("551 Couldn't parse string\r\n", conn); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - tor_free(key); - return 0; - } - } - tor_asprintf(&entry, "%s %s", key, val); - tor_free(key); - tor_free(val); - } else { - entry = key; - } - smartlist_add(entries, entry); - while (TOR_ISSPACE(*body)) - ++body; - } - - smartlist_add_strdup(entries, ""); - config = smartlist_join_strings(entries, "\n", 0, NULL); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - - if (config_get_lines(config, &lines, 0) < 0) { - log_warn(LD_CONTROL,"Controller gave us config lines we can't parse."); - connection_write_str_to_buf("551 Couldn't parse configuration\r\n", - conn); - tor_free(config); - return 0; - } - tor_free(config); - - opt_err = options_trial_assign(lines, flags, &errstring); - { - const char *msg; - switch (opt_err) { - case SETOPT_ERR_MISC: - msg = "552 Unrecognized option"; - break; - case SETOPT_ERR_PARSE: - msg = "513 Unacceptable option value"; - break; - case SETOPT_ERR_TRANSITION: - msg = "553 Transition not allowed"; - break; - case SETOPT_ERR_SETTING: - default: - msg = "553 Unable to set option"; - break; - case SETOPT_OK: - config_free_lines(lines); - send_control_done(conn); - return 0; - } - log_warn(LD_CONTROL, - "Controller gave us config lines that didn't validate: %s", - errstring); - connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); - config_free_lines(lines); - tor_free(errstring); - return 0; - } -} - -/** Called when we receive a SETCONF message: parse the body and try - * to update our configuration. Reply with a DONE or ERROR message. - * Modifies the contents of body.*/ -static int -handle_control_setconf(control_connection_t *conn, uint32_t len, char *body) -{ - return control_setconf_helper(conn, len, body, 0); -} - -/** Called when we receive a RESETCONF message: parse the body and try - * to update our configuration. Reply with a DONE or ERROR message. - * Modifies the contents of body. */ -static int -handle_control_resetconf(control_connection_t *conn, uint32_t len, char *body) -{ - return control_setconf_helper(conn, len, body, 1); -} - -/** Called when we receive a GETCONF message. Parse the request, and - * reply with a CONFVALUE or an ERROR message */ -static int -handle_control_getconf(control_connection_t *conn, uint32_t body_len, - const char *body) -{ - smartlist_t *questions = smartlist_new(); - smartlist_t *answers = smartlist_new(); - smartlist_t *unrecognized = smartlist_new(); - char *msg = NULL; - size_t msg_len; - const or_options_t *options = get_options(); - int i, len; - - (void) body_len; /* body is NUL-terminated; so we can ignore len. */ - smartlist_split_string(questions, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { - if (!option_is_recognized(q)) { - smartlist_add(unrecognized, (char*) q); - } else { - config_line_t *answer = option_get_assignment(options,q); - if (!answer) { - const char *name = option_get_canonical_name(q); - smartlist_add_asprintf(answers, "250-%s\r\n", name); - } - - while (answer) { - config_line_t *next; - smartlist_add_asprintf(answers, "250-%s=%s\r\n", - answer->key, answer->value); - - next = answer->next; - tor_free(answer->key); - tor_free(answer->value); - tor_free(answer); - answer = next; - } - } - } SMARTLIST_FOREACH_END(q); - - if ((len = smartlist_len(unrecognized))) { - for (i=0; i < len-1; ++i) - connection_printf_to_buf(conn, - "552-Unrecognized configuration key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); - connection_printf_to_buf(conn, - "552 Unrecognized configuration key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, len-1)); - } else if ((len = smartlist_len(answers))) { - char *tmp = smartlist_get(answers, len-1); - tor_assert(strlen(tmp)>4); - tmp[3] = ' '; - msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_buf_add(msg, msg_len, TO_CONN(conn)); - } else { - connection_write_str_to_buf("250 OK\r\n", conn); - } - - SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); - smartlist_free(answers); - SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); - smartlist_free(questions); - smartlist_free(unrecognized); - - tor_free(msg); - - return 0; -} - -/** Called when we get a +LOADCONF message. */ -static int -handle_control_loadconf(control_connection_t *conn, uint32_t len, - const char *body) -{ - setopt_err_t retval; - char *errstring = NULL; - const char *msg = NULL; - (void) len; - - retval = options_init_from_string(NULL, body, CMD_RUN_TOR, NULL, &errstring); - - if (retval != SETOPT_OK) - log_warn(LD_CONTROL, - "Controller gave us config file that didn't validate: %s", - errstring); - - switch (retval) { - case SETOPT_ERR_PARSE: - msg = "552 Invalid config file"; - break; - case SETOPT_ERR_TRANSITION: - msg = "553 Transition not allowed"; - break; - case SETOPT_ERR_SETTING: - msg = "553 Unable to set option"; - break; - case SETOPT_ERR_MISC: - default: - msg = "550 Unable to load config"; - break; - case SETOPT_OK: - break; - } - if (msg) { - if (errstring) - connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); - else - connection_printf_to_buf(conn, "%s\r\n", msg); - } else { - send_control_done(conn); - } - tor_free(errstring); - return 0; -} - -/** Helper structure: maps event values to their names. */ -struct control_event_t { - uint16_t event_code; - const char *event_name; -}; -/** Table mapping event values to their names. Used to implement SETEVENTS - * and GETINFO events/names, and to keep they in sync. */ -static const struct control_event_t control_event_table[] = { - { EVENT_CIRCUIT_STATUS, "CIRC" }, - { EVENT_CIRCUIT_STATUS_MINOR, "CIRC_MINOR" }, - { EVENT_STREAM_STATUS, "STREAM" }, - { EVENT_OR_CONN_STATUS, "ORCONN" }, - { EVENT_BANDWIDTH_USED, "BW" }, - { EVENT_DEBUG_MSG, "DEBUG" }, - { EVENT_INFO_MSG, "INFO" }, - { EVENT_NOTICE_MSG, "NOTICE" }, - { EVENT_WARN_MSG, "WARN" }, - { EVENT_ERR_MSG, "ERR" }, - { EVENT_NEW_DESC, "NEWDESC" }, - { EVENT_ADDRMAP, "ADDRMAP" }, - { EVENT_DESCCHANGED, "DESCCHANGED" }, - { EVENT_NS, "NS" }, - { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, - { EVENT_STATUS_CLIENT, "STATUS_CLIENT" }, - { EVENT_STATUS_SERVER, "STATUS_SERVER" }, - { EVENT_GUARD, "GUARD" }, - { EVENT_STREAM_BANDWIDTH_USED, "STREAM_BW" }, - { EVENT_CLIENTS_SEEN, "CLIENTS_SEEN" }, - { EVENT_NEWCONSENSUS, "NEWCONSENSUS" }, - { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" }, - { EVENT_GOT_SIGNAL, "SIGNAL" }, - { EVENT_CONF_CHANGED, "CONF_CHANGED"}, - { EVENT_CONN_BW, "CONN_BW" }, - { EVENT_CELL_STATS, "CELL_STATS" }, - { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" }, - { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" }, - { EVENT_HS_DESC, "HS_DESC" }, - { EVENT_HS_DESC_CONTENT, "HS_DESC_CONTENT" }, - { EVENT_NETWORK_LIVENESS, "NETWORK_LIVENESS" }, - { 0, NULL }, -}; - -/** Called when we get a SETEVENTS message: update conn->event_mask, - * and reply with DONE or ERROR. */ -static int -handle_control_setevents(control_connection_t *conn, uint32_t len, - const char *body) -{ - int event_code; - event_mask_t event_mask = 0; - smartlist_t *events = smartlist_new(); - - (void) len; - - smartlist_split_string(events, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(events, const char *, ev) - { - if (!strcasecmp(ev, "EXTENDED") || - !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { - log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " - "supported.", ev); - continue; - } else { - int i; - event_code = -1; - - for (i = 0; control_event_table[i].event_name != NULL; ++i) { - if (!strcasecmp(ev, control_event_table[i].event_name)) { - event_code = control_event_table[i].event_code; - break; - } - } - - if (event_code == -1) { - connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", - ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); - return 0; - } - } - event_mask |= (((event_mask_t)1) << event_code); - } - SMARTLIST_FOREACH_END(ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); - - conn->event_mask = event_mask; - - control_update_global_event_mask(); - send_control_done(conn); - return 0; -} - -/** Decode the hashed, base64'd passwords stored in <b>passwords</b>. - * Return a smartlist of acceptable passwords (unterminated strings of - * length S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on - * failure. - */ -smartlist_t * -decode_hashed_passwords(config_line_t *passwords) -{ - char decoded[64]; - config_line_t *cl; - smartlist_t *sl = smartlist_new(); - - tor_assert(passwords); - - for (cl = passwords; cl; cl = cl->next) { - const char *hashed = cl->value; - - if (!strcmpstart(hashed, "16:")) { - if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3)) - != S2K_RFC2440_SPECIFIER_LEN + DIGEST_LEN - || strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) { - goto err; - } - } else { - if (base64_decode(decoded, sizeof(decoded), hashed, strlen(hashed)) - != S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) { - goto err; - } - } - smartlist_add(sl, - tor_memdup(decoded, S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)); - } - - return sl; - - err: - SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp)); - smartlist_free(sl); - return NULL; -} - -/** Called when we get an AUTHENTICATE message. Check whether the - * authentication is valid, and if so, update the connection's state to - * OPEN. Reply with DONE or ERROR. - */ -static int -handle_control_authenticate(control_connection_t *conn, uint32_t len, - const char *body) -{ - int used_quoted_string = 0; - const or_options_t *options = get_options(); - const char *errstr = "Unknown error"; - char *password; - size_t password_len; - const char *cp; - int i; - int bad_cookie=0, bad_password=0; - smartlist_t *sl = NULL; - - if (!len) { - password = tor_strdup(""); - password_len = 0; - } else if (TOR_ISXDIGIT(body[0])) { - cp = body; - while (TOR_ISXDIGIT(*cp)) - ++cp; - i = (int)(cp - body); - tor_assert(i>0); - password_len = i/2; - password = tor_malloc(password_len + 1); - if (base16_decode(password, password_len+1, body, i) - != (int) password_len) { - connection_write_str_to_buf( - "551 Invalid hexadecimal encoding. Maybe you tried a plain text " - "password? If so, the standard requires that you put it in " - "double quotes.\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(password); - return 0; - } - } else { - if (!decode_escaped_string(body, len, &password, &password_len)) { - connection_write_str_to_buf("551 Invalid quoted string. You need " - "to put the password in double quotes.\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return 0; - } - used_quoted_string = 1; - } - - if (conn->safecookie_client_hash != NULL) { - /* The controller has chosen safe cookie authentication; the only - * acceptable authentication value is the controller-to-server - * response. */ - - tor_assert(authentication_cookie_is_set); - - if (password_len != DIGEST256_LEN) { - log_warn(LD_CONTROL, - "Got safe cookie authentication response with wrong length " - "(%d)", (int)password_len); - errstr = "Wrong length for safe cookie response."; - goto err; - } - - if (tor_memneq(conn->safecookie_client_hash, password, DIGEST256_LEN)) { - log_warn(LD_CONTROL, - "Got incorrect safe cookie authentication response"); - errstr = "Safe cookie response did not match expected value."; - goto err; - } - - tor_free(conn->safecookie_client_hash); - goto ok; - } - - if (!options->CookieAuthentication && !options->HashedControlPassword && - !options->HashedControlSessionPassword) { - /* if Tor doesn't demand any stronger authentication, then - * the controller can get in with anything. */ - goto ok; - } - - if (options->CookieAuthentication) { - int also_password = options->HashedControlPassword != NULL || - options->HashedControlSessionPassword != NULL; - if (password_len != AUTHENTICATION_COOKIE_LEN) { - if (!also_password) { - log_warn(LD_CONTROL, "Got authentication cookie with wrong length " - "(%d)", (int)password_len); - errstr = "Wrong length on authentication cookie."; - goto err; - } - bad_cookie = 1; - } else if (tor_memneq(authentication_cookie, password, password_len)) { - if (!also_password) { - log_warn(LD_CONTROL, "Got mismatched authentication cookie"); - errstr = "Authentication cookie did not match expected value."; - goto err; - } - bad_cookie = 1; - } else { - goto ok; - } - } - - if (options->HashedControlPassword || - options->HashedControlSessionPassword) { - int bad = 0; - smartlist_t *sl_tmp; - char received[DIGEST_LEN]; - int also_cookie = options->CookieAuthentication; - sl = smartlist_new(); - if (options->HashedControlPassword) { - sl_tmp = decode_hashed_passwords(options->HashedControlPassword); - if (!sl_tmp) - bad = 1; - else { - smartlist_add_all(sl, sl_tmp); - smartlist_free(sl_tmp); - } - } - if (options->HashedControlSessionPassword) { - sl_tmp = decode_hashed_passwords(options->HashedControlSessionPassword); - if (!sl_tmp) - bad = 1; - else { - smartlist_add_all(sl, sl_tmp); - smartlist_free(sl_tmp); - } - } - if (bad) { - if (!also_cookie) { - log_warn(LD_BUG, - "Couldn't decode HashedControlPassword: invalid base16"); - errstr="Couldn't decode HashedControlPassword value in configuration."; - goto err; - } - bad_password = 1; - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - sl = NULL; - } else { - SMARTLIST_FOREACH(sl, char *, expected, - { - secret_to_key_rfc2440(received,DIGEST_LEN, - password,password_len,expected); - if (tor_memeq(expected + S2K_RFC2440_SPECIFIER_LEN, - received, DIGEST_LEN)) - goto ok; - }); - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - sl = NULL; - - if (used_quoted_string) - errstr = "Password did not match HashedControlPassword value from " - "configuration"; - else - errstr = "Password did not match HashedControlPassword value from " - "configuration. Maybe you tried a plain text password? " - "If so, the standard requires that you put it in double quotes."; - bad_password = 1; - if (!also_cookie) - goto err; - } - } - - /** We only get here if both kinds of authentication failed. */ - tor_assert(bad_password && bad_cookie); - log_warn(LD_CONTROL, "Bad password or authentication cookie on controller."); - errstr = "Password did not match HashedControlPassword *or* authentication " - "cookie."; - - err: - tor_free(password); - connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", errstr); - connection_mark_for_close(TO_CONN(conn)); - if (sl) { /* clean up */ - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - } - return 0; - ok: - log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT - ")", conn->base_.s); - send_control_done(conn); - conn->base_.state = CONTROL_CONN_STATE_OPEN; - tor_free(password); - if (sl) { /* clean up */ - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - } - return 0; -} - -/** Called when we get a SAVECONF command. Try to flush the current options to - * disk, and report success or failure. */ -static int -handle_control_saveconf(control_connection_t *conn, uint32_t len, - const char *body) -{ - (void) len; - - int force = !strcmpstart(body, "FORCE"); - const or_options_t *options = get_options(); - if ((!force && options->IncludeUsed) || options_save_current() < 0) { - connection_write_str_to_buf( - "551 Unable to write configuration to disk.\r\n", conn); - } else { - send_control_done(conn); - } - return 0; -} - -struct signal_t { - int sig; - const char *signal_name; -}; - -static const struct signal_t signal_table[] = { +const struct signal_name_t signal_table[] = { + /* NOTE: this table is used for handling SIGNAL commands and generating + * SIGNAL events. Order is significant: if there are two entries for the + * same numeric signal, the first one is the canonical name generated + * for the events. */ { SIGHUP, "RELOAD" }, { SIGHUP, "HUP" }, { SIGINT, "SHUTDOWN" }, @@ -1680,3576 +175,11 @@ static const struct signal_t signal_table[] = { { SIGNEWNYM, "NEWNYM" }, { SIGCLEARDNSCACHE, "CLEARDNSCACHE"}, { SIGHEARTBEAT, "HEARTBEAT"}, + { SIGACTIVE, "ACTIVE" }, + { SIGDORMANT, "DORMANT" }, { 0, NULL }, }; -/** Called when we get a SIGNAL command. React to the provided signal, and - * report success or failure. (If the signal results in a shutdown, success - * may not be reported.) */ -static int -handle_control_signal(control_connection_t *conn, uint32_t len, - const char *body) -{ - int sig = -1; - int i; - int n = 0; - char *s; - - (void) len; - - while (body[n] && ! TOR_ISSPACE(body[n])) - ++n; - s = tor_strndup(body, n); - - for (i = 0; signal_table[i].signal_name != NULL; ++i) { - if (!strcasecmp(s, signal_table[i].signal_name)) { - sig = signal_table[i].sig; - break; - } - } - - if (sig < 0) - connection_printf_to_buf(conn, "552 Unrecognized signal code \"%s\"\r\n", - s); - tor_free(s); - if (sig < 0) - return 0; - - send_control_done(conn); - /* Flush the "done" first if the signal might make us shut down. */ - if (sig == SIGTERM || sig == SIGINT) - connection_flush(TO_CONN(conn)); - - activate_signal(sig); - - return 0; -} - -/** Called when we get a TAKEOWNERSHIP command. Mark this connection - * as an owning connection, so that we will exit if the connection - * closes. */ -static int -handle_control_takeownership(control_connection_t *conn, uint32_t len, - const char *body) -{ - (void)len; - (void)body; - - conn->is_owning_control_connection = 1; - - log_info(LD_CONTROL, "Control connection %d has taken ownership of this " - "Tor instance.", - (int)(conn->base_.s)); - - send_control_done(conn); - return 0; -} - -/** Return true iff <b>addr</b> is unusable as a mapaddress target because of - * containing funny characters. */ -static int -address_is_invalid_mapaddress_target(const char *addr) -{ - if (!strcmpstart(addr, "*.")) - return address_is_invalid_destination(addr+2, 1); - else - return address_is_invalid_destination(addr, 1); -} - -/** Called when we get a MAPADDRESS command; try to bind all listed addresses, - * and report success or failure. */ -static int -handle_control_mapaddress(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *elts; - smartlist_t *lines; - smartlist_t *reply; - char *r; - size_t sz; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - lines = smartlist_new(); - elts = smartlist_new(); - reply = smartlist_new(); - smartlist_split_string(lines, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(lines, char *, line) { - tor_strlower(line); - smartlist_split_string(elts, line, "=", 0, 2); - if (smartlist_len(elts) == 2) { - const char *from = smartlist_get(elts,0); - const char *to = smartlist_get(elts,1); - if (address_is_invalid_mapaddress_target(to)) { - smartlist_add_asprintf(reply, - "512-syntax error: invalid address '%s'", to); - log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg", to); - } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0") || - !strcmp(from, "::")) { - const char type = - !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : - (!strcmp(from, "0.0.0.0") ? RESOLVED_TYPE_IPV4 : RESOLVED_TYPE_IPV6); - const char *address = addressmap_register_virtual_address( - type, tor_strdup(to)); - if (!address) { - smartlist_add_asprintf(reply, - "451-resource exhausted: skipping '%s'", line); - log_warn(LD_CONTROL, - "Unable to allocate address for '%s' in MapAddress msg", - safe_str_client(line)); - } else { - smartlist_add_asprintf(reply, "250-%s=%s", address, to); - } - } else { - const char *msg; - if (addressmap_register_auto(from, to, 1, - ADDRMAPSRC_CONTROLLER, &msg) < 0) { - smartlist_add_asprintf(reply, - "512-syntax error: invalid address mapping " - " '%s': %s", line, msg); - log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg: %s", - line, msg); - } else { - smartlist_add_asprintf(reply, "250-%s", line); - } - } - } else { - smartlist_add_asprintf(reply, "512-syntax error: mapping '%s' is " - "not of expected form 'foo=bar'.", line); - log_info(LD_CONTROL, "Skipping MapAddress '%s': wrong " - "number of items.", - safe_str_client(line)); - } - SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); - smartlist_clear(elts); - } SMARTLIST_FOREACH_END(line); - SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); - smartlist_free(lines); - smartlist_free(elts); - - if (smartlist_len(reply)) { - ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; - r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_buf_add(r, sz, TO_CONN(conn)); - tor_free(r); - } else { - const char *response = - "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_buf_add(response, strlen(response), TO_CONN(conn)); - } - - SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); - smartlist_free(reply); - return 0; -} - -/** Implementation helper for GETINFO: knows the answers for various - * trivial-to-implement questions. */ -static int -getinfo_helper_misc(control_connection_t *conn, const char *question, - char **answer, const char **errmsg) -{ - (void) conn; - if (!strcmp(question, "version")) { - *answer = tor_strdup(get_version()); - } else if (!strcmp(question, "bw-event-cache")) { - *answer = get_bw_samples(); - } else if (!strcmp(question, "config-file")) { - const char *a = get_torrc_fname(0); - if (a) - *answer = tor_strdup(a); - } else if (!strcmp(question, "config-defaults-file")) { - const char *a = get_torrc_fname(1); - if (a) - *answer = tor_strdup(a); - } else if (!strcmp(question, "config-text")) { - *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); - } else if (!strcmp(question, "config-can-saveconf")) { - *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1"); - } else if (!strcmp(question, "info/names")) { - *answer = list_getinfo_options(); - } else if (!strcmp(question, "dormant")) { - int dormant = rep_hist_circbuilding_dormant(time(NULL)); - *answer = tor_strdup(dormant ? "1" : "0"); - } else if (!strcmp(question, "events/names")) { - int i; - smartlist_t *event_names = smartlist_new(); - - for (i = 0; control_event_table[i].event_name != NULL; ++i) { - smartlist_add(event_names, (char *)control_event_table[i].event_name); - } - - *answer = smartlist_join_strings(event_names, " ", 0, NULL); - - smartlist_free(event_names); - } else if (!strcmp(question, "signal/names")) { - smartlist_t *signal_names = smartlist_new(); - int j; - for (j = 0; signal_table[j].signal_name != NULL; ++j) { - smartlist_add(signal_names, (char*)signal_table[j].signal_name); - } - - *answer = smartlist_join_strings(signal_names, " ", 0, NULL); - - smartlist_free(signal_names); - } else if (!strcmp(question, "features/names")) { - *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS"); - } else if (!strcmp(question, "address")) { - uint32_t addr; - if (router_pick_published_address(get_options(), &addr, 0) < 0) { - *errmsg = "Address unknown"; - return -1; - } - *answer = tor_dup_ip(addr); - } else if (!strcmp(question, "traffic/read")) { - tor_asprintf(answer, "%"PRIu64, (get_bytes_read())); - } else if (!strcmp(question, "traffic/written")) { - tor_asprintf(answer, "%"PRIu64, (get_bytes_written())); - } else if (!strcmp(question, "uptime")) { - long uptime_secs = get_uptime(); - tor_asprintf(answer, "%ld", uptime_secs); - } else if (!strcmp(question, "process/pid")) { - int myPid = -1; - -#ifdef _WIN32 - myPid = _getpid(); -#else - myPid = getpid(); -#endif - - tor_asprintf(answer, "%d", myPid); - } else if (!strcmp(question, "process/uid")) { -#ifdef _WIN32 - *answer = tor_strdup("-1"); -#else - int myUid = geteuid(); - tor_asprintf(answer, "%d", myUid); -#endif /* defined(_WIN32) */ - } else if (!strcmp(question, "process/user")) { -#ifdef _WIN32 - *answer = tor_strdup(""); -#else - int myUid = geteuid(); - const struct passwd *myPwEntry = tor_getpwuid(myUid); - - if (myPwEntry) { - *answer = tor_strdup(myPwEntry->pw_name); - } else { - *answer = tor_strdup(""); - } -#endif /* defined(_WIN32) */ - } else if (!strcmp(question, "process/descriptor-limit")) { - int max_fds = get_max_sockets(); - tor_asprintf(answer, "%d", max_fds); - } else if (!strcmp(question, "limits/max-mem-in-queues")) { - tor_asprintf(answer, "%"PRIu64, - (get_options()->MaxMemInQueues)); - } else if (!strcmp(question, "fingerprint")) { - crypto_pk_t *server_key; - if (!server_mode(get_options())) { - *errmsg = "Not running in server mode"; - return -1; - } - server_key = get_server_identity_key(); - *answer = tor_malloc(HEX_DIGEST_LEN+1); - crypto_pk_get_fingerprint(server_key, *answer, 0); - } - return 0; -} - -/** Awful hack: return a newly allocated string based on a routerinfo and - * (possibly) an extrainfo, sticking the read-history and write-history from - * <b>ei</b> into the resulting string. The thing you get back won't - * necessarily have a valid signature. - * - * New code should never use this; it's for backward compatibility. - * - * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might - * not be NUL-terminated. */ -static char * -munge_extrainfo_into_routerinfo(const char *ri_body, - const signed_descriptor_t *ri, - const signed_descriptor_t *ei) -{ - char *out = NULL, *outp; - int i; - const char *router_sig; - const char *ei_body = signed_descriptor_get_body(ei); - size_t ri_len = ri->signed_descriptor_len; - size_t ei_len = ei->signed_descriptor_len; - if (!ei_body) - goto bail; - - outp = out = tor_malloc(ri_len+ei_len+1); - if (!(router_sig = tor_memstr(ri_body, ri_len, "\nrouter-signature"))) - goto bail; - ++router_sig; - memcpy(out, ri_body, router_sig-ri_body); - outp += router_sig-ri_body; - - for (i=0; i < 2; ++i) { - const char *kwd = i ? "\nwrite-history " : "\nread-history "; - const char *cp, *eol; - if (!(cp = tor_memstr(ei_body, ei_len, kwd))) - continue; - ++cp; - if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body)))) - continue; - memcpy(outp, cp, eol-cp+1); - outp += eol-cp+1; - } - memcpy(outp, router_sig, ri_len - (router_sig-ri_body)); - *outp++ = '\0'; - tor_assert(outp-out < (int)(ri_len+ei_len+1)); - - return out; - bail: - tor_free(out); - return tor_strndup(ri_body, ri->signed_descriptor_len); -} - -/** Implementation helper for GETINFO: answers requests for information about - * which ports are bound. */ -static int -getinfo_helper_listeners(control_connection_t *control_conn, - const char *question, - char **answer, const char **errmsg) -{ - int type; - smartlist_t *res; - - (void)control_conn; - (void)errmsg; - - if (!strcmp(question, "net/listeners/or")) - type = CONN_TYPE_OR_LISTENER; - else if (!strcmp(question, "net/listeners/extor")) - type = CONN_TYPE_EXT_OR_LISTENER; - else if (!strcmp(question, "net/listeners/dir")) - type = CONN_TYPE_DIR_LISTENER; - else if (!strcmp(question, "net/listeners/socks")) - type = CONN_TYPE_AP_LISTENER; - else if (!strcmp(question, "net/listeners/trans")) - type = CONN_TYPE_AP_TRANS_LISTENER; - else if (!strcmp(question, "net/listeners/natd")) - type = CONN_TYPE_AP_NATD_LISTENER; - else if (!strcmp(question, "net/listeners/httptunnel")) - type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; - else if (!strcmp(question, "net/listeners/dns")) - type = CONN_TYPE_AP_DNS_LISTENER; - else if (!strcmp(question, "net/listeners/control")) - type = CONN_TYPE_CONTROL_LISTENER; - else - return 0; /* unknown key */ - - res = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { - struct sockaddr_storage ss; - socklen_t ss_len = sizeof(ss); - - if (conn->type != type || conn->marked_for_close || !SOCKET_OK(conn->s)) - continue; - - if (getsockname(conn->s, (struct sockaddr *)&ss, &ss_len) < 0) { - smartlist_add_asprintf(res, "%s:%d", conn->address, (int)conn->port); - } else { - char *tmp = tor_sockaddr_to_str((struct sockaddr *)&ss); - smartlist_add(res, esc_for_log(tmp)); - tor_free(tmp); - } - - } SMARTLIST_FOREACH_END(conn); - - *answer = smartlist_join_strings(res, " ", 0, NULL); - - SMARTLIST_FOREACH(res, char *, cp, tor_free(cp)); - smartlist_free(res); - return 0; -} - -/** Implementation helper for GETINFO: answers requests for information about - * the current time in both local and UTC forms. */ -STATIC int -getinfo_helper_current_time(control_connection_t *control_conn, - const char *question, - char **answer, const char **errmsg) -{ - (void)control_conn; - (void)errmsg; - - struct timeval now; - tor_gettimeofday(&now); - char timebuf[ISO_TIME_LEN+1]; - - if (!strcmp(question, "current-time/local")) - format_local_iso_time_nospace(timebuf, (time_t)now.tv_sec); - else if (!strcmp(question, "current-time/utc")) - format_iso_time_nospace(timebuf, (time_t)now.tv_sec); - else - return 0; - - *answer = tor_strdup(timebuf); - return 0; -} - -/** Implementation helper for GETINFO: knows the answers for questions about - * directory information. */ -STATIC int -getinfo_helper_dir(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - (void) control_conn; - if (!strcmpstart(question, "desc/id/")) { - const routerinfo_t *ri = NULL; - const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); - if (node) - ri = node->ri; - if (ri) { - const char *body = signed_descriptor_get_body(&ri->cache_info); - if (body) - *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); - } else if (! we_fetch_router_descriptors(get_options())) { - /* Descriptors won't be available, provide proper error */ - *errmsg = "We fetch microdescriptors, not router " - "descriptors. You'll need to use md/id/* " - "instead of desc/id/*."; - return 0; - } - } else if (!strcmpstart(question, "desc/name/")) { - const routerinfo_t *ri = NULL; - /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the - * warning goes to the user, not to the controller. */ - const node_t *node = - node_get_by_nickname(question+strlen("desc/name/"), 0); - if (node) - ri = node->ri; - if (ri) { - const char *body = signed_descriptor_get_body(&ri->cache_info); - if (body) - *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); - } else if (! we_fetch_router_descriptors(get_options())) { - /* Descriptors won't be available, provide proper error */ - *errmsg = "We fetch microdescriptors, not router " - "descriptors. You'll need to use md/name/* " - "instead of desc/name/*."; - return 0; - } - } else if (!strcmp(question, "desc/download-enabled")) { - int r = we_fetch_router_descriptors(get_options()); - tor_asprintf(answer, "%d", !!r); - } else if (!strcmp(question, "desc/all-recent")) { - routerlist_t *routerlist = router_get_routerlist(); - smartlist_t *sl = smartlist_new(); - if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, - { - const char *body = signed_descriptor_get_body(&ri->cache_info); - if (body) - smartlist_add(sl, - tor_strndup(body, ri->cache_info.signed_descriptor_len)); - }); - } - *answer = smartlist_join_strings(sl, "", 0, NULL); - SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); - smartlist_free(sl); - } else if (!strcmp(question, "desc/all-recent-extrainfo-hack")) { - /* XXXX Remove this once Torstat asks for extrainfos. */ - routerlist_t *routerlist = router_get_routerlist(); - smartlist_t *sl = smartlist_new(); - if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH_BEGIN(routerlist->routers, const routerinfo_t *, ri) { - const char *body = signed_descriptor_get_body(&ri->cache_info); - signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest( - ri->cache_info.extra_info_digest); - if (ei && body) { - smartlist_add(sl, munge_extrainfo_into_routerinfo(body, - &ri->cache_info, ei)); - } else if (body) { - smartlist_add(sl, - tor_strndup(body, ri->cache_info.signed_descriptor_len)); - } - } SMARTLIST_FOREACH_END(ri); - } - *answer = smartlist_join_strings(sl, "", 0, NULL); - SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); - smartlist_free(sl); - } else if (!strcmpstart(question, "hs/client/desc/id/")) { - hostname_type_t addr_type; - - question += strlen("hs/client/desc/id/"); - if (rend_valid_v2_service_id(question)) { - addr_type = ONION_V2_HOSTNAME; - } else if (hs_address_is_valid(question)) { - addr_type = ONION_V3_HOSTNAME; - } else { - *errmsg = "Invalid address"; - return -1; - } - - if (addr_type == ONION_V2_HOSTNAME) { - rend_cache_entry_t *e = NULL; - if (!rend_cache_lookup_entry(question, -1, &e)) { - /* Descriptor found in cache */ - *answer = tor_strdup(e->desc); - } else { - *errmsg = "Not found in cache"; - return -1; - } - } else { - ed25519_public_key_t service_pk; - const char *desc; - - /* The check before this if/else makes sure of this. */ - tor_assert(addr_type == ONION_V3_HOSTNAME); - - if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { - *errmsg = "Invalid v3 address"; - return -1; - } - - desc = hs_cache_lookup_encoded_as_client(&service_pk); - if (desc) { - *answer = tor_strdup(desc); - } else { - *errmsg = "Not found in cache"; - return -1; - } - } - } else if (!strcmpstart(question, "hs/service/desc/id/")) { - hostname_type_t addr_type; - - question += strlen("hs/service/desc/id/"); - if (rend_valid_v2_service_id(question)) { - addr_type = ONION_V2_HOSTNAME; - } else if (hs_address_is_valid(question)) { - addr_type = ONION_V3_HOSTNAME; - } else { - *errmsg = "Invalid address"; - return -1; - } - rend_cache_entry_t *e = NULL; - - if (addr_type == ONION_V2_HOSTNAME) { - if (!rend_cache_lookup_v2_desc_as_service(question, &e)) { - /* Descriptor found in cache */ - *answer = tor_strdup(e->desc); - } else { - *errmsg = "Not found in cache"; - return -1; - } - } else { - ed25519_public_key_t service_pk; - char *desc; - - /* The check before this if/else makes sure of this. */ - tor_assert(addr_type == ONION_V3_HOSTNAME); - - if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { - *errmsg = "Invalid v3 address"; - return -1; - } - - desc = hs_service_lookup_current_desc(&service_pk); - if (desc) { - /* Newly allocated string, we have ownership. */ - *answer = desc; - } else { - *errmsg = "Not found in cache"; - return -1; - } - } - } else if (!strcmp(question, "md/all")) { - const smartlist_t *nodes = nodelist_get_list(); - tor_assert(nodes); - - if (smartlist_len(nodes) == 0) { - *answer = tor_strdup(""); - return 0; - } - - smartlist_t *microdescs = smartlist_new(); - - SMARTLIST_FOREACH_BEGIN(nodes, node_t *, n) { - if (n->md && n->md->body) { - char *copy = tor_strndup(n->md->body, n->md->bodylen); - smartlist_add(microdescs, copy); - } - } SMARTLIST_FOREACH_END(n); - - *answer = smartlist_join_strings(microdescs, "", 0, NULL); - SMARTLIST_FOREACH(microdescs, char *, md, tor_free(md)); - smartlist_free(microdescs); - } else if (!strcmpstart(question, "md/id/")) { - const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); - const microdesc_t *md = NULL; - if (node) md = node->md; - if (md && md->body) { - *answer = tor_strndup(md->body, md->bodylen); - } - } else if (!strcmpstart(question, "md/name/")) { - /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the - * warning goes to the user, not to the controller. */ - const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); - /* XXXX duplicated code */ - const microdesc_t *md = NULL; - if (node) md = node->md; - if (md && md->body) { - *answer = tor_strndup(md->body, md->bodylen); - } - } else if (!strcmp(question, "md/download-enabled")) { - int r = we_fetch_microdescriptors(get_options()); - tor_asprintf(answer, "%d", !!r); - } else if (!strcmpstart(question, "desc-annotations/id/")) { - const routerinfo_t *ri = NULL; - const node_t *node = - node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); - if (node) - ri = node->ri; - if (ri) { - const char *annotations = - signed_descriptor_get_annotations(&ri->cache_info); - if (annotations) - *answer = tor_strndup(annotations, - ri->cache_info.annotations_len); - } - } else if (!strcmpstart(question, "dir/server/")) { - size_t answer_len = 0; - char *url = NULL; - smartlist_t *descs = smartlist_new(); - const char *msg; - int res; - char *cp; - tor_asprintf(&url, "/tor/%s", question+4); - res = dirserv_get_routerdescs(descs, url, &msg); - if (res) { - log_warn(LD_CONTROL, "getinfo '%s': %s", question, msg); - smartlist_free(descs); - tor_free(url); - *errmsg = msg; - return -1; - } - SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd, - answer_len += sd->signed_descriptor_len); - cp = *answer = tor_malloc(answer_len+1); - SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd, - { - memcpy(cp, signed_descriptor_get_body(sd), - sd->signed_descriptor_len); - cp += sd->signed_descriptor_len; - }); - *cp = '\0'; - tor_free(url); - smartlist_free(descs); - } else if (!strcmpstart(question, "dir/status/")) { - *answer = tor_strdup(""); - } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */ - if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) { - const cached_dir_t *consensus = dirserv_get_consensus("ns"); - if (consensus) - *answer = tor_strdup(consensus->dir); - } - if (!*answer) { /* try loading it from disk */ - *answer = networkstatus_read_cached_consensus("ns"); - if (!*answer) { /* generate an error */ - *errmsg = "Could not open cached consensus. " - "Make sure FetchUselessDescriptors is set to 1."; - return -1; - } - } - } else if (!strcmp(question, "network-status")) { /* v1 */ - static int network_status_warned = 0; - if (!network_status_warned) { - log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will " - "go away in a future version of Tor."); - network_status_warned = 1; - } - routerlist_t *routerlist = router_get_routerlist(); - if (!routerlist || !routerlist->routers || - list_server_status_v1(routerlist->routers, answer, 1) < 0) { - return -1; - } - } else if (!strcmpstart(question, "extra-info/digest/")) { - question += strlen("extra-info/digest/"); - if (strlen(question) == HEX_DIGEST_LEN) { - char d[DIGEST_LEN]; - signed_descriptor_t *sd = NULL; - if (base16_decode(d, sizeof(d), question, strlen(question)) - == sizeof(d)) { - /* XXXX this test should move into extrainfo_get_by_descriptor_digest, - * but I don't want to risk affecting other parts of the code, - * especially since the rules for using our own extrainfo (including - * when it might be freed) are different from those for using one - * we have downloaded. */ - if (router_extrainfo_digest_is_me(d)) - sd = &(router_get_my_extrainfo()->cache_info); - else - sd = extrainfo_get_by_descriptor_digest(d); - } - if (sd) { - const char *body = signed_descriptor_get_body(sd); - if (body) - *answer = tor_strndup(body, sd->signed_descriptor_len); - } - } - } - - return 0; -} - -/** Given a smartlist of 20-byte digests, return a newly allocated string - * containing each of those digests in order, formatted in HEX, and terminated - * with a newline. */ -static char * -digest_list_to_string(const smartlist_t *sl) -{ - int len; - char *result, *s; - - /* Allow for newlines, and a \0 at the end */ - len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1; - result = tor_malloc_zero(len); - - s = result; - SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) { - base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN); - s[HEX_DIGEST_LEN] = '\n'; - s += HEX_DIGEST_LEN + 1; - } SMARTLIST_FOREACH_END(digest); - *s = '\0'; - - return result; -} - -/** Turn a download_status_t into a human-readable description in a newly - * allocated string. The format is specified in control-spec.txt, under - * the documentation for "GETINFO download/..." . */ -static char * -download_status_to_string(const download_status_t *dl) -{ - char *rv = NULL; - char tbuf[ISO_TIME_LEN+1]; - const char *schedule_str, *want_authority_str; - const char *increment_on_str, *backoff_str; - - if (dl) { - /* Get some substrings of the eventual output ready */ - format_iso_time(tbuf, download_status_get_next_attempt_at(dl)); - - switch (dl->schedule) { - case DL_SCHED_GENERIC: - schedule_str = "DL_SCHED_GENERIC"; - break; - case DL_SCHED_CONSENSUS: - schedule_str = "DL_SCHED_CONSENSUS"; - break; - case DL_SCHED_BRIDGE: - schedule_str = "DL_SCHED_BRIDGE"; - break; - default: - schedule_str = "unknown"; - break; - } - - switch (dl->want_authority) { - case DL_WANT_ANY_DIRSERVER: - want_authority_str = "DL_WANT_ANY_DIRSERVER"; - break; - case DL_WANT_AUTHORITY: - want_authority_str = "DL_WANT_AUTHORITY"; - break; - default: - want_authority_str = "unknown"; - break; - } - - switch (dl->increment_on) { - case DL_SCHED_INCREMENT_FAILURE: - increment_on_str = "DL_SCHED_INCREMENT_FAILURE"; - break; - case DL_SCHED_INCREMENT_ATTEMPT: - increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT"; - break; - default: - increment_on_str = "unknown"; - break; - } - - backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL"; - - /* Now assemble them */ - tor_asprintf(&rv, - "next-attempt-at %s\n" - "n-download-failures %u\n" - "n-download-attempts %u\n" - "schedule %s\n" - "want-authority %s\n" - "increment-on %s\n" - "backoff %s\n" - "last-backoff-position %u\n" - "last-delay-used %d\n", - tbuf, - dl->n_download_failures, - dl->n_download_attempts, - schedule_str, - want_authority_str, - increment_on_str, - backoff_str, - dl->last_backoff_position, - dl->last_delay_used); - } - - return rv; -} - -/** Handle the consensus download cases for getinfo_helper_downloads() */ -STATIC void -getinfo_helper_downloads_networkstatus(const char *flavor, - download_status_t **dl_to_emit, - const char **errmsg) -{ - /* - * We get the one for the current bootstrapped status by default, or - * take an extra /bootstrap or /running suffix - */ - if (strcmp(flavor, "ns") == 0) { - *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS); - } else if (strcmp(flavor, "ns/bootstrap") == 0) { - *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS); - } else if (strcmp(flavor, "ns/running") == 0 ) { - *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS); - } else if (strcmp(flavor, "microdesc") == 0) { - *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC); - } else if (strcmp(flavor, "microdesc/bootstrap") == 0) { - *dl_to_emit = - networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC); - } else if (strcmp(flavor, "microdesc/running") == 0) { - *dl_to_emit = - networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC); - } else { - *errmsg = "Unknown flavor"; - } -} - -/** Handle the cert download cases for getinfo_helper_downloads() */ -STATIC void -getinfo_helper_downloads_cert(const char *fp_sk_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg) -{ - const char *sk_req; - char id_digest[DIGEST_LEN]; - char sk_digest[DIGEST_LEN]; - - /* - * We have to handle four cases; fp_sk_req is the request with - * a prefix of "downloads/cert/" snipped off. - * - * Case 1: fp_sk_req = "fps" - * - We should emit a digest_list with a list of all the identity - * fingerprints that can be queried for certificate download status; - * get it by calling list_authority_ids_with_downloads(). - * - * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp - * - We want the default certificate for this identity fingerprint's - * download status; this is the download we get from URLs starting - * in /fp/ on the directory server. We can get it with - * id_only_download_status_for_authority_id(). - * - * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp - * - We want a list of all signing key digests for this identity - * fingerprint which can be queried for certificate download status. - * Get it with list_sk_digests_for_authority_id(). - * - * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and - * signing key digest sk - * - We want the download status for the certificate for this specific - * signing key and fingerprint. These correspond to the ones we get - * from URLs starting in /fp-sk/ on the directory server. Get it with - * list_sk_digests_for_authority_id(). - */ - - if (strcmp(fp_sk_req, "fps") == 0) { - *digest_list = list_authority_ids_with_downloads(); - if (!(*digest_list)) { - *errmsg = "Failed to get list of authority identity digests (!)"; - } - } else if (!strcmpstart(fp_sk_req, "fp/")) { - fp_sk_req += strlen("fp/"); - /* Okay, look for another / to tell the fp from fp-sk cases */ - sk_req = strchr(fp_sk_req, '/'); - if (sk_req) { - /* okay, split it here and try to parse <fp> */ - if (base16_decode(id_digest, DIGEST_LEN, - fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) { - /* Skip past the '/' */ - ++sk_req; - if (strcmp(sk_req, "sks") == 0) { - /* We're asking for the list of signing key fingerprints */ - *digest_list = list_sk_digests_for_authority_id(id_digest); - if (!(*digest_list)) { - *errmsg = "Failed to get list of signing key digests for this " - "authority identity digest"; - } - } else { - /* We've got a signing key digest */ - if (base16_decode(sk_digest, DIGEST_LEN, - sk_req, strlen(sk_req)) == DIGEST_LEN) { - *dl_to_emit = - download_status_for_authority_id_and_sk(id_digest, sk_digest); - if (!(*dl_to_emit)) { - *errmsg = "Failed to get download status for this identity/" - "signing key digest pair"; - } - } else { - *errmsg = "That didn't look like a signing key digest"; - } - } - } else { - *errmsg = "That didn't look like an identity digest"; - } - } else { - /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */ - if (strlen(fp_sk_req) == HEX_DIGEST_LEN) { - if (base16_decode(id_digest, DIGEST_LEN, - fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) { - *dl_to_emit = id_only_download_status_for_authority_id(id_digest); - if (!(*dl_to_emit)) { - *errmsg = "Failed to get download status for this authority " - "identity digest"; - } - } else { - *errmsg = "That didn't look like a digest"; - } - } else { - *errmsg = "That didn't look like a digest"; - } - } - } else { - *errmsg = "Unknown certificate download status query"; - } -} - -/** Handle the routerdesc download cases for getinfo_helper_downloads() */ -STATIC void -getinfo_helper_downloads_desc(const char *desc_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg) -{ - char desc_digest[DIGEST_LEN]; - /* - * Two cases to handle here: - * - * Case 1: desc_req = "descs" - * - Emit a list of all router descriptor digests, which we get by - * calling router_get_descriptor_digests(); this can return NULL - * if we have no current ns-flavor consensus. - * - * Case 2: desc_req = <fp> - * - Check on the specified fingerprint and emit its download_status_t - * using router_get_dl_status_by_descriptor_digest(). - */ - - if (strcmp(desc_req, "descs") == 0) { - *digest_list = router_get_descriptor_digests(); - if (!(*digest_list)) { - *errmsg = "We don't seem to have a networkstatus-flavored consensus"; - } - /* - * Microdescs don't use the download_status_t mechanism, so we don't - * answer queries about their downloads here; see microdesc.c. - */ - } else if (strlen(desc_req) == HEX_DIGEST_LEN) { - if (base16_decode(desc_digest, DIGEST_LEN, - desc_req, strlen(desc_req)) == DIGEST_LEN) { - /* Okay we got a digest-shaped thing; try asking for it */ - *dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest); - if (!(*dl_to_emit)) { - *errmsg = "No such descriptor digest found"; - } - } else { - *errmsg = "That didn't look like a digest"; - } - } else { - *errmsg = "Unknown router descriptor download status query"; - } -} - -/** Handle the bridge download cases for getinfo_helper_downloads() */ -STATIC void -getinfo_helper_downloads_bridge(const char *bridge_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg) -{ - char bridge_digest[DIGEST_LEN]; - /* - * Two cases to handle here: - * - * Case 1: bridge_req = "bridges" - * - Emit a list of all bridge identity digests, which we get by - * calling list_bridge_identities(); this can return NULL if we are - * not using bridges. - * - * Case 2: bridge_req = <fp> - * - Check on the specified fingerprint and emit its download_status_t - * using get_bridge_dl_status_by_id(). - */ - - if (strcmp(bridge_req, "bridges") == 0) { - *digest_list = list_bridge_identities(); - if (!(*digest_list)) { - *errmsg = "We don't seem to be using bridges"; - } - } else if (strlen(bridge_req) == HEX_DIGEST_LEN) { - if (base16_decode(bridge_digest, DIGEST_LEN, - bridge_req, strlen(bridge_req)) == DIGEST_LEN) { - /* Okay we got a digest-shaped thing; try asking for it */ - *dl_to_emit = get_bridge_dl_status_by_id(bridge_digest); - if (!(*dl_to_emit)) { - *errmsg = "No such bridge identity digest found"; - } - } else { - *errmsg = "That didn't look like a digest"; - } - } else { - *errmsg = "Unknown bridge descriptor download status query"; - } -} - -/** Implementation helper for GETINFO: knows the answers for questions about - * download status information. */ -STATIC int -getinfo_helper_downloads(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - download_status_t *dl_to_emit = NULL; - smartlist_t *digest_list = NULL; - - /* Assert args are sane */ - tor_assert(control_conn != NULL); - tor_assert(question != NULL); - tor_assert(answer != NULL); - tor_assert(errmsg != NULL); - - /* We check for this later to see if we should supply a default */ - *errmsg = NULL; - - /* Are we after networkstatus downloads? */ - if (!strcmpstart(question, "downloads/networkstatus/")) { - getinfo_helper_downloads_networkstatus( - question + strlen("downloads/networkstatus/"), - &dl_to_emit, errmsg); - /* Certificates? */ - } else if (!strcmpstart(question, "downloads/cert/")) { - getinfo_helper_downloads_cert( - question + strlen("downloads/cert/"), - &dl_to_emit, &digest_list, errmsg); - /* Router descriptors? */ - } else if (!strcmpstart(question, "downloads/desc/")) { - getinfo_helper_downloads_desc( - question + strlen("downloads/desc/"), - &dl_to_emit, &digest_list, errmsg); - /* Bridge descriptors? */ - } else if (!strcmpstart(question, "downloads/bridge/")) { - getinfo_helper_downloads_bridge( - question + strlen("downloads/bridge/"), - &dl_to_emit, &digest_list, errmsg); - } else { - *errmsg = "Unknown download status query"; - } - - if (dl_to_emit) { - *answer = download_status_to_string(dl_to_emit); - - return 0; - } else if (digest_list) { - *answer = digest_list_to_string(digest_list); - SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s)); - smartlist_free(digest_list); - - return 0; - } else { - if (!(*errmsg)) { - *errmsg = "Unknown error"; - } - - return -1; - } -} - -/** Allocate and return a description of <b>circ</b>'s current status, - * including its path (if any). */ -static char * -circuit_describe_status_for_controller(origin_circuit_t *circ) -{ - char *rv; - smartlist_t *descparts = smartlist_new(); - - { - char *vpath = circuit_list_path_for_controller(circ); - if (*vpath) { - smartlist_add(descparts, vpath); - } else { - tor_free(vpath); /* empty path; don't put an extra space in the result */ - } - } - - { - cpath_build_state_t *build_state = circ->build_state; - smartlist_t *flaglist = smartlist_new(); - char *flaglist_joined; - - if (build_state->onehop_tunnel) - smartlist_add(flaglist, (void *)"ONEHOP_TUNNEL"); - if (build_state->is_internal) - smartlist_add(flaglist, (void *)"IS_INTERNAL"); - if (build_state->need_capacity) - smartlist_add(flaglist, (void *)"NEED_CAPACITY"); - if (build_state->need_uptime) - smartlist_add(flaglist, (void *)"NEED_UPTIME"); - - /* Only emit a BUILD_FLAGS argument if it will have a non-empty value. */ - if (smartlist_len(flaglist)) { - flaglist_joined = smartlist_join_strings(flaglist, ",", 0, NULL); - - smartlist_add_asprintf(descparts, "BUILD_FLAGS=%s", flaglist_joined); - - tor_free(flaglist_joined); - } - - smartlist_free(flaglist); - } - - smartlist_add_asprintf(descparts, "PURPOSE=%s", - circuit_purpose_to_controller_string(circ->base_.purpose)); - - { - const char *hs_state = - circuit_purpose_to_controller_hs_state_string(circ->base_.purpose); - - if (hs_state != NULL) { - smartlist_add_asprintf(descparts, "HS_STATE=%s", hs_state); - } - } - - if (circ->rend_data != NULL || circ->hs_ident != NULL) { - char addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; - const char *onion_address; - if (circ->rend_data) { - onion_address = rend_data_get_address(circ->rend_data); - } else { - hs_build_address(&circ->hs_ident->identity_pk, HS_VERSION_THREE, addr); - onion_address = addr; - } - smartlist_add_asprintf(descparts, "REND_QUERY=%s", onion_address); - } - - { - char tbuf[ISO_TIME_USEC_LEN+1]; - format_iso_time_nospace_usec(tbuf, &circ->base_.timestamp_created); - - smartlist_add_asprintf(descparts, "TIME_CREATED=%s", tbuf); - } - - // Show username and/or password if available. - if (circ->socks_username_len > 0) { - char* socks_username_escaped = esc_for_log_len(circ->socks_username, - (size_t) circ->socks_username_len); - smartlist_add_asprintf(descparts, "SOCKS_USERNAME=%s", - socks_username_escaped); - tor_free(socks_username_escaped); - } - if (circ->socks_password_len > 0) { - char* socks_password_escaped = esc_for_log_len(circ->socks_password, - (size_t) circ->socks_password_len); - smartlist_add_asprintf(descparts, "SOCKS_PASSWORD=%s", - socks_password_escaped); - tor_free(socks_password_escaped); - } - - rv = smartlist_join_strings(descparts, " ", 0, NULL); - - SMARTLIST_FOREACH(descparts, char *, cp, tor_free(cp)); - smartlist_free(descparts); - - return rv; -} - -/** Implementation helper for GETINFO: knows how to generate summaries of the - * current states of things we send events about. */ -static int -getinfo_helper_events(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - const or_options_t *options = get_options(); - (void) control_conn; - if (!strcmp(question, "circuit-status")) { - smartlist_t *status = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) { - origin_circuit_t *circ; - char *circdesc; - const char *state; - if (! CIRCUIT_IS_ORIGIN(circ_) || circ_->marked_for_close) - continue; - circ = TO_ORIGIN_CIRCUIT(circ_); - - if (circ->base_.state == CIRCUIT_STATE_OPEN) - state = "BUILT"; - else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) - state = "GUARD_WAIT"; - else if (circ->cpath) - state = "EXTENDED"; - else - state = "LAUNCHED"; - - circdesc = circuit_describe_status_for_controller(circ); - - smartlist_add_asprintf(status, "%lu %s%s%s", - (unsigned long)circ->global_identifier, - state, *circdesc ? " " : "", circdesc); - tor_free(circdesc); - } - SMARTLIST_FOREACH_END(circ_); - *answer = smartlist_join_strings(status, "\r\n", 0, NULL); - SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); - smartlist_free(status); - } else if (!strcmp(question, "stream-status")) { - smartlist_t *conns = get_connection_array(); - smartlist_t *status = smartlist_new(); - char buf[256]; - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { - const char *state; - entry_connection_t *conn; - circuit_t *circ; - origin_circuit_t *origin_circ = NULL; - if (base_conn->type != CONN_TYPE_AP || - base_conn->marked_for_close || - base_conn->state == AP_CONN_STATE_SOCKS_WAIT || - base_conn->state == AP_CONN_STATE_NATD_WAIT) - continue; - conn = TO_ENTRY_CONN(base_conn); - switch (base_conn->state) - { - case AP_CONN_STATE_CONTROLLER_WAIT: - case AP_CONN_STATE_CIRCUIT_WAIT: - if (conn->socks_request && - SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)) - state = "NEWRESOLVE"; - else - state = "NEW"; - break; - case AP_CONN_STATE_RENDDESC_WAIT: - case AP_CONN_STATE_CONNECT_WAIT: - state = "SENTCONNECT"; break; - case AP_CONN_STATE_RESOLVE_WAIT: - state = "SENTRESOLVE"; break; - case AP_CONN_STATE_OPEN: - state = "SUCCEEDED"; break; - default: - log_warn(LD_BUG, "Asked for stream in unknown state %d", - base_conn->state); - continue; - } - circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); - if (circ && CIRCUIT_IS_ORIGIN(circ)) - origin_circ = TO_ORIGIN_CIRCUIT(circ); - write_stream_target_to_buf(conn, buf, sizeof(buf)); - smartlist_add_asprintf(status, "%lu %s %lu %s", - (unsigned long) base_conn->global_identifier,state, - origin_circ? - (unsigned long)origin_circ->global_identifier : 0ul, - buf); - } SMARTLIST_FOREACH_END(base_conn); - *answer = smartlist_join_strings(status, "\r\n", 0, NULL); - SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); - smartlist_free(status); - } else if (!strcmp(question, "orconn-status")) { - smartlist_t *conns = get_connection_array(); - smartlist_t *status = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { - const char *state; - char name[128]; - or_connection_t *conn; - if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close) - continue; - conn = TO_OR_CONN(base_conn); - if (conn->base_.state == OR_CONN_STATE_OPEN) - state = "CONNECTED"; - else if (conn->nickname) - state = "LAUNCHED"; - else - state = "NEW"; - orconn_target_get_name(name, sizeof(name), conn); - smartlist_add_asprintf(status, "%s %s", name, state); - } SMARTLIST_FOREACH_END(base_conn); - *answer = smartlist_join_strings(status, "\r\n", 0, NULL); - SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); - smartlist_free(status); - } else if (!strcmpstart(question, "address-mappings/")) { - time_t min_e, max_e; - smartlist_t *mappings; - question += strlen("address-mappings/"); - if (!strcmp(question, "all")) { - min_e = 0; max_e = TIME_MAX; - } else if (!strcmp(question, "cache")) { - min_e = 2; max_e = TIME_MAX; - } else if (!strcmp(question, "config")) { - min_e = 0; max_e = 0; - } else if (!strcmp(question, "control")) { - min_e = 1; max_e = 1; - } else { - return 0; - } - mappings = smartlist_new(); - addressmap_get_mappings(mappings, min_e, max_e, 1); - *answer = smartlist_join_strings(mappings, "\r\n", 0, NULL); - SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp)); - smartlist_free(mappings); - } else if (!strcmpstart(question, "status/")) { - /* Note that status/ is not a catch-all for events; there's only supposed - * to be a status GETINFO if there's a corresponding STATUS event. */ - if (!strcmp(question, "status/circuit-established")) { - *answer = tor_strdup(have_completed_a_circuit() ? "1" : "0"); - } else if (!strcmp(question, "status/enough-dir-info")) { - *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0"); - } else if (!strcmp(question, "status/good-server-descriptor") || - !strcmp(question, "status/accepted-server-descriptor")) { - /* They're equivalent for now, until we can figure out how to make - * good-server-descriptor be what we want. See comment in - * control-spec.txt. */ - *answer = tor_strdup(directories_have_accepted_server_descriptor() - ? "1" : "0"); - } else if (!strcmp(question, "status/reachability-succeeded/or")) { - *answer = tor_strdup(check_whether_orport_reachable(options) ? - "1" : "0"); - } else if (!strcmp(question, "status/reachability-succeeded/dir")) { - *answer = tor_strdup(check_whether_dirport_reachable(options) ? - "1" : "0"); - } else if (!strcmp(question, "status/reachability-succeeded")) { - tor_asprintf(answer, "OR=%d DIR=%d", - check_whether_orport_reachable(options) ? 1 : 0, - check_whether_dirport_reachable(options) ? 1 : 0); - } else if (!strcmp(question, "status/bootstrap-phase")) { - *answer = tor_strdup(last_sent_bootstrap_message); - } else if (!strcmpstart(question, "status/version/")) { - int is_server = server_mode(options); - networkstatus_t *c = networkstatus_get_latest_consensus(); - version_status_t status; - const char *recommended; - if (c) { - recommended = is_server ? c->server_versions : c->client_versions; - status = tor_version_is_obsolete(VERSION, recommended); - } else { - recommended = "?"; - status = VS_UNKNOWN; - } - - if (!strcmp(question, "status/version/recommended")) { - *answer = tor_strdup(recommended); - return 0; - } - if (!strcmp(question, "status/version/current")) { - switch (status) - { - case VS_RECOMMENDED: *answer = tor_strdup("recommended"); break; - case VS_OLD: *answer = tor_strdup("obsolete"); break; - case VS_NEW: *answer = tor_strdup("new"); break; - case VS_NEW_IN_SERIES: *answer = tor_strdup("new in series"); break; - case VS_UNRECOMMENDED: *answer = tor_strdup("unrecommended"); break; - case VS_EMPTY: *answer = tor_strdup("none recommended"); break; - case VS_UNKNOWN: *answer = tor_strdup("unknown"); break; - default: tor_fragile_assert(); - } - } else if (!strcmp(question, "status/version/num-versioning") || - !strcmp(question, "status/version/num-concurring")) { - tor_asprintf(answer, "%d", get_n_authorities(V3_DIRINFO)); - log_warn(LD_GENERAL, "%s is deprecated; it no longer gives useful " - "information", question); - } - } else if (!strcmp(question, "status/clients-seen")) { - char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL)); - if (!bridge_stats) { - *errmsg = "No bridge-client stats available"; - return -1; - } - *answer = bridge_stats; - } else if (!strcmp(question, "status/fresh-relay-descs")) { - if (!server_mode(options)) { - *errmsg = "Only relays have descriptors"; - return -1; - } - routerinfo_t *r; - extrainfo_t *e; - if (router_build_fresh_descriptor(&r, &e) < 0) { - *errmsg = "Error generating descriptor"; - return -1; - } - size_t size = r->cache_info.signed_descriptor_len + 1; - if (e) { - size += e->cache_info.signed_descriptor_len + 1; - } - tor_assert(r->cache_info.signed_descriptor_len); - char *descs = tor_malloc(size); - char *cp = descs; - memcpy(cp, signed_descriptor_get_body(&r->cache_info), - r->cache_info.signed_descriptor_len); - cp += r->cache_info.signed_descriptor_len - 1; - if (e) { - if (cp[0] == '\0') { - cp[0] = '\n'; - } else if (cp[0] != '\n') { - cp[1] = '\n'; - cp++; - } - memcpy(cp, signed_descriptor_get_body(&e->cache_info), - e->cache_info.signed_descriptor_len); - cp += e->cache_info.signed_descriptor_len - 1; - } - if (cp[0] == '\n') { - cp[0] = '\0'; - } else if (cp[0] != '\0') { - cp[1] = '\0'; - } - *answer = descs; - routerinfo_free(r); - extrainfo_free(e); - } else { - return 0; - } - } - return 0; -} - -/** Implementation helper for GETINFO: knows how to enumerate hidden services - * created via the control port. */ -STATIC int -getinfo_helper_onions(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - smartlist_t *onion_list = NULL; - (void) errmsg; /* no errors from this method */ - - if (control_conn && !strcmp(question, "onions/current")) { - onion_list = control_conn->ephemeral_onion_services; - } else if (!strcmp(question, "onions/detached")) { - onion_list = detached_onion_services; - } else { - return 0; - } - if (!onion_list || smartlist_len(onion_list) == 0) { - if (answer) { - *answer = tor_strdup(""); - } - } else { - if (answer) { - *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL); - } - } - - return 0; -} - -/** Implementation helper for GETINFO: answers queries about network - * liveness. */ -static int -getinfo_helper_liveness(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - (void)control_conn; - (void)errmsg; - if (strcmp(question, "network-liveness") == 0) { - if (get_cached_network_liveness()) { - *answer = tor_strdup("up"); - } else { - *answer = tor_strdup("down"); - } - } - - return 0; -} - -/** Implementation helper for GETINFO: answers queries about shared random - * value. */ -static int -getinfo_helper_sr(control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg) -{ - (void) control_conn; - (void) errmsg; - - if (!strcmp(question, "sr/current")) { - *answer = sr_get_current_for_control(); - } else if (!strcmp(question, "sr/previous")) { - *answer = sr_get_previous_for_control(); - } - /* Else statement here is unrecognized key so do nothing. */ - - return 0; -} - -/** Callback function for GETINFO: on a given control connection, try to - * answer the question <b>q</b> and store the newly-allocated answer in - * *<b>a</b>. If an internal error occurs, return -1 and optionally set - * *<b>error_out</b> to point to an error message to be delivered to the - * controller. On success, _or if the key is not recognized_, return 0. Do not - * set <b>a</b> if the key is not recognized but you may set <b>error_out</b> - * to improve the error message. - */ -typedef int (*getinfo_helper_t)(control_connection_t *, - const char *q, char **a, - const char **error_out); - -/** A single item for the GETINFO question-to-answer-function table. */ -typedef struct getinfo_item_t { - const char *varname; /**< The value (or prefix) of the question. */ - getinfo_helper_t fn; /**< The function that knows the answer: NULL if - * this entry is documentation-only. */ - const char *desc; /**< Description of the variable. */ - int is_prefix; /** Must varname match exactly, or must it be a prefix? */ -} getinfo_item_t; - -#define ITEM(name, fn, desc) { name, getinfo_helper_##fn, desc, 0 } -#define PREFIX(name, fn, desc) { name, getinfo_helper_##fn, desc, 1 } -#define DOC(name, desc) { name, NULL, desc, 0 } - -/** Table mapping questions accepted by GETINFO to the functions that know how - * to answer them. */ -static const getinfo_item_t getinfo_items[] = { - ITEM("version", misc, "The current version of Tor."), - ITEM("bw-event-cache", misc, "Cached BW events for a short interval."), - ITEM("config-file", misc, "Current location of the \"torrc\" file."), - ITEM("config-defaults-file", misc, "Current location of the defaults file."), - ITEM("config-text", misc, - "Return the string that would be written by a saveconf command."), - ITEM("config-can-saveconf", misc, - "Is it possible to save the configuration to the \"torrc\" file?"), - ITEM("accounting/bytes", accounting, - "Number of bytes read/written so far in the accounting interval."), - ITEM("accounting/bytes-left", accounting, - "Number of bytes left to write/read so far in the accounting interval."), - ITEM("accounting/enabled", accounting, "Is accounting currently enabled?"), - ITEM("accounting/hibernating", accounting, "Are we hibernating or awake?"), - ITEM("accounting/interval-start", accounting, - "Time when the accounting period starts."), - ITEM("accounting/interval-end", accounting, - "Time when the accounting period ends."), - ITEM("accounting/interval-wake", accounting, - "Time to wake up in this accounting period."), - ITEM("helper-nodes", entry_guards, NULL), /* deprecated */ - ITEM("entry-guards", entry_guards, - "Which nodes are we using as entry guards?"), - ITEM("fingerprint", misc, NULL), - PREFIX("config/", config, "Current configuration values."), - DOC("config/names", - "List of configuration options, types, and documentation."), - DOC("config/defaults", - "List of default values for configuration options. " - "See also config/names"), - PREFIX("current-time/", current_time, "Current time."), - DOC("current-time/local", "Current time on the local system."), - DOC("current-time/utc", "Current UTC time."), - PREFIX("downloads/networkstatus/", downloads, - "Download statuses for networkstatus objects"), - DOC("downloads/networkstatus/ns", - "Download status for current-mode networkstatus download"), - DOC("downloads/networkstatus/ns/bootstrap", - "Download status for bootstrap-time networkstatus download"), - DOC("downloads/networkstatus/ns/running", - "Download status for run-time networkstatus download"), - DOC("downloads/networkstatus/microdesc", - "Download status for current-mode microdesc download"), - DOC("downloads/networkstatus/microdesc/bootstrap", - "Download status for bootstrap-time microdesc download"), - DOC("downloads/networkstatus/microdesc/running", - "Download status for run-time microdesc download"), - PREFIX("downloads/cert/", downloads, - "Download statuses for certificates, by id fingerprint and " - "signing key"), - DOC("downloads/cert/fps", - "List of authority fingerprints for which any download statuses " - "exist"), - DOC("downloads/cert/fp/<fp>", - "Download status for <fp> with the default signing key; corresponds " - "to /fp/ URLs on directory server."), - DOC("downloads/cert/fp/<fp>/sks", - "List of signing keys for which specific download statuses are " - "available for this id fingerprint"), - DOC("downloads/cert/fp/<fp>/<sk>", - "Download status for <fp> with signing key <sk>; corresponds " - "to /fp-sk/ URLs on directory server."), - PREFIX("downloads/desc/", downloads, - "Download statuses for router descriptors, by descriptor digest"), - DOC("downloads/desc/descs", - "Return a list of known router descriptor digests"), - DOC("downloads/desc/<desc>", - "Return a download status for a given descriptor digest"), - PREFIX("downloads/bridge/", downloads, - "Download statuses for bridge descriptors, by bridge identity " - "digest"), - DOC("downloads/bridge/bridges", - "Return a list of configured bridge identity digests with download " - "statuses"), - DOC("downloads/bridge/<desc>", - "Return a download status for a given bridge identity digest"), - ITEM("info/names", misc, - "List of GETINFO options, types, and documentation."), - ITEM("events/names", misc, - "Events that the controller can ask for with SETEVENTS."), - ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"), - ITEM("features/names", misc, "What arguments can USEFEATURE take?"), - PREFIX("desc/id/", dir, "Router descriptors by ID."), - PREFIX("desc/name/", dir, "Router descriptors by nickname."), - ITEM("desc/all-recent", dir, - "All non-expired, non-superseded router descriptors."), - ITEM("desc/download-enabled", dir, - "Do we try to download router descriptors?"), - ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */ - ITEM("md/all", dir, "All known microdescriptors."), - PREFIX("md/id/", dir, "Microdescriptors by ID"), - PREFIX("md/name/", dir, "Microdescriptors by name"), - ITEM("md/download-enabled", dir, - "Do we try to download microdescriptors?"), - PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), - PREFIX("hs/client/desc/id", dir, - "Hidden Service descriptor in client's cache by onion."), - PREFIX("hs/service/desc/id/", dir, - "Hidden Service descriptor in services's cache by onion."), - PREFIX("net/listeners/", listeners, "Bound addresses by type"), - ITEM("ns/all", networkstatus, - "Brief summary of router status (v2 directory format)"), - PREFIX("ns/id/", networkstatus, - "Brief summary of router status by ID (v2 directory format)."), - PREFIX("ns/name/", networkstatus, - "Brief summary of router status by nickname (v2 directory format)."), - PREFIX("ns/purpose/", networkstatus, - "Brief summary of router status by purpose (v2 directory format)."), - PREFIX("consensus/", networkstatus, - "Information about and from the ns consensus."), - ITEM("network-status", dir, - "Brief summary of router status (v1 directory format)"), - ITEM("network-liveness", liveness, - "Current opinion on whether the network is live"), - ITEM("circuit-status", events, "List of current circuits originating here."), - ITEM("stream-status", events,"List of current streams."), - ITEM("orconn-status", events, "A list of current OR connections."), - ITEM("dormant", misc, - "Is Tor dormant (not building circuits because it's idle)?"), - PREFIX("address-mappings/", events, NULL), - DOC("address-mappings/all", "Current address mappings."), - DOC("address-mappings/cache", "Current cached DNS replies."), - DOC("address-mappings/config", - "Current address mappings from configuration."), - DOC("address-mappings/control", "Current address mappings from controller."), - PREFIX("status/", events, NULL), - DOC("status/circuit-established", - "Whether we think client functionality is working."), - DOC("status/enough-dir-info", - "Whether we have enough up-to-date directory information to build " - "circuits."), - DOC("status/bootstrap-phase", - "The last bootstrap phase status event that Tor sent."), - DOC("status/clients-seen", - "Breakdown of client countries seen by a bridge."), - DOC("status/fresh-relay-descs", - "A fresh relay/ei descriptor pair for Tor's current state. Not stored."), - DOC("status/version/recommended", "List of currently recommended versions."), - DOC("status/version/current", "Status of the current version."), - DOC("status/version/num-versioning", "Number of versioning authorities."), - DOC("status/version/num-concurring", - "Number of versioning authorities agreeing on the status of the " - "current version"), - ITEM("address", misc, "IP address of this Tor host, if we can guess it."), - ITEM("traffic/read", misc,"Bytes read since the process was started."), - ITEM("traffic/written", misc, - "Bytes written since the process was started."), - ITEM("uptime", misc, "Uptime of the Tor daemon in seconds."), - ITEM("process/pid", misc, "Process id belonging to the main tor process."), - ITEM("process/uid", misc, "User id running the tor process."), - ITEM("process/user", misc, - "Username under which the tor process is running."), - ITEM("process/descriptor-limit", misc, "File descriptor limit."), - ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"), - PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."), - PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."), - PREFIX("dir/status/", dir, - "v2 networkstatus docs as retrieved from a DirPort."), - ITEM("dir/status-vote/current/consensus", dir, - "v3 Networkstatus consensus as retrieved from a DirPort."), - ITEM("exit-policy/default", policies, - "The default value appended to the configured exit policy."), - ITEM("exit-policy/reject-private/default", policies, - "The default rules appended to the configured exit policy by" - " ExitPolicyRejectPrivate."), - ITEM("exit-policy/reject-private/relay", policies, - "The relay-specific rules appended to the configured exit policy by" - " ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."), - ITEM("exit-policy/full", policies, "The entire exit policy of onion router"), - ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), - ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), - PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"), - ITEM("onions/current", onions, - "Onion services owned by the current control connection."), - ITEM("onions/detached", onions, - "Onion services detached from the control connection."), - ITEM("sr/current", sr, "Get current shared random value."), - ITEM("sr/previous", sr, "Get previous shared random value."), - { NULL, NULL, NULL, 0 } -}; - -/** Allocate and return a list of recognized GETINFO options. */ -static char * -list_getinfo_options(void) -{ - int i; - smartlist_t *lines = smartlist_new(); - char *ans; - for (i = 0; getinfo_items[i].varname; ++i) { - if (!getinfo_items[i].desc) - continue; - - smartlist_add_asprintf(lines, "%s%s -- %s\n", - getinfo_items[i].varname, - getinfo_items[i].is_prefix ? "*" : "", - getinfo_items[i].desc); - } - smartlist_sort_strings(lines); - - ans = smartlist_join_strings(lines, "", 0, NULL); - SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); - smartlist_free(lines); - - return ans; -} - -/** Lookup the 'getinfo' entry <b>question</b>, and return - * the answer in <b>*answer</b> (or NULL if key not recognized). - * Return 0 if success or unrecognized, or -1 if recognized but - * internal error. */ -static int -handle_getinfo_helper(control_connection_t *control_conn, - const char *question, char **answer, - const char **err_out) -{ - int i; - *answer = NULL; /* unrecognized key by default */ - - for (i = 0; getinfo_items[i].varname; ++i) { - int match; - if (getinfo_items[i].is_prefix) - match = !strcmpstart(question, getinfo_items[i].varname); - else - match = !strcmp(question, getinfo_items[i].varname); - if (match) { - tor_assert(getinfo_items[i].fn); - return getinfo_items[i].fn(control_conn, question, answer, err_out); - } - } - - return 0; /* unrecognized */ -} - -/** Called when we receive a GETINFO command. Try to fetch all requested - * information, and reply with information or error message. */ -static int -handle_control_getinfo(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *questions = smartlist_new(); - smartlist_t *answers = smartlist_new(); - smartlist_t *unrecognized = smartlist_new(); - char *ans = NULL; - int i; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - smartlist_split_string(questions, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { - const char *errmsg = NULL; - - if (handle_getinfo_helper(conn, q, &ans, &errmsg) < 0) { - if (!errmsg) - errmsg = "Internal error"; - connection_printf_to_buf(conn, "551 %s\r\n", errmsg); - goto done; - } - if (!ans) { - if (errmsg) /* use provided error message */ - smartlist_add_strdup(unrecognized, errmsg); - else /* use default error message */ - smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q); - } else { - smartlist_add_strdup(answers, q); - smartlist_add(answers, ans); - } - } SMARTLIST_FOREACH_END(q); - - if (smartlist_len(unrecognized)) { - /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */ - for (i=0; i < smartlist_len(unrecognized)-1; ++i) - connection_printf_to_buf(conn, - "552-%s\r\n", - (char *)smartlist_get(unrecognized, i)); - - connection_printf_to_buf(conn, - "552 %s\r\n", - (char *)smartlist_get(unrecognized, i)); - goto done; - } - - for (i = 0; i < smartlist_len(answers); i += 2) { - char *k = smartlist_get(answers, i); - char *v = smartlist_get(answers, i+1); - if (!strchr(v, '\n') && !strchr(v, '\r')) { - connection_printf_to_buf(conn, "250-%s=", k); - connection_write_str_to_buf(v, conn); - connection_write_str_to_buf("\r\n", conn); - } else { - char *esc = NULL; - size_t esc_len; - esc_len = write_escaped_data(v, strlen(v), &esc); - connection_printf_to_buf(conn, "250+%s=\r\n", k); - connection_buf_add(esc, esc_len, TO_CONN(conn)); - tor_free(esc); - } - } - connection_write_str_to_buf("250 OK\r\n", conn); - - done: - SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); - smartlist_free(answers); - SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); - smartlist_free(questions); - SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp)); - smartlist_free(unrecognized); - - return 0; -} - -/** Given a string, convert it to a circuit purpose. */ -static uint8_t -circuit_purpose_from_string(const char *string) -{ - if (!strcasecmpstart(string, "purpose=")) - string += strlen("purpose="); - - if (!strcasecmp(string, "general")) - return CIRCUIT_PURPOSE_C_GENERAL; - else if (!strcasecmp(string, "controller")) - return CIRCUIT_PURPOSE_CONTROLLER; - else - return CIRCUIT_PURPOSE_UNKNOWN; -} - -/** Return a newly allocated smartlist containing the arguments to the command - * waiting in <b>body</b>. If there are fewer than <b>min_args</b> arguments, - * or if <b>max_args</b> is nonnegative and there are more than - * <b>max_args</b> arguments, send a 512 error to the controller, using - * <b>command</b> as the command name in the error message. */ -static smartlist_t * -getargs_helper(const char *command, control_connection_t *conn, - const char *body, int min_args, int max_args) -{ - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(args) < min_args) { - connection_printf_to_buf(conn, "512 Missing argument to %s\r\n",command); - goto err; - } else if (max_args >= 0 && smartlist_len(args) > max_args) { - connection_printf_to_buf(conn, "512 Too many arguments to %s\r\n",command); - goto err; - } - return args; - err: - SMARTLIST_FOREACH(args, char *, s, tor_free(s)); - smartlist_free(args); - return NULL; -} - -/** Helper. Return the first element of <b>sl</b> at index <b>start_at</b> or - * higher that starts with <b>prefix</b>, case-insensitive. Return NULL if no - * such element exists. */ -static const char * -find_element_starting_with(smartlist_t *sl, int start_at, const char *prefix) -{ - int i; - for (i = start_at; i < smartlist_len(sl); ++i) { - const char *elt = smartlist_get(sl, i); - if (!strcasecmpstart(elt, prefix)) - return elt; - } - return NULL; -} - -/** Helper. Return true iff s is an argument that we should treat as a - * key-value pair. */ -static int -is_keyval_pair(const char *s) -{ - /* An argument is a key-value pair if it has an =, and it isn't of the form - * $fingeprint=name */ - return strchr(s, '=') && s[0] != '$'; -} - -/** Called when we get an EXTENDCIRCUIT message. Try to extend the listed - * circuit, and report success or failure. */ -static int -handle_control_extendcircuit(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *router_nicknames=NULL, *nodes=NULL; - origin_circuit_t *circ = NULL; - int zero_circ; - uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; - smartlist_t *args; - (void) len; - - router_nicknames = smartlist_new(); - - args = getargs_helper("EXTENDCIRCUIT", conn, body, 1, -1); - if (!args) - goto done; - - zero_circ = !strcmp("0", (char*)smartlist_get(args,0)); - - if (zero_circ) { - const char *purp = find_element_starting_with(args, 1, "PURPOSE="); - - if (purp) { - intended_purpose = circuit_purpose_from_string(purp); - if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - } - - if ((smartlist_len(args) == 1) || - (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { - // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" - circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); - if (!circ) { - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - } else { - connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", - (unsigned long)circ->global_identifier); - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - // "EXTENDCIRCUIT 0 router1,router2" || - // "EXTENDCIRCUIT 0 router1,router2 PURPOSE=foo" - } - - if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - - if (smartlist_len(args) < 2) { - connection_printf_to_buf(conn, - "512 syntax error: not enough arguments.\r\n"); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - - smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0); - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - - nodes = smartlist_new(); - int first_node = zero_circ; - SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 0); - if (!node) { - connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); - goto done; - } - if (!node_has_preferred_descriptor(node, first_node)) { - connection_printf_to_buf(conn, "552 No descriptor for \"%s\"\r\n", n); - goto done; - } - smartlist_add(nodes, (void*)node); - first_node = 0; - } SMARTLIST_FOREACH_END(n); - if (!smartlist_len(nodes)) { - connection_write_str_to_buf("512 No router names provided\r\n", conn); - goto done; - } - - if (zero_circ) { - /* start a new circuit */ - circ = origin_circuit_init(intended_purpose, 0); - } - - /* now circ refers to something that is ready to be extended */ - first_node = zero_circ; - SMARTLIST_FOREACH(nodes, const node_t *, node, - { - extend_info_t *info = extend_info_from_node(node, first_node); - if (!info) { - tor_assert_nonfatal(first_node); - log_warn(LD_CONTROL, - "controller tried to connect to a node that lacks a suitable " - "descriptor, or which doesn't have any " - "addresses that are allowed by the firewall configuration; " - "circuit marked for closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - goto done; - } - circuit_append_new_exit(circ, info); - if (circ->build_state->desired_path_len > 1) { - circ->build_state->onehop_tunnel = 0; - } - extend_info_free(info); - first_node = 0; - }); - - /* now that we've populated the cpath, start extending */ - if (zero_circ) { - int err_reason = 0; - if ((err_reason = circuit_handle_first_hop(circ)) < 0) { - circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - goto done; - } - } else { - if (circ->base_.state == CIRCUIT_STATE_OPEN || - circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) { - int err_reason = 0; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { - log_info(LD_CONTROL, - "send_next_onion_skin failed; circuit marked for closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); - connection_write_str_to_buf("551 Couldn't send onion skin\r\n", conn); - goto done; - } - } - } - - connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", - (unsigned long)circ->global_identifier); - if (zero_circ) /* send a 'launched' event, for completeness */ - control_event_circuit_status(circ, CIRC_EVENT_LAUNCHED, 0); - done: - SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); - smartlist_free(router_nicknames); - smartlist_free(nodes); - return 0; -} - -/** Called when we get a SETCIRCUITPURPOSE message. If we can find the - * circuit and it's a valid purpose, change it. */ -static int -handle_control_setcircuitpurpose(control_connection_t *conn, - uint32_t len, const char *body) -{ - origin_circuit_t *circ = NULL; - uint8_t new_purpose; - smartlist_t *args; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - args = getargs_helper("SETCIRCUITPURPOSE", conn, body, 2, -1); - if (!args) - goto done; - - if (!(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - goto done; - } - - { - const char *purp = find_element_starting_with(args,1,"PURPOSE="); - if (!purp) { - connection_write_str_to_buf("552 No purpose given\r\n", conn); - goto done; - } - new_purpose = circuit_purpose_from_string(purp); - if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); - goto done; - } - } - - circuit_change_purpose(TO_CIRCUIT(circ), new_purpose); - connection_write_str_to_buf("250 OK\r\n", conn); - - done: - if (args) { - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - } - return 0; -} - -/** Called when we get an ATTACHSTREAM message. Try to attach the requested - * stream, and report success or failure. */ -static int -handle_control_attachstream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn = NULL; - origin_circuit_t *circ = NULL; - int zero_circ; - smartlist_t *args; - crypt_path_t *cpath=NULL; - int hop=0, hop_line_ok=1; - (void) len; - - args = getargs_helper("ATTACHSTREAM", conn, body, 2, -1); - if (!args) - return 0; - - zero_circ = !strcmp("0", (char*)smartlist_get(args,1)); - - if (!(ap_conn = get_stream(smartlist_get(args, 0)))) { - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - } else if (!zero_circ && !(circ = get_circ(smartlist_get(args, 1)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 1)); - } else if (circ) { - const char *hopstring = find_element_starting_with(args,2,"HOP="); - if (hopstring) { - hopstring += strlen("HOP="); - hop = (int) tor_parse_ulong(hopstring, 10, 0, INT_MAX, - &hop_line_ok, NULL); - if (!hop_line_ok) { /* broken hop line */ - connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", hopstring); - } - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!ap_conn || (!zero_circ && !circ) || !hop_line_ok) - return 0; - - if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && - ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && - ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_RESOLVE_WAIT) { - connection_write_str_to_buf( - "555 Connection is not managed by controller.\r\n", - conn); - return 0; - } - - /* Do we need to detach it first? */ - if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT) { - edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); - circuit_t *tmpcirc = circuit_get_by_edge_conn(edge_conn); - connection_edge_end(edge_conn, END_STREAM_REASON_TIMEOUT); - /* Un-mark it as ending, since we're going to reuse it. */ - edge_conn->edge_has_sent_end = 0; - edge_conn->end_reason = 0; - if (tmpcirc) - circuit_detach_stream(tmpcirc, edge_conn); - CONNECTION_AP_EXPECT_NONPENDING(ap_conn); - TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; - } - - if (circ && (circ->base_.state != CIRCUIT_STATE_OPEN)) { - connection_write_str_to_buf( - "551 Can't attach stream to non-open origin circuit\r\n", - conn); - return 0; - } - /* Is this a single hop circuit? */ - if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { - connection_write_str_to_buf( - "551 Can't attach stream to this one-hop circuit.\r\n", conn); - return 0; - } - - if (circ && hop>0) { - /* find this hop in the circuit, and set cpath */ - cpath = circuit_get_cpath_hop(circ, hop); - if (!cpath) { - connection_printf_to_buf(conn, - "551 Circuit doesn't have %d hops.\r\n", hop); - return 0; - } - } - if (connection_ap_handshake_rewrite_and_attach(ap_conn, circ, cpath) < 0) { - connection_write_str_to_buf("551 Unable to attach stream\r\n", conn); - return 0; - } - send_control_done(conn); - return 0; -} - -/** Called when we get a POSTDESCRIPTOR message. Try to learn the provided - * descriptor, and report success or failure. */ -static int -handle_control_postdescriptor(control_connection_t *conn, uint32_t len, - const char *body) -{ - char *desc; - const char *msg=NULL; - uint8_t purpose = ROUTER_PURPOSE_GENERAL; - int cache = 0; /* eventually, we may switch this to 1 */ - - const char *cp = memchr(body, '\n', len); - - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; - } - ++cp; - - char *cmdline = tor_memdup_nulterm(body, cp-body); - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, cmdline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, char *, option) { - if (!strcasecmpstart(option, "purpose=")) { - option += strlen("purpose="); - purpose = router_purpose_from_string(option); - if (purpose == ROUTER_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", - option); - goto done; - } - } else if (!strcasecmpstart(option, "cache=")) { - option += strlen("cache="); - if (!strcasecmp(option, "no")) - cache = 0; - else if (!strcasecmp(option, "yes")) - cache = 1; - else { - connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n", - option); - goto done; - } - } else { /* unrecognized argument? */ - connection_printf_to_buf(conn, - "512 Unexpected argument \"%s\" to postdescriptor\r\n", option); - goto done; - } - } SMARTLIST_FOREACH_END(option); - - read_escaped_data(cp, len-(cp-body), &desc); - - switch (router_load_single_router(desc, purpose, cache, &msg)) { - case -1: - if (!msg) msg = "Could not parse descriptor"; - connection_printf_to_buf(conn, "554 %s\r\n", msg); - break; - case 0: - if (!msg) msg = "Descriptor not added"; - connection_printf_to_buf(conn, "251 %s\r\n",msg); - break; - case 1: - send_control_done(conn); - break; - } - - tor_free(desc); - done: - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - tor_free(cmdline); - return 0; -} - -/** Called when we receive a REDIRECTSTERAM command. Try to change the target - * address of the named AP stream, and report success or failure. */ -static int -handle_control_redirectstream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn = NULL; - char *new_addr = NULL; - uint16_t new_port = 0; - smartlist_t *args; - (void) len; - - args = getargs_helper("REDIRECTSTREAM", conn, body, 2, -1); - if (!args) - return 0; - - if (!(ap_conn = get_stream(smartlist_get(args, 0))) - || !ap_conn->socks_request) { - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - } else { - int ok = 1; - if (smartlist_len(args) > 2) { /* they included a port too */ - new_port = (uint16_t) tor_parse_ulong(smartlist_get(args, 2), - 10, 1, 65535, &ok, NULL); - } - if (!ok) { - connection_printf_to_buf(conn, "512 Cannot parse port \"%s\"\r\n", - (char*)smartlist_get(args, 2)); - } else { - new_addr = tor_strdup(smartlist_get(args, 1)); - } - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!new_addr) - return 0; - - strlcpy(ap_conn->socks_request->address, new_addr, - sizeof(ap_conn->socks_request->address)); - if (new_port) - ap_conn->socks_request->port = new_port; - tor_free(new_addr); - send_control_done(conn); - return 0; -} - -/** Called when we get a CLOSESTREAM command; try to close the named stream - * and report success or failure. */ -static int -handle_control_closestream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn=NULL; - uint8_t reason=0; - smartlist_t *args; - int ok; - (void) len; - - args = getargs_helper("CLOSESTREAM", conn, body, 2, -1); - if (!args) - return 0; - - else if (!(ap_conn = get_stream(smartlist_get(args, 0)))) - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - else { - reason = (uint8_t) tor_parse_ulong(smartlist_get(args,1), 10, 0, 255, - &ok, NULL); - if (!ok) { - connection_printf_to_buf(conn, "552 Unrecognized reason \"%s\"\r\n", - (char*)smartlist_get(args, 1)); - ap_conn = NULL; - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!ap_conn) - return 0; - - connection_mark_unattached_ap(ap_conn, reason); - send_control_done(conn); - return 0; -} - -/** Called when we get a CLOSECIRCUIT command; try to close the named circuit - * and report success or failure. */ -static int -handle_control_closecircuit(control_connection_t *conn, uint32_t len, - const char *body) -{ - origin_circuit_t *circ = NULL; - int safe = 0; - smartlist_t *args; - (void) len; - - args = getargs_helper("CLOSECIRCUIT", conn, body, 1, -1); - if (!args) - return 0; - - if (!(circ=get_circ(smartlist_get(args, 0)))) - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - else { - int i; - for (i=1; i < smartlist_len(args); ++i) { - if (!strcasecmp(smartlist_get(args, i), "IfUnused")) - safe = 1; - else - log_info(LD_CONTROL, "Skipping unknown option %s", - (char*)smartlist_get(args,i)); - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!circ) - return 0; - - if (!safe || !circ->p_streams) { - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); - } - - send_control_done(conn); - return 0; -} - -/** Called when we get a RESOLVE command: start trying to resolve - * the listed addresses. */ -static int -handle_control_resolve(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *args, *failed; - int is_reverse = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - - if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { - log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " - "isn't listening for ADDRMAP events. It probably won't see " - "the answer."); - } - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - { - const char *modearg = find_element_starting_with(args, 0, "mode="); - if (modearg && !strcasecmp(modearg, "mode=reverse")) - is_reverse = 1; - } - failed = smartlist_new(); - SMARTLIST_FOREACH(args, const char *, arg, { - if (!is_keyval_pair(arg)) { - if (dnsserv_launch_request(arg, is_reverse, conn)<0) - smartlist_add(failed, (char*)arg); - } - }); - - send_control_done(conn); - SMARTLIST_FOREACH(failed, const char *, arg, { - control_event_address_mapped(arg, arg, time(NULL), - "internal", 0); - }); - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - smartlist_free(failed); - return 0; -} - -/** Called when we get a PROTOCOLINFO command: send back a reply. */ -static int -handle_control_protocolinfo(control_connection_t *conn, uint32_t len, - const char *body) -{ - const char *bad_arg = NULL; - smartlist_t *args; - (void)len; - - conn->have_sent_protocolinfo = 1; - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH(args, const char *, arg, { - int ok; - tor_parse_long(arg, 10, 0, LONG_MAX, &ok, NULL); - if (!ok) { - bad_arg = arg; - break; - } - }); - if (bad_arg) { - connection_printf_to_buf(conn, "513 No such version %s\r\n", - escaped(bad_arg)); - /* Don't tolerate bad arguments when not authenticated. */ - if (!STATE_IS_OPEN(TO_CONN(conn)->state)) - connection_mark_for_close(TO_CONN(conn)); - goto done; - } else { - const or_options_t *options = get_options(); - int cookies = options->CookieAuthentication; - char *cfile = get_controller_cookie_file_name(); - char *abs_cfile; - char *esc_cfile; - char *methods; - abs_cfile = make_path_absolute(cfile); - esc_cfile = esc_for_log(abs_cfile); - { - int passwd = (options->HashedControlPassword != NULL || - options->HashedControlSessionPassword != NULL); - smartlist_t *mlist = smartlist_new(); - if (cookies) { - smartlist_add(mlist, (char*)"COOKIE"); - smartlist_add(mlist, (char*)"SAFECOOKIE"); - } - if (passwd) - smartlist_add(mlist, (char*)"HASHEDPASSWORD"); - if (!cookies && !passwd) - smartlist_add(mlist, (char*)"NULL"); - methods = smartlist_join_strings(mlist, ",", 0, NULL); - smartlist_free(mlist); - } - - connection_printf_to_buf(conn, - "250-PROTOCOLINFO 1\r\n" - "250-AUTH METHODS=%s%s%s\r\n" - "250-VERSION Tor=%s\r\n" - "250 OK\r\n", - methods, - cookies?" COOKIEFILE=":"", - cookies?esc_cfile:"", - escaped(VERSION)); - tor_free(methods); - tor_free(cfile); - tor_free(abs_cfile); - tor_free(esc_cfile); - } - done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Called when we get an AUTHCHALLENGE command. */ -static int -handle_control_authchallenge(control_connection_t *conn, uint32_t len, - const char *body) -{ - const char *cp = body; - char *client_nonce; - size_t client_nonce_len; - char server_hash[DIGEST256_LEN]; - char server_hash_encoded[HEX_DIGEST256_LEN+1]; - char server_nonce[SAFECOOKIE_SERVER_NONCE_LEN]; - char server_nonce_encoded[(2*SAFECOOKIE_SERVER_NONCE_LEN) + 1]; - - cp += strspn(cp, " \t\n\r"); - if (!strcasecmpstart(cp, "SAFECOOKIE")) { - cp += strlen("SAFECOOKIE"); - } else { - connection_write_str_to_buf("513 AUTHCHALLENGE only supports SAFECOOKIE " - "authentication\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - - if (!authentication_cookie_is_set) { - connection_write_str_to_buf("515 Cookie authentication is disabled\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - - cp += strspn(cp, " \t\n\r"); - if (*cp == '"') { - const char *newcp = - decode_escaped_string(cp, len - (cp - body), - &client_nonce, &client_nonce_len); - if (newcp == NULL) { - connection_write_str_to_buf("513 Invalid quoted client nonce\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - cp = newcp; - } else { - size_t client_nonce_encoded_len = strspn(cp, "0123456789ABCDEFabcdef"); - - client_nonce_len = client_nonce_encoded_len / 2; - client_nonce = tor_malloc_zero(client_nonce_len); - - if (base16_decode(client_nonce, client_nonce_len, - cp, client_nonce_encoded_len) - != (int) client_nonce_len) { - connection_write_str_to_buf("513 Invalid base16 client nonce\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(client_nonce); - return -1; - } - - cp += client_nonce_encoded_len; - } - - cp += strspn(cp, " \t\n\r"); - if (*cp != '\0' || - cp != body + len) { - connection_write_str_to_buf("513 Junk at end of AUTHCHALLENGE command\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(client_nonce); - return -1; - } - crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); - - /* Now compute and send the server-to-controller response, and the - * server's nonce. */ - tor_assert(authentication_cookie != NULL); - - { - size_t tmp_len = (AUTHENTICATION_COOKIE_LEN + - client_nonce_len + - SAFECOOKIE_SERVER_NONCE_LEN); - char *tmp = tor_malloc_zero(tmp_len); - char *client_hash = tor_malloc_zero(DIGEST256_LEN); - memcpy(tmp, authentication_cookie, AUTHENTICATION_COOKIE_LEN); - memcpy(tmp + AUTHENTICATION_COOKIE_LEN, client_nonce, client_nonce_len); - memcpy(tmp + AUTHENTICATION_COOKIE_LEN + client_nonce_len, - server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); - - crypto_hmac_sha256(server_hash, - SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT, - strlen(SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT), - tmp, - tmp_len); - - crypto_hmac_sha256(client_hash, - SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT, - strlen(SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT), - tmp, - tmp_len); - - conn->safecookie_client_hash = client_hash; - - tor_free(tmp); - } - - 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)); - - connection_printf_to_buf(conn, - "250 AUTHCHALLENGE SERVERHASH=%s " - "SERVERNONCE=%s\r\n", - server_hash_encoded, - server_nonce_encoded); - - tor_free(client_nonce); - return 0; -} - -/** Called when we get a USEFEATURE command: parse the feature list, and - * set up the control_connection's options properly. */ -static int -handle_control_usefeature(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - int bad = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { - if (!strcasecmp(arg, "VERBOSE_NAMES")) - ; - else if (!strcasecmp(arg, "EXTENDED_EVENTS")) - ; - else { - connection_printf_to_buf(conn, "552 Unrecognized feature \"%s\"\r\n", - arg); - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(arg); - - if (!bad) { - send_control_done(conn); - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Implementation for the DROPGUARDS command. */ -static int -handle_control_dropguards(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - - static int have_warned = 0; - if (! have_warned) { - log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand " - "the risks before using it. It may be removed in a future " - "version of Tor."); - have_warned = 1; - } - - if (smartlist_len(args)) { - connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); - } else { - remove_all_entry_guards(); - send_control_done(conn); - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Implementation for the HSFETCH command. */ -static int -handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body) -{ - int i; - char digest[DIGEST_LEN], *hsaddress = NULL, *arg1 = NULL, *desc_id = NULL; - smartlist_t *args = NULL, *hsdirs = NULL; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - static const char *hsfetch_command = "HSFETCH"; - static const char *v2_str = "v2-"; - const size_t v2_str_len = strlen(v2_str); - rend_data_t *rend_query = NULL; - - /* Make sure we have at least one argument, the HSAddress. */ - args = getargs_helper(hsfetch_command, conn, body, 1, -1); - if (!args) { - goto exit; - } - - /* Extract the first argument (either HSAddress or DescID). */ - arg1 = smartlist_get(args, 0); - /* Test if it's an HS address without the .onion part. */ - if (rend_valid_v2_service_id(arg1)) { - hsaddress = arg1; - } else if (strcmpstart(arg1, v2_str) == 0 && - rend_valid_descriptor_id(arg1 + v2_str_len) && - base32_decode(digest, sizeof(digest), arg1 + v2_str_len, - REND_DESC_ID_V2_LEN_BASE32) == 0) { - /* We have a well formed version 2 descriptor ID. Keep the decoded value - * of the id. */ - desc_id = digest; - } else { - connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n", - arg1); - goto done; - } - - static const char *opt_server = "SERVER="; - - /* Skip first argument because it's the HSAddress or DescID. */ - for (i = 1; i < smartlist_len(args); ++i) { - const char *arg = smartlist_get(args, i); - const node_t *node; - - if (!strcasecmpstart(arg, opt_server)) { - const char *server; - - server = arg + strlen(opt_server); - node = node_get_by_hex_id(server, 0); - if (!node) { - connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", - server); - goto done; - } - if (!hsdirs) { - /* Stores routerstatus_t object for each specified server. */ - hsdirs = smartlist_new(); - } - /* Valid server, add it to our local list. */ - smartlist_add(hsdirs, node->rs); - } else { - connection_printf_to_buf(conn, "513 Unexpected argument \"%s\"\r\n", - arg); - goto done; - } - } - - rend_query = rend_data_client_create(hsaddress, desc_id, NULL, - REND_NO_AUTH); - if (rend_query == NULL) { - connection_printf_to_buf(conn, "551 Error creating the HS query\r\n"); - goto done; - } - - /* Using a descriptor ID, we force the user to provide at least one - * hsdir server using the SERVER= option. */ - if (desc_id && (!hsdirs || !smartlist_len(hsdirs))) { - connection_printf_to_buf(conn, "512 %s option is required\r\n", - opt_server); - goto done; - } - - /* We are about to trigger HSDir fetch so send the OK now because after - * that 650 event(s) are possible so better to have the 250 OK before them - * to avoid out of order replies. */ - send_control_done(conn); - - /* Trigger the fetch using the built rend query and possibly a list of HS - * directory to use. This function ignores the client cache thus this will - * always send a fetch command. */ - rend_client_fetch_v2_desc(rend_query, hsdirs); - - done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - /* Contains data pointer that we don't own thus no cleanup. */ - smartlist_free(hsdirs); - rend_data_free(rend_query); - exit: - return 0; -} - -/** Implementation for the HSPOST command. */ -static int -handle_control_hspost(control_connection_t *conn, - uint32_t len, - const char *body) -{ - static const char *opt_server = "SERVER="; - static const char *opt_hsaddress = "HSADDRESS="; - smartlist_t *hs_dirs = NULL; - const char *encoded_desc = body; - size_t encoded_desc_len = len; - const char *onion_address = NULL; - - char *cp = memchr(body, '\n', len); - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; - } - char *argline = tor_strndup(body, cp-body); - - smartlist_t *args = smartlist_new(); - - /* If any SERVER= or HSADDRESS= options were specified, try to parse - * the options line. */ - if (!strcasecmpstart(argline, opt_server) || - !strcasecmpstart(argline, opt_hsaddress)) { - /* encoded_desc begins after a newline character */ - cp = cp + 1; - encoded_desc = cp; - encoded_desc_len = len-(cp-body); - - smartlist_split_string(args, argline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { - if (!strcasecmpstart(arg, opt_server)) { - const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server, 0); - - if (!node || !node->rs) { - connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", - server); - goto done; - } - /* Valid server, add it to our local list. */ - if (!hs_dirs) - hs_dirs = smartlist_new(); - smartlist_add(hs_dirs, node->rs); - } else if (!strcasecmpstart(arg, opt_hsaddress)) { - const char *address = arg + strlen(opt_hsaddress); - if (!hs_address_is_valid(address)) { - connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); - goto done; - } - onion_address = address; - } else { - connection_printf_to_buf(conn, "512 Unexpected argument \"%s\"\r\n", - arg); - goto done; - } - } SMARTLIST_FOREACH_END(arg); - } - - /* Handle the v3 case. */ - if (onion_address) { - char *desc_str = NULL; - read_escaped_data(encoded_desc, encoded_desc_len, &desc_str); - if (hs_control_hspost_command(desc_str, onion_address, hs_dirs) < 0) { - connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); - } else { - send_control_done(conn); - } - tor_free(desc_str); - goto done; - } - - /* From this point on, it is only v2. */ - - /* Read the dot encoded descriptor, and parse it. */ - rend_encoded_v2_service_descriptor_t *desc = - tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); - read_escaped_data(encoded_desc, encoded_desc_len, &desc->desc_str); - - rend_service_descriptor_t *parsed = NULL; - char *intro_content = NULL; - size_t intro_size; - size_t encoded_size; - const char *next_desc; - if (!rend_parse_v2_service_descriptor(&parsed, desc->desc_id, &intro_content, - &intro_size, &encoded_size, - &next_desc, desc->desc_str, 1)) { - /* Post the descriptor. */ - char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - if (!rend_get_service_id(parsed->pk, serviceid)) { - smartlist_t *descs = smartlist_new(); - smartlist_add(descs, desc); - - /* We are about to trigger HS descriptor upload so send the OK now - * because after that 650 event(s) are possible so better to have the - * 250 OK before them to avoid out of order replies. */ - send_control_done(conn); - - /* Trigger the descriptor upload */ - directory_post_to_hs_dir(parsed, descs, hs_dirs, serviceid, 0); - smartlist_free(descs); - } - - rend_service_descriptor_free(parsed); - } else { - connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); - } - - tor_free(intro_content); - rend_encoded_v2_service_descriptor_free(desc); - done: - tor_free(argline); - smartlist_free(hs_dirs); /* Contents belong to the rend service code. */ - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - return 0; -} - -/* Helper function for ADD_ONION that adds an ephemeral service depending on - * the given hs_version. - * - * The secret key in pk depends on the hs_version. The ownership of the key - * used in pk is given to the HS subsystem so the caller must stop accessing - * it after. - * - * The port_cfgs is a list of service port. Ownership transferred to service. - * The max_streams refers to the MaxStreams= key. - * The max_streams_close_circuit refers to the MaxStreamsCloseCircuit key. - * The auth_type is the authentication type of the clients in auth_clients. - * The ownership of that list is transferred to the service. - * - * On success (RSAE_OKAY), the address_out points to a newly allocated string - * containing the onion address without the .onion part. On error, address_out - * is untouched. */ -static hs_service_add_ephemeral_status_t -add_onion_helper_add_service(int hs_version, - add_onion_secret_key_t *pk, - smartlist_t *port_cfgs, int max_streams, - int max_streams_close_circuit, int auth_type, - smartlist_t *auth_clients, char **address_out) -{ - hs_service_add_ephemeral_status_t ret; - - tor_assert(pk); - tor_assert(port_cfgs); - tor_assert(address_out); - - switch (hs_version) { - case HS_VERSION_TWO: - ret = rend_service_add_ephemeral(pk->v2, port_cfgs, max_streams, - max_streams_close_circuit, auth_type, - auth_clients, address_out); - break; - case HS_VERSION_THREE: - ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, - max_streams_close_circuit, address_out); - break; - default: - tor_assert_unreached(); - } - - return ret; -} - -/** Called when we get a ADD_ONION command; parse the body, and set up - * the new ephemeral Onion Service. */ -static int -handle_control_add_onion(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - int arg_len; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("ADD_ONION", conn, body, 2, -1); - if (!args) - return 0; - arg_len = smartlist_len(args); - - /* Parse all of the arguments that do not involve handling cryptographic - * material first, since there's no reason to touch that at all if any of - * the other arguments are malformed. - */ - smartlist_t *port_cfgs = smartlist_new(); - smartlist_t *auth_clients = NULL; - smartlist_t *auth_created_clients = NULL; - int discard_pk = 0; - int detach = 0; - int max_streams = 0; - int max_streams_close_circuit = 0; - rend_auth_type_t auth_type = REND_NO_AUTH; - /* Default to adding an anonymous hidden service if no flag is given */ - int non_anonymous = 0; - for (int i = 1; i < arg_len; i++) { - static const char *port_prefix = "Port="; - static const char *flags_prefix = "Flags="; - static const char *max_s_prefix = "MaxStreams="; - static const char *auth_prefix = "ClientAuth="; - - const char *arg = smartlist_get(args, (int)i); - if (!strcasecmpstart(arg, port_prefix)) { - /* "Port=VIRTPORT[,TARGET]". */ - const char *port_str = arg + strlen(port_prefix); - - rend_service_port_config_t *cfg = - rend_service_parse_port_config(port_str, ",", NULL); - if (!cfg) { - connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); - goto out; - } - smartlist_add(port_cfgs, cfg); - } else if (!strcasecmpstart(arg, max_s_prefix)) { - /* "MaxStreams=[0..65535]". */ - const char *max_s_str = arg + strlen(max_s_prefix); - int ok = 0; - max_streams = (int)tor_parse_long(max_s_str, 10, 0, 65535, &ok, NULL); - if (!ok) { - connection_printf_to_buf(conn, "512 Invalid MaxStreams\r\n"); - goto out; - } - } else if (!strcasecmpstart(arg, flags_prefix)) { - /* "Flags=Flag[,Flag]", where Flag can be: - * * 'DiscardPK' - If tor generates the keypair, do not include it in - * the response. - * * 'Detach' - Do not tie this onion service to any particular control - * connection. - * * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is - * exceeded. - * * 'BasicAuth' - Client authorization using the 'basic' method. - * * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this - * flag is present, tor must be in non-anonymous - * hidden service mode. If this flag is absent, - * tor must be in anonymous hidden service mode. - */ - static const char *discard_flag = "DiscardPK"; - static const char *detach_flag = "Detach"; - static const char *max_s_close_flag = "MaxStreamsCloseCircuit"; - static const char *basicauth_flag = "BasicAuth"; - static const char *non_anonymous_flag = "NonAnonymous"; - - smartlist_t *flags = smartlist_new(); - int bad = 0; - - smartlist_split_string(flags, arg + strlen(flags_prefix), ",", - SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(flags) < 1) { - connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); - bad = 1; - } - SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) - { - if (!strcasecmp(flag, discard_flag)) { - discard_pk = 1; - } else if (!strcasecmp(flag, detach_flag)) { - detach = 1; - } else if (!strcasecmp(flag, max_s_close_flag)) { - max_streams_close_circuit = 1; - } else if (!strcasecmp(flag, basicauth_flag)) { - auth_type = REND_BASIC_AUTH; - } else if (!strcasecmp(flag, non_anonymous_flag)) { - non_anonymous = 1; - } else { - connection_printf_to_buf(conn, - "512 Invalid 'Flags' argument: %s\r\n", - escaped(flag)); - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(flag); - SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); - smartlist_free(flags); - if (bad) - goto out; - } else if (!strcasecmpstart(arg, auth_prefix)) { - char *err_msg = NULL; - int created = 0; - rend_authorized_client_t *client = - add_onion_helper_clientauth(arg + strlen(auth_prefix), - &created, &err_msg); - if (!client) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } - goto out; - } - - if (auth_clients != NULL) { - int bad = 0; - SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) { - if (strcmp(ac->client_name, client->client_name) == 0) { - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(ac); - if (bad) { - connection_printf_to_buf(conn, - "512 Duplicate name in ClientAuth\r\n"); - rend_authorized_client_free(client); - goto out; - } - } else { - auth_clients = smartlist_new(); - auth_created_clients = smartlist_new(); - } - smartlist_add(auth_clients, client); - if (created) { - smartlist_add(auth_created_clients, client); - } - } else { - connection_printf_to_buf(conn, "513 Invalid argument\r\n"); - goto out; - } - } - if (smartlist_len(port_cfgs) == 0) { - connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n"); - goto out; - } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) { - connection_printf_to_buf(conn, "512 No auth type specified\r\n"); - goto out; - } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) { - connection_printf_to_buf(conn, "512 No auth clients specified\r\n"); - goto out; - } else if ((auth_type == REND_BASIC_AUTH && - smartlist_len(auth_clients) > 512) || - (auth_type == REND_STEALTH_AUTH && - smartlist_len(auth_clients) > 16)) { - connection_printf_to_buf(conn, "512 Too many auth clients\r\n"); - goto out; - } else if (non_anonymous != rend_service_non_anonymous_mode_enabled( - get_options())) { - /* If we failed, and the non-anonymous flag is set, Tor must be in - * anonymous hidden service mode. - * The error message changes based on the current Tor config: - * 512 Tor is in anonymous hidden service mode - * 512 Tor is in non-anonymous hidden service mode - * (I've deliberately written them out in full here to aid searchability.) - */ - connection_printf_to_buf(conn, "512 Tor is in %sanonymous hidden service " - "mode\r\n", - non_anonymous ? "" : "non-"); - goto out; - } - - /* Parse the "keytype:keyblob" argument. */ - int hs_version = 0; - add_onion_secret_key_t pk = { NULL }; - const char *key_new_alg = NULL; - char *key_new_blob = NULL; - char *err_msg = NULL; - - if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, - &key_new_alg, &key_new_blob, &pk, &hs_version, - &err_msg) < 0) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } - goto out; - } - tor_assert(!err_msg); - - /* Hidden service version 3 don't have client authentication support so if - * ClientAuth was given, send back an error. */ - if (hs_version == HS_VERSION_THREE && auth_clients) { - connection_printf_to_buf(conn, "513 ClientAuth not supported\r\n"); - goto out; - } - - /* Create the HS, using private key pk, client authentication auth_type, - * the list of auth_clients, and port config port_cfg. - * rend_service_add_ephemeral() will take ownership of pk and port_cfg, - * regardless of success/failure. - */ - char *service_id = NULL; - int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, - max_streams, - max_streams_close_circuit, auth_type, - auth_clients, &service_id); - port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ - auth_clients = NULL; /* so is auth_clients */ - switch (ret) { - case RSAE_OKAY: - { - if (detach) { - if (!detached_onion_services) - detached_onion_services = smartlist_new(); - smartlist_add(detached_onion_services, service_id); - } else { - if (!conn->ephemeral_onion_services) - conn->ephemeral_onion_services = smartlist_new(); - smartlist_add(conn->ephemeral_onion_services, service_id); - } - - tor_assert(service_id); - connection_printf_to_buf(conn, "250-ServiceID=%s\r\n", service_id); - if (key_new_alg) { - tor_assert(key_new_blob); - connection_printf_to_buf(conn, "250-PrivateKey=%s:%s\r\n", - key_new_alg, key_new_blob); - } - if (auth_created_clients) { - SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, { - char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie, - auth_type); - tor_assert(encoded); - connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n", - ac->client_name, encoded); - memwipe(encoded, 0, strlen(encoded)); - tor_free(encoded); - }); - } - - connection_printf_to_buf(conn, "250 OK\r\n"); - break; - } - case RSAE_BADPRIVKEY: - connection_printf_to_buf(conn, "551 Failed to generate onion address\r\n"); - break; - case RSAE_ADDREXISTS: - connection_printf_to_buf(conn, "550 Onion address collision\r\n"); - break; - case RSAE_BADVIRTPORT: - connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); - break; - case RSAE_BADAUTH: - connection_printf_to_buf(conn, "512 Invalid client authorization\r\n"); - break; - case RSAE_INTERNAL: FALLTHROUGH; - default: - connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n"); - } - if (key_new_blob) { - memwipe(key_new_blob, 0, strlen(key_new_blob)); - tor_free(key_new_blob); - } - - out: - if (port_cfgs) { - SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, - rend_service_port_config_free(p)); - smartlist_free(port_cfgs); - } - - if (auth_clients) { - SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac, - rend_authorized_client_free(ac)); - smartlist_free(auth_clients); - } - if (auth_created_clients) { - // Do not free entries; they are the same as auth_clients - smartlist_free(auth_created_clients); - } - - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); - return 0; -} - -/** Helper function to handle parsing the KeyType:KeyBlob argument to the - * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated - * and the private key not discarded, the algorithm and serialized private key, - * or NULL and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned key_new_blob and err_msg. - * - * Note: The error messages returned are deliberately vague to avoid echoing - * key material. - */ -STATIC int -add_onion_helper_keyarg(const char *arg, int discard_pk, - const char **key_new_alg_out, char **key_new_blob_out, - add_onion_secret_key_t *decoded_key, int *hs_version, - char **err_msg_out) -{ - smartlist_t *key_args = smartlist_new(); - crypto_pk_t *pk = NULL; - const char *key_new_alg = NULL; - char *key_new_blob = NULL; - char *err_msg = NULL; - int ret = -1; - - smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(key_args) != 2) { - err_msg = tor_strdup("512 Invalid key type/blob\r\n"); - goto err; - } - - /* The format is "KeyType:KeyBlob". */ - static const char *key_type_new = "NEW"; - static const char *key_type_best = "BEST"; - static const char *key_type_rsa1024 = "RSA1024"; - static const char *key_type_ed25519_v3 = "ED25519-V3"; - - const char *key_type = smartlist_get(key_args, 0); - const char *key_blob = smartlist_get(key_args, 1); - - if (!strcasecmp(key_type_rsa1024, key_type)) { - /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ - pk = crypto_pk_base64_decode_private(key_blob, strlen(key_blob)); - if (!pk) { - err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); - goto err; - } - if (crypto_pk_num_bits(pk) != PK_BYTES*8) { - crypto_pk_free(pk); - err_msg = tor_strdup("512 Invalid RSA key size\r\n"); - goto err; - } - decoded_key->v2 = pk; - *hs_version = HS_VERSION_TWO; - } else if (!strcasecmp(key_type_ed25519_v3, key_type)) { - /* "ED25519-V3:<Base64 Blob>" - Loading a pre-existing ed25519 key. */ - ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); - if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, - strlen(key_blob)) != sizeof(sk->seckey)) { - tor_free(sk); - err_msg = tor_strdup("512 Failed to decode ED25519-V3 key\r\n"); - goto err; - } - decoded_key->v3 = sk; - *hs_version = HS_VERSION_THREE; - } else if (!strcasecmp(key_type_new, key_type)) { - /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ - if (!strcasecmp(key_type_rsa1024, key_blob) || - !strcasecmp(key_type_best, key_blob)) { - /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ - pk = crypto_pk_new(); - if (crypto_pk_generate_key(pk)) { - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_rsa1024); - goto err; - } - if (!discard_pk) { - if (crypto_pk_base64_encode_private(pk, &key_new_blob)) { - crypto_pk_free(pk); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_rsa1024); - goto err; - } - key_new_alg = key_type_rsa1024; - } - decoded_key->v2 = pk; - *hs_version = HS_VERSION_TWO; - } else if (!strcasecmp(key_type_ed25519_v3, key_blob)) { - ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); - if (ed25519_secret_key_generate(sk, 1) < 0) { - tor_free(sk); - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_ed25519_v3); - goto err; - } - if (!discard_pk) { - ssize_t len = base64_encode_size(sizeof(sk->seckey), 0) + 1; - key_new_blob = tor_malloc_zero(len); - if (base64_encode(key_new_blob, len, (const char *) sk->seckey, - sizeof(sk->seckey), 0) != (len - 1)) { - tor_free(sk); - tor_free(key_new_blob); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_ed25519_v3); - goto err; - } - key_new_alg = key_type_ed25519_v3; - } - decoded_key->v3 = sk; - *hs_version = HS_VERSION_THREE; - } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); - goto err; - } - } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); - goto err; - } - - /* Succeeded in loading or generating a private key. */ - ret = 0; - - err: - SMARTLIST_FOREACH(key_args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(key_args); - - if (err_msg_out) { - *err_msg_out = err_msg; - } else { - tor_free(err_msg); - } - *key_new_alg_out = key_new_alg; - *key_new_blob_out = key_new_blob; - - return ret; -} - -/** Helper function to handle parsing a ClientAuth argument to the - * ADD_ONION command. Return a new rend_authorized_client_t, or NULL - * and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned auth_client and err_msg. - * - * If 'created' is specified, it will be set to 1 when a new cookie has - * been generated. - */ -STATIC rend_authorized_client_t * -add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) -{ - int ok = 0; - - tor_assert(arg); - tor_assert(created); - tor_assert(err_msg); - *err_msg = NULL; - - smartlist_t *auth_args = smartlist_new(); - rend_authorized_client_t *client = - tor_malloc_zero(sizeof(rend_authorized_client_t)); - smartlist_split_string(auth_args, arg, ":", 0, 0); - if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) { - *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n"); - goto err; - } - client->client_name = tor_strdup(smartlist_get(auth_args, 0)); - if (smartlist_len(auth_args) == 2) { - char *decode_err_msg = NULL; - if (rend_auth_decode_cookie(smartlist_get(auth_args, 1), - client->descriptor_cookie, - NULL, &decode_err_msg) < 0) { - tor_assert(decode_err_msg); - tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg); - tor_free(decode_err_msg); - goto err; - } - *created = 0; - } else { - crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN); - *created = 1; - } - - if (!rend_valid_client_name(client->client_name)) { - *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n"); - goto err; - } - - ok = 1; - err: - SMARTLIST_FOREACH(auth_args, char *, item, tor_free(item)); - smartlist_free(auth_args); - if (!ok) { - rend_authorized_client_free(client); - client = NULL; - } - return client; -} - -/** Called when we get a DEL_ONION command; parse the body, and remove - * the existing ephemeral Onion Service. */ -static int -handle_control_del_onion(control_connection_t *conn, - uint32_t len, - const char *body) -{ - int hs_version = 0; - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("DEL_ONION", conn, body, 1, 1); - if (!args) - return 0; - - const char *service_id = smartlist_get(args, 0); - if (rend_valid_v2_service_id(service_id)) { - hs_version = HS_VERSION_TWO; - } else if (hs_address_is_valid(service_id)) { - hs_version = HS_VERSION_THREE; - } else { - connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); - goto out; - } - - /* Determine if the onion service belongs to this particular control - * connection, or if it is in the global list of detached services. If it - * is in neither, either the service ID is invalid in some way, or it - * explicitly belongs to a different control connection, and an error - * should be returned. - */ - smartlist_t *services[2] = { - conn->ephemeral_onion_services, - detached_onion_services - }; - smartlist_t *onion_services = NULL; - int idx = -1; - for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { - idx = smartlist_string_pos(services[i], service_id); - if (idx != -1) { - onion_services = services[i]; - break; - } - } - if (onion_services == NULL) { - connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); - } else { - int ret = -1; - switch (hs_version) { - case HS_VERSION_TWO: - ret = rend_service_del_ephemeral(service_id); - break; - case HS_VERSION_THREE: - ret = hs_service_del_ephemeral(service_id); - break; - default: - /* The ret value will be -1 thus hitting the warning below. This should - * never happen because of the check at the start of the function. */ - break; - } - if (ret < 0) { - /* This should *NEVER* fail, since the service is on either the - * per-control connection list, or the global one. - */ - log_warn(LD_BUG, "Failed to remove Onion Service %s.", - escaped(service_id)); - tor_fragile_assert(); - } - - /* Remove/scrub the service_id from the appropriate list. */ - char *cp = smartlist_get(onion_services, idx); - smartlist_del(onion_services, idx); - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - - send_control_done(conn); - } - - out: - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); - return 0; -} - /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) @@ -5349,6 +279,44 @@ peek_connection_has_http_command(connection_t *conn) return peek_buf_has_http_command(conn->inbuf); } +/** + * Helper: take a nul-terminated command of given length, and find where the + * command starts and the arguments begin. Separate them, allocate a new + * string in <b>current_cmd_out</b> for the command, and return a pointer + * to the arguments. + **/ +STATIC char * +control_split_incoming_command(char *incoming_cmd, + size_t *data_len, + char **current_cmd_out) +{ + const bool is_multiline = *data_len && incoming_cmd[0] == '+'; + size_t cmd_len = 0; + while (cmd_len < *data_len + && !TOR_ISSPACE(incoming_cmd[cmd_len])) + ++cmd_len; + + *current_cmd_out = tor_memdup_nulterm(incoming_cmd, cmd_len); + char *args = incoming_cmd+cmd_len; + tor_assert(*data_len>=cmd_len); + *data_len -= cmd_len; + if (is_multiline) { + // Only match horizontal space: any line after the first is data, + // not arguments. + while ((*args == '\t' || *args == ' ') && *data_len) { + ++args; + --*data_len; + } + } else { + while (TOR_ISSPACE(*args) && *data_len) { + ++args; + --*data_len; + } + } + + return args; +} + static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" @@ -5375,6 +343,60 @@ static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = "</body>\n" "</html>\n"; +/** Return an error on a control connection that tried to use the v0 protocol. + */ +static void +control_send_v0_reject(control_connection_t *conn) +{ + size_t body_len; + char buf[128]; + set_uint16(buf+2, htons(0x0000)); /* type == error */ + set_uint16(buf+4, htons(0x0001)); /* code == internal error */ + strlcpy(buf+6, "The v0 control protocol is not supported by Tor 0.1.2.17 " + "and later; upgrade your controller.", + sizeof(buf)-6); + body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ + set_uint16(buf+0, htons(body_len)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); + + connection_mark_and_flush(TO_CONN(conn)); +} + +/** Return an error on a control connection that tried to use HTTP. + */ +static void +control_send_http_reject(control_connection_t *conn) +{ + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); +} + +/** Check if a control connection has tried to use a known invalid protocol. + * If it has, then: + * - send a reject response, + * - log a notice-level message, and + * - return false. */ +static bool +control_protocol_is_valid(control_connection_t *conn) +{ + /* Detect v0 commands and send a "no more v0" message. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_control0_command(TO_CONN(conn))) { + control_send_v0_reject(conn); + return 0; + } + + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + control_send_http_reject(conn); + return 0; + } + + return 1; +} + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -5383,7 +405,6 @@ connection_control_process_inbuf(control_connection_t *conn) { size_t data_len; uint32_t cmd_data_len; - int cmd_len; char *args; tor_assert(conn); @@ -5396,30 +417,7 @@ connection_control_process_inbuf(control_connection_t *conn) conn->incoming_cmd_cur_len = 0; } - if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_connection_has_control0_command(TO_CONN(conn))) { - /* Detect v0 commands and send a "no more v0" message. */ - size_t body_len; - char buf[128]; - set_uint16(buf+2, htons(0x0000)); /* type == error */ - set_uint16(buf+4, htons(0x0001)); /* code == internal error */ - strlcpy(buf+6, "The v0 control protocol is not supported by Tor 0.1.2.17 " - "and later; upgrade your controller.", - sizeof(buf)-6); - body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ - set_uint16(buf+0, htons(body_len)); - connection_buf_add(buf, 4+body_len, TO_CONN(conn)); - - connection_mark_and_flush(TO_CONN(conn)); - return 0; - } - - /* If the user has the HTTP proxy port and the control port confused. */ - if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_connection_has_http_command(TO_CONN(conn))) { - connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); - log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); - connection_mark_and_flush(TO_CONN(conn)); + if (!control_protocol_is_valid(conn)) { return 0; } @@ -5438,7 +436,7 @@ connection_control_process_inbuf(control_connection_t *conn) return 0; else if (r == -1) { if (data_len + conn->incoming_cmd_cur_len > MAX_COMMAND_LINE_LENGTH) { - connection_write_str_to_buf("500 Line too long.\r\n", conn); + control_write_endreply(conn, 500, "Line too long."); connection_stop_reading(TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); } @@ -5475,22 +473,15 @@ connection_control_process_inbuf(control_connection_t *conn) /* Otherwise, read another line. */ } data_len = conn->incoming_cmd_cur_len; + /* Okay, we now have a command sitting on conn->incoming_cmd. See if we * recognize it. */ - cmd_len = 0; - while ((size_t)cmd_len < data_len - && !TOR_ISSPACE(conn->incoming_cmd[cmd_len])) - ++cmd_len; - - conn->incoming_cmd[cmd_len]='\0'; - args = conn->incoming_cmd+cmd_len+1; - tor_assert(data_len>(size_t)cmd_len); - data_len -= (cmd_len+1); /* skip the command and NUL we added after it */ - while (TOR_ISSPACE(*args)) { - ++args; - --data_len; - } + tor_free(conn->current_cmd); + args = control_split_incoming_command(conn->incoming_cmd, &data_len, + &conn->current_cmd); + if (BUG(!conn->current_cmd)) + return -1; /* If the connection is already closing, ignore further commands */ if (TO_CONN(conn)->marked_for_close) { @@ -5498,1448 +489,50 @@ connection_control_process_inbuf(control_connection_t *conn) } /* Otherwise, Quit is always valid. */ - if (!strcasecmp(conn->incoming_cmd, "QUIT")) { - connection_write_str_to_buf("250 closing connection\r\n", conn); + if (!strcasecmp(conn->current_cmd, "QUIT")) { + control_write_endreply(conn, 250, "closing connection"); connection_mark_and_flush(TO_CONN(conn)); return 0; } if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - !is_valid_initial_command(conn, conn->incoming_cmd)) { - connection_write_str_to_buf("514 Authentication required.\r\n", conn); + !is_valid_initial_command(conn, conn->current_cmd)) { + control_write_endreply(conn, 514, "Authentication required."); connection_mark_for_close(TO_CONN(conn)); return 0; } if (data_len >= UINT32_MAX) { - connection_write_str_to_buf("500 A 4GB command? Nice try.\r\n", conn); + control_write_endreply(conn, 500, "A 4GB command? Nice try."); connection_mark_for_close(TO_CONN(conn)); return 0; } - /* XXXX Why is this not implemented as a table like the GETINFO - * items are? Even handling the plus signs at the beginnings of - * commands wouldn't be very hard with proper macros. */ cmd_data_len = (uint32_t)data_len; - if (!strcasecmp(conn->incoming_cmd, "SETCONF")) { - if (handle_control_setconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESETCONF")) { - if (handle_control_resetconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETCONF")) { - if (handle_control_getconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+LOADCONF")) { - if (handle_control_loadconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETEVENTS")) { - if (handle_control_setevents(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHENTICATE")) { - if (handle_control_authenticate(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SAVECONF")) { - if (handle_control_saveconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SIGNAL")) { - if (handle_control_signal(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "TAKEOWNERSHIP")) { - if (handle_control_takeownership(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "MAPADDRESS")) { - if (handle_control_mapaddress(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETINFO")) { - if (handle_control_getinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "EXTENDCIRCUIT")) { - if (handle_control_extendcircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETCIRCUITPURPOSE")) { - if (handle_control_setcircuitpurpose(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETROUTERPURPOSE")) { - connection_write_str_to_buf("511 SETROUTERPURPOSE is obsolete.\r\n", conn); - } else if (!strcasecmp(conn->incoming_cmd, "ATTACHSTREAM")) { - if (handle_control_attachstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+POSTDESCRIPTOR")) { - if (handle_control_postdescriptor(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "REDIRECTSTREAM")) { - if (handle_control_redirectstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSESTREAM")) { - if (handle_control_closestream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSECIRCUIT")) { - if (handle_control_closecircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "USEFEATURE")) { - if (handle_control_usefeature(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESOLVE")) { - if (handle_control_resolve(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "PROTOCOLINFO")) { - if (handle_control_protocolinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { - if (handle_control_authchallenge(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { - if (handle_control_dropguards(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { - if (handle_control_hsfetch(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+HSPOST")) { - if (handle_control_hspost(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { - int ret = handle_control_add_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ - if (ret) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { - int ret = handle_control_del_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ - if (ret) - return -1; - } else { - connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", - conn->incoming_cmd); - } + if (handle_control_command(conn, cmd_data_len, args) < 0) + return -1; conn->incoming_cmd_cur_len = 0; goto again; } -/** Something major has happened to circuit <b>circ</b>: tell any - * interested control connections. */ -int -control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp, - int reason_code) -{ - const char *status; - char reasons[64] = ""; - if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS)) - return 0; - tor_assert(circ); - - switch (tp) - { - case CIRC_EVENT_LAUNCHED: status = "LAUNCHED"; break; - case CIRC_EVENT_BUILT: status = "BUILT"; break; - case CIRC_EVENT_EXTENDED: status = "EXTENDED"; break; - case CIRC_EVENT_FAILED: status = "FAILED"; break; - case CIRC_EVENT_CLOSED: status = "CLOSED"; break; - default: - log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); - tor_fragile_assert(); - return 0; - } - - if (tp == CIRC_EVENT_FAILED || tp == CIRC_EVENT_CLOSED) { - const char *reason_str = circuit_end_reason_to_control_string(reason_code); - char unk_reason_buf[16]; - if (!reason_str) { - tor_snprintf(unk_reason_buf, 16, "UNKNOWN_%d", reason_code); - reason_str = unk_reason_buf; - } - if (reason_code > 0 && reason_code & END_CIRC_REASON_FLAG_REMOTE) { - tor_snprintf(reasons, sizeof(reasons), - " REASON=DESTROYED REMOTE_REASON=%s", reason_str); - } else { - tor_snprintf(reasons, sizeof(reasons), - " REASON=%s", reason_str); - } - } - - { - char *circdesc = circuit_describe_status_for_controller(circ); - const char *sp = strlen(circdesc) ? " " : ""; - send_control_event(EVENT_CIRCUIT_STATUS, - "650 CIRC %lu %s%s%s%s\r\n", - (unsigned long)circ->global_identifier, - status, sp, - circdesc, - reasons); - tor_free(circdesc); - } - - return 0; -} - -/** Something minor has happened to circuit <b>circ</b>: tell any - * interested control connections. */ -static int -control_event_circuit_status_minor(origin_circuit_t *circ, - circuit_status_minor_event_t e, - int purpose, const struct timeval *tv) -{ - const char *event_desc; - char event_tail[160] = ""; - if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS_MINOR)) - return 0; - tor_assert(circ); - - switch (e) - { - case CIRC_MINOR_EVENT_PURPOSE_CHANGED: - event_desc = "PURPOSE_CHANGED"; - - { - /* event_tail can currently be up to 68 chars long */ - const char *hs_state_str = - circuit_purpose_to_controller_hs_state_string(purpose); - tor_snprintf(event_tail, sizeof(event_tail), - " OLD_PURPOSE=%s%s%s", - circuit_purpose_to_controller_string(purpose), - (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", - (hs_state_str != NULL) ? hs_state_str : ""); - } - - break; - case CIRC_MINOR_EVENT_CANNIBALIZED: - event_desc = "CANNIBALIZED"; - - { - /* event_tail can currently be up to 130 chars long */ - const char *hs_state_str = - circuit_purpose_to_controller_hs_state_string(purpose); - const struct timeval *old_timestamp_began = tv; - char tbuf[ISO_TIME_USEC_LEN+1]; - format_iso_time_nospace_usec(tbuf, old_timestamp_began); - - tor_snprintf(event_tail, sizeof(event_tail), - " OLD_PURPOSE=%s%s%s OLD_TIME_CREATED=%s", - circuit_purpose_to_controller_string(purpose), - (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", - (hs_state_str != NULL) ? hs_state_str : "", - tbuf); - } - - break; - default: - log_warn(LD_BUG, "Unrecognized status code %d", (int)e); - tor_fragile_assert(); - return 0; - } - - { - char *circdesc = circuit_describe_status_for_controller(circ); - const char *sp = strlen(circdesc) ? " " : ""; - send_control_event(EVENT_CIRCUIT_STATUS_MINOR, - "650 CIRC_MINOR %lu %s%s%s%s\r\n", - (unsigned long)circ->global_identifier, - event_desc, sp, - circdesc, - event_tail); - tor_free(circdesc); - } - - return 0; -} - -/** - * <b>circ</b> has changed its purpose from <b>old_purpose</b>: tell any - * interested controllers. - */ -int -control_event_circuit_purpose_changed(origin_circuit_t *circ, - int old_purpose) -{ - return control_event_circuit_status_minor(circ, - CIRC_MINOR_EVENT_PURPOSE_CHANGED, - old_purpose, - NULL); -} - -/** - * <b>circ</b> has changed its purpose from <b>old_purpose</b>, and its - * created-time from <b>old_tv_created</b>: tell any interested controllers. - */ -int -control_event_circuit_cannibalized(origin_circuit_t *circ, - int old_purpose, - const struct timeval *old_tv_created) -{ - return control_event_circuit_status_minor(circ, - CIRC_MINOR_EVENT_CANNIBALIZED, - old_purpose, - old_tv_created); -} - -/** Given an AP connection <b>conn</b> and a <b>len</b>-character buffer - * <b>buf</b>, determine the address:port combination requested on - * <b>conn</b>, and write it to <b>buf</b>. Return 0 on success, -1 on - * failure. */ -static int -write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len) -{ - char buf2[256]; - if (conn->chosen_exit_name) - if (tor_snprintf(buf2, sizeof(buf2), ".%s.exit", conn->chosen_exit_name)<0) - return -1; - if (!conn->socks_request) - return -1; - if (tor_snprintf(buf, len, "%s%s%s:%d", - conn->socks_request->address, - conn->chosen_exit_name ? buf2 : "", - !conn->chosen_exit_name && connection_edge_is_rendezvous_stream( - ENTRY_TO_EDGE_CONN(conn)) ? ".onion" : "", - conn->socks_request->port)<0) - return -1; - return 0; -} - -/** Something has happened to the stream associated with AP connection - * <b>conn</b>: tell any interested control connections. */ -int -control_event_stream_status(entry_connection_t *conn, stream_status_event_t tp, - int reason_code) -{ - char reason_buf[64]; - char addrport_buf[64]; - const char *status; - circuit_t *circ; - origin_circuit_t *origin_circ = NULL; - char buf[256]; - const char *purpose = ""; - tor_assert(conn->socks_request); - - if (!EVENT_IS_INTERESTING(EVENT_STREAM_STATUS)) - return 0; - - if (tp == STREAM_EVENT_CLOSED && - (reason_code & END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED)) - return 0; - - write_stream_target_to_buf(conn, buf, sizeof(buf)); - - reason_buf[0] = '\0'; - switch (tp) - { - case STREAM_EVENT_SENT_CONNECT: status = "SENTCONNECT"; break; - case STREAM_EVENT_SENT_RESOLVE: status = "SENTRESOLVE"; break; - case STREAM_EVENT_SUCCEEDED: status = "SUCCEEDED"; break; - case STREAM_EVENT_FAILED: status = "FAILED"; break; - case STREAM_EVENT_CLOSED: status = "CLOSED"; break; - case STREAM_EVENT_NEW: status = "NEW"; break; - case STREAM_EVENT_NEW_RESOLVE: status = "NEWRESOLVE"; break; - case STREAM_EVENT_FAILED_RETRIABLE: status = "DETACHED"; break; - case STREAM_EVENT_REMAP: status = "REMAP"; break; - default: - log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); - return 0; - } - if (reason_code && (tp == STREAM_EVENT_FAILED || - tp == STREAM_EVENT_CLOSED || - tp == STREAM_EVENT_FAILED_RETRIABLE)) { - const char *reason_str = stream_end_reason_to_control_string(reason_code); - char *r = NULL; - if (!reason_str) { - tor_asprintf(&r, " UNKNOWN_%d", reason_code); - reason_str = r; - } - if (reason_code & END_STREAM_REASON_FLAG_REMOTE) - tor_snprintf(reason_buf, sizeof(reason_buf), - " REASON=END REMOTE_REASON=%s", reason_str); - else - tor_snprintf(reason_buf, sizeof(reason_buf), - " REASON=%s", reason_str); - tor_free(r); - } else if (reason_code && tp == STREAM_EVENT_REMAP) { - switch (reason_code) { - case REMAP_STREAM_SOURCE_CACHE: - strlcpy(reason_buf, " SOURCE=CACHE", sizeof(reason_buf)); - break; - case REMAP_STREAM_SOURCE_EXIT: - strlcpy(reason_buf, " SOURCE=EXIT", sizeof(reason_buf)); - break; - default: - tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=UNKNOWN_%d", - reason_code); - /* XXX do we want SOURCE=UNKNOWN_%d above instead? -RD */ - break; - } - } - - if (tp == STREAM_EVENT_NEW || tp == STREAM_EVENT_NEW_RESOLVE) { - /* - * When the control conn is an AF_UNIX socket and we have no address, - * it gets set to "(Tor_internal)"; see dnsserv_launch_request() in - * dnsserv.c. - */ - if (strcmp(ENTRY_TO_CONN(conn)->address, "(Tor_internal)") != 0) { - tor_snprintf(addrport_buf,sizeof(addrport_buf), " SOURCE_ADDR=%s:%d", - ENTRY_TO_CONN(conn)->address, ENTRY_TO_CONN(conn)->port); - } else { - /* - * else leave it blank so control on AF_UNIX doesn't need to make - * something up. - */ - addrport_buf[0] = '\0'; - } - } else { - addrport_buf[0] = '\0'; - } - - if (tp == STREAM_EVENT_NEW_RESOLVE) { - purpose = " PURPOSE=DNS_REQUEST"; - } else if (tp == STREAM_EVENT_NEW) { - if (conn->use_begindir) { - connection_t *linked = ENTRY_TO_CONN(conn)->linked_conn; - int linked_dir_purpose = -1; - if (linked && linked->type == CONN_TYPE_DIR) - linked_dir_purpose = linked->purpose; - if (DIR_PURPOSE_IS_UPLOAD(linked_dir_purpose)) - purpose = " PURPOSE=DIR_UPLOAD"; - else - purpose = " PURPOSE=DIR_FETCH"; - } else - purpose = " PURPOSE=USER"; - } - - circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); - if (circ && CIRCUIT_IS_ORIGIN(circ)) - origin_circ = TO_ORIGIN_CIRCUIT(circ); - send_control_event(EVENT_STREAM_STATUS, - "650 STREAM %"PRIu64" %s %lu %s%s%s%s\r\n", - (ENTRY_TO_CONN(conn)->global_identifier), - status, - origin_circ? - (unsigned long)origin_circ->global_identifier : 0ul, - buf, reason_buf, addrport_buf, purpose); - - /* XXX need to specify its intended exit, etc? */ - - return 0; -} - -/** Figure out the best name for the target router of an OR connection - * <b>conn</b>, and write it into the <b>len</b>-character buffer - * <b>name</b>. */ -static void -orconn_target_get_name(char *name, size_t len, or_connection_t *conn) -{ - const node_t *node = node_get_by_id(conn->identity_digest); - if (node) { - tor_assert(len > MAX_VERBOSE_NICKNAME_LEN); - node_get_verbose_nickname(node, name); - } else if (! tor_digest_is_zero(conn->identity_digest)) { - name[0] = '$'; - base16_encode(name+1, len-1, conn->identity_digest, - DIGEST_LEN); - } else { - tor_snprintf(name, len, "%s:%d", - conn->base_.address, conn->base_.port); - } -} - -/** Called when the status of an OR connection <b>conn</b> changes: tell any - * interested control connections. <b>tp</b> is the new status for the - * connection. If <b>conn</b> has just closed or failed, then <b>reason</b> - * may be the reason why. - */ -int -control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, - int reason) -{ - int ncircs = 0; - const char *status; - char name[128]; - char ncircs_buf[32] = {0}; /* > 8 + log10(2^32)=10 + 2 */ - - if (!EVENT_IS_INTERESTING(EVENT_OR_CONN_STATUS)) - return 0; - - switch (tp) - { - case OR_CONN_EVENT_LAUNCHED: status = "LAUNCHED"; break; - case OR_CONN_EVENT_CONNECTED: status = "CONNECTED"; break; - case OR_CONN_EVENT_FAILED: status = "FAILED"; break; - case OR_CONN_EVENT_CLOSED: status = "CLOSED"; break; - case OR_CONN_EVENT_NEW: status = "NEW"; break; - default: - log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); - return 0; - } - if (conn->chan) { - ncircs = circuit_count_pending_on_channel(TLS_CHAN_TO_BASE(conn->chan)); - } else { - ncircs = 0; - } - ncircs += connection_or_get_num_circuits(conn); - if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { - tor_snprintf(ncircs_buf, sizeof(ncircs_buf), " NCIRCS=%d", ncircs); - } - - orconn_target_get_name(name, sizeof(name), conn); - send_control_event(EVENT_OR_CONN_STATUS, - "650 ORCONN %s %s%s%s%s ID=%"PRIu64"\r\n", - name, status, - reason ? " REASON=" : "", - orconn_end_reason_to_control_string(reason), - ncircs_buf, - (conn->base_.global_identifier)); - - return 0; -} - -/** - * Print out STREAM_BW event for a single conn - */ -int -control_event_stream_bandwidth(edge_connection_t *edge_conn) -{ - struct timeval now; - char tbuf[ISO_TIME_USEC_LEN+1]; - if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { - if (!edge_conn->n_read && !edge_conn->n_written) - return 0; - - tor_gettimeofday(&now); - format_iso_time_nospace_usec(tbuf, &now); - send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW %"PRIu64" %lu %lu %s\r\n", - (edge_conn->base_.global_identifier), - (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written, - tbuf); - - edge_conn->n_written = edge_conn->n_read = 0; - } - - return 0; -} - -/** A second or more has elapsed: tell any interested control - * connections how much bandwidth streams have used. */ -int -control_event_stream_bandwidth_used(void) -{ - if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { - smartlist_t *conns = get_connection_array(); - edge_connection_t *edge_conn; - struct timeval now; - char tbuf[ISO_TIME_USEC_LEN+1]; - - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) - { - if (conn->type != CONN_TYPE_AP) - continue; - edge_conn = TO_EDGE_CONN(conn); - if (!edge_conn->n_read && !edge_conn->n_written) - continue; - - tor_gettimeofday(&now); - format_iso_time_nospace_usec(tbuf, &now); - send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW %"PRIu64" %lu %lu %s\r\n", - (edge_conn->base_.global_identifier), - (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written, - tbuf); - - edge_conn->n_written = edge_conn->n_read = 0; - } - SMARTLIST_FOREACH_END(conn); - } - - return 0; -} - -/** A second or more has elapsed: tell any interested control connections - * how much bandwidth origin circuits have used. */ -int -control_event_circ_bandwidth_used(void) -{ - if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) - return 0; - - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { - if (!CIRCUIT_IS_ORIGIN(circ)) - continue; - - control_event_circ_bandwidth_used_for_circ(TO_ORIGIN_CIRCUIT(circ)); - } - SMARTLIST_FOREACH_END(circ); - - return 0; -} - -/** - * Emit a CIRC_BW event line for a specific circuit. - * - * This function sets the values it emits to 0, and does not emit - * an event if there is no new data to report since the last call. - * - * Therefore, it may be called at any frequency. - */ -int -control_event_circ_bandwidth_used_for_circ(origin_circuit_t *ocirc) -{ - struct timeval now; - char tbuf[ISO_TIME_USEC_LEN+1]; - - tor_assert(ocirc); - - if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) - return 0; - - /* n_read_circ_bw and n_written_circ_bw are always updated - * when there is any new cell on a circuit, and set to 0 after - * the event, below. - * - * Therefore, checking them is sufficient to determine if there - * is new data to report. */ - if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) - return 0; - - tor_gettimeofday(&now); - format_iso_time_nospace_usec(tbuf, &now); - send_control_event(EVENT_CIRC_BANDWIDTH_USED, - "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu TIME=%s " - "DELIVERED_READ=%lu OVERHEAD_READ=%lu " - "DELIVERED_WRITTEN=%lu OVERHEAD_WRITTEN=%lu\r\n", - ocirc->global_identifier, - (unsigned long)ocirc->n_read_circ_bw, - (unsigned long)ocirc->n_written_circ_bw, - tbuf, - (unsigned long)ocirc->n_delivered_read_circ_bw, - (unsigned long)ocirc->n_overhead_read_circ_bw, - (unsigned long)ocirc->n_delivered_written_circ_bw, - (unsigned long)ocirc->n_overhead_written_circ_bw); - ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; - ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0; - ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0; - - return 0; -} - -/** Print out CONN_BW event for a single OR/DIR/EXIT <b>conn</b> and reset - * bandwidth counters. */ -int -control_event_conn_bandwidth(connection_t *conn) -{ - const char *conn_type_str; - if (!get_options()->TestingEnableConnBwEvent || - !EVENT_IS_INTERESTING(EVENT_CONN_BW)) - return 0; - if (!conn->n_read_conn_bw && !conn->n_written_conn_bw) - return 0; - switch (conn->type) { - case CONN_TYPE_OR: - conn_type_str = "OR"; - break; - case CONN_TYPE_DIR: - conn_type_str = "DIR"; - break; - case CONN_TYPE_EXIT: - conn_type_str = "EXIT"; - break; - default: - return 0; - } - send_control_event(EVENT_CONN_BW, - "650 CONN_BW ID=%"PRIu64" TYPE=%s " - "READ=%lu WRITTEN=%lu\r\n", - (conn->global_identifier), - conn_type_str, - (unsigned long)conn->n_read_conn_bw, - (unsigned long)conn->n_written_conn_bw); - conn->n_written_conn_bw = conn->n_read_conn_bw = 0; - return 0; -} - -/** A second or more has elapsed: tell any interested control - * connections how much bandwidth connections have used. */ -int -control_event_conn_bandwidth_used(void) -{ - if (get_options()->TestingEnableConnBwEvent && - EVENT_IS_INTERESTING(EVENT_CONN_BW)) { - SMARTLIST_FOREACH(get_connection_array(), connection_t *, conn, - control_event_conn_bandwidth(conn)); - } - return 0; -} - -/** Helper: iterate over cell statistics of <b>circ</b> and sum up added - * cells, removed cells, and waiting times by cell command and direction. - * Store results in <b>cell_stats</b>. Free cell statistics of the - * circuit afterwards. */ -void -sum_up_cell_stats_by_command(circuit_t *circ, cell_stats_t *cell_stats) -{ - memset(cell_stats, 0, sizeof(cell_stats_t)); - SMARTLIST_FOREACH_BEGIN(circ->testing_cell_stats, - const testing_cell_stats_entry_t *, ent) { - tor_assert(ent->command <= CELL_COMMAND_MAX_); - if (!ent->removed && !ent->exitward) { - cell_stats->added_cells_appward[ent->command] += 1; - } else if (!ent->removed && ent->exitward) { - cell_stats->added_cells_exitward[ent->command] += 1; - } else if (!ent->exitward) { - cell_stats->removed_cells_appward[ent->command] += 1; - cell_stats->total_time_appward[ent->command] += ent->waiting_time * 10; - } else { - cell_stats->removed_cells_exitward[ent->command] += 1; - cell_stats->total_time_exitward[ent->command] += ent->waiting_time * 10; - } - } SMARTLIST_FOREACH_END(ent); - circuit_clear_testing_cell_stats(circ); -} - -/** Helper: append a cell statistics string to <code>event_parts</code>, - * prefixed with <code>key</code>=. Statistics consist of comma-separated - * key:value pairs with lower-case command strings as keys and cell - * numbers or total waiting times as values. A key:value pair is included - * if the entry in <code>include_if_non_zero</code> is not zero, but with - * the (possibly zero) entry from <code>number_to_include</code>. Both - * arrays are expected to have a length of CELL_COMMAND_MAX_ + 1. If no - * entry in <code>include_if_non_zero</code> is positive, no string will - * be added to <code>event_parts</code>. */ -void -append_cell_stats_by_command(smartlist_t *event_parts, const char *key, - const uint64_t *include_if_non_zero, - const uint64_t *number_to_include) -{ - smartlist_t *key_value_strings = smartlist_new(); - int i; - for (i = 0; i <= CELL_COMMAND_MAX_; i++) { - if (include_if_non_zero[i] > 0) { - smartlist_add_asprintf(key_value_strings, "%s:%"PRIu64, - cell_command_to_string(i), - (number_to_include[i])); - } - } - if (smartlist_len(key_value_strings) > 0) { - char *joined = smartlist_join_strings(key_value_strings, ",", 0, NULL); - smartlist_add_asprintf(event_parts, "%s=%s", key, joined); - SMARTLIST_FOREACH(key_value_strings, char *, cp, tor_free(cp)); - tor_free(joined); - } - smartlist_free(key_value_strings); -} - -/** Helper: format <b>cell_stats</b> for <b>circ</b> for inclusion in a - * CELL_STATS event and write result string to <b>event_string</b>. */ -void -format_cell_stats(char **event_string, circuit_t *circ, - cell_stats_t *cell_stats) -{ - smartlist_t *event_parts = smartlist_new(); - if (CIRCUIT_IS_ORIGIN(circ)) { - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - smartlist_add_asprintf(event_parts, "ID=%lu", - (unsigned long)ocirc->global_identifier); - } else if (TO_OR_CIRCUIT(circ)->p_chan) { - or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - smartlist_add_asprintf(event_parts, "InboundQueue=%lu", - (unsigned long)or_circ->p_circ_id); - smartlist_add_asprintf(event_parts, "InboundConn=%"PRIu64, - (or_circ->p_chan->global_identifier)); - append_cell_stats_by_command(event_parts, "InboundAdded", - cell_stats->added_cells_appward, - cell_stats->added_cells_appward); - append_cell_stats_by_command(event_parts, "InboundRemoved", - cell_stats->removed_cells_appward, - cell_stats->removed_cells_appward); - append_cell_stats_by_command(event_parts, "InboundTime", - cell_stats->removed_cells_appward, - cell_stats->total_time_appward); - } - if (circ->n_chan) { - smartlist_add_asprintf(event_parts, "OutboundQueue=%lu", - (unsigned long)circ->n_circ_id); - smartlist_add_asprintf(event_parts, "OutboundConn=%"PRIu64, - (circ->n_chan->global_identifier)); - append_cell_stats_by_command(event_parts, "OutboundAdded", - cell_stats->added_cells_exitward, - cell_stats->added_cells_exitward); - append_cell_stats_by_command(event_parts, "OutboundRemoved", - cell_stats->removed_cells_exitward, - cell_stats->removed_cells_exitward); - append_cell_stats_by_command(event_parts, "OutboundTime", - cell_stats->removed_cells_exitward, - cell_stats->total_time_exitward); - } - *event_string = smartlist_join_strings(event_parts, " ", 0, NULL); - SMARTLIST_FOREACH(event_parts, char *, cp, tor_free(cp)); - smartlist_free(event_parts); -} - -/** A second or more has elapsed: tell any interested control connection - * how many cells have been processed for a given circuit. */ -int -control_event_circuit_cell_stats(void) -{ - cell_stats_t *cell_stats; - char *event_string; - if (!get_options()->TestingEnableCellStatsEvent || - !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) - return 0; - cell_stats = tor_malloc(sizeof(cell_stats_t)); - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { - if (!circ->testing_cell_stats) - continue; - sum_up_cell_stats_by_command(circ, cell_stats); - format_cell_stats(&event_string, circ, cell_stats); - send_control_event(EVENT_CELL_STATS, - "650 CELL_STATS %s\r\n", event_string); - tor_free(event_string); - } - SMARTLIST_FOREACH_END(circ); - tor_free(cell_stats); - return 0; -} - -/* about 5 minutes worth. */ -#define N_BW_EVENTS_TO_CACHE 300 -/* Index into cached_bw_events to next write. */ -static int next_measurement_idx = 0; -/* number of entries set in n_measurements */ -static int n_measurements = 0; -static struct cached_bw_event_s { - uint32_t n_read; - uint32_t n_written; -} cached_bw_events[N_BW_EVENTS_TO_CACHE]; - -/** A second or more has elapsed: tell any interested control - * connections how much bandwidth we used. */ -int -control_event_bandwidth_used(uint32_t n_read, uint32_t n_written) -{ - cached_bw_events[next_measurement_idx].n_read = n_read; - cached_bw_events[next_measurement_idx].n_written = n_written; - if (++next_measurement_idx == N_BW_EVENTS_TO_CACHE) - next_measurement_idx = 0; - if (n_measurements < N_BW_EVENTS_TO_CACHE) - ++n_measurements; - - if (EVENT_IS_INTERESTING(EVENT_BANDWIDTH_USED)) { - send_control_event(EVENT_BANDWIDTH_USED, - "650 BW %lu %lu\r\n", - (unsigned long)n_read, - (unsigned long)n_written); - } - - return 0; -} - -STATIC char * -get_bw_samples(void) -{ - int i; - int idx = (next_measurement_idx + N_BW_EVENTS_TO_CACHE - n_measurements) - % N_BW_EVENTS_TO_CACHE; - tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); - - smartlist_t *elements = smartlist_new(); - - for (i = 0; i < n_measurements; ++i) { - tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); - const struct cached_bw_event_s *bwe = &cached_bw_events[idx]; - - smartlist_add_asprintf(elements, "%u,%u", - (unsigned)bwe->n_read, - (unsigned)bwe->n_written); - - idx = (idx + 1) % N_BW_EVENTS_TO_CACHE; - } - - char *result = smartlist_join_strings(elements, " ", 0, NULL); - - SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); - smartlist_free(elements); - - return result; -} - -/** Called when we are sending a log message to the controllers: suspend - * sending further log messages to the controllers until we're done. Used by - * CONN_LOG_PROTECT. */ -void -disable_control_logging(void) -{ - ++disable_log_messages; -} - -/** We're done sending a log message to the controllers: re-enable controller - * logging. Used by CONN_LOG_PROTECT. */ -void -enable_control_logging(void) -{ - if (--disable_log_messages < 0) - tor_assert(0); -} - -/** We got a log message: tell any interested control connections. */ -void -control_event_logmsg(int severity, uint32_t domain, const char *msg) -{ - int event; - - /* Don't even think of trying to add stuff to a buffer from a cpuworker - * thread. (See #25987 for plan to fix.) */ - if (! in_main_thread()) - return; - - if (disable_log_messages) - return; - - if (domain == LD_BUG && EVENT_IS_INTERESTING(EVENT_STATUS_GENERAL) && - severity <= LOG_NOTICE) { - char *esc = esc_for_log(msg); - ++disable_log_messages; - control_event_general_status(severity, "BUG REASON=%s", esc); - --disable_log_messages; - tor_free(esc); - } - - event = log_severity_to_event(severity); - if (event >= 0 && EVENT_IS_INTERESTING(event)) { - char *b = NULL; - const char *s; - if (strchr(msg, '\n')) { - char *cp; - b = tor_strdup(msg); - for (cp = b; *cp; ++cp) - if (*cp == '\r' || *cp == '\n') - *cp = ' '; - } - switch (severity) { - case LOG_DEBUG: s = "DEBUG"; break; - case LOG_INFO: s = "INFO"; break; - case LOG_NOTICE: s = "NOTICE"; break; - case LOG_WARN: s = "WARN"; break; - case LOG_ERR: s = "ERR"; break; - default: s = "UnknownLogSeverity"; break; - } - ++disable_log_messages; - send_control_event(event, "650 %s %s\r\n", s, b?b:msg); - if (severity == LOG_ERR) { - /* Force a flush, since we may be about to die horribly */ - queued_events_flush_all(1); - } - --disable_log_messages; - tor_free(b); - } -} - -/** - * Logging callback: called when there is a queued pending log callback. - */ -void -control_event_logmsg_pending(void) -{ - if (! in_main_thread()) { - /* We can't handle this case yet, since we're using a - * mainloop_event_t to invoke queued_events_flush_all. We ought to - * use a different mechanism instead: see #25987. - **/ - return; - } - tor_assert(flush_queued_events_event); - mainloop_event_activate(flush_queued_events_event); -} - -/** Called whenever we receive new router descriptors: tell any - * interested control connections. <b>routers</b> is a list of - * routerinfo_t's. - */ -int -control_event_descriptors_changed(smartlist_t *routers) -{ - char *msg; - - if (!EVENT_IS_INTERESTING(EVENT_NEW_DESC)) - return 0; - - { - smartlist_t *names = smartlist_new(); - char *ids; - SMARTLIST_FOREACH(routers, routerinfo_t *, ri, { - char *b = tor_malloc(MAX_VERBOSE_NICKNAME_LEN+1); - router_get_verbose_nickname(b, ri); - smartlist_add(names, b); - }); - ids = smartlist_join_strings(names, " ", 0, NULL); - tor_asprintf(&msg, "650 NEWDESC %s\r\n", ids); - send_control_event_string(EVENT_NEW_DESC, msg); - tor_free(ids); - tor_free(msg); - SMARTLIST_FOREACH(names, char *, cp, tor_free(cp)); - smartlist_free(names); - } - return 0; -} - -/** Called when an address mapping on <b>from</b> from changes to <b>to</b>. - * <b>expires</b> values less than 3 are special; see connection_edge.c. If - * <b>error</b> is non-NULL, it is an error code describing the failure - * mode of the mapping. - */ -int -control_event_address_mapped(const char *from, const char *to, time_t expires, - const char *error, const int cached) -{ - if (!EVENT_IS_INTERESTING(EVENT_ADDRMAP)) - return 0; - - if (expires < 3 || expires == TIME_MAX) - send_control_event(EVENT_ADDRMAP, - "650 ADDRMAP %s %s NEVER %s%s" - "CACHED=\"%s\"\r\n", - from, to, error?error:"", error?" ":"", - cached?"YES":"NO"); - else { - char buf[ISO_TIME_LEN+1]; - char buf2[ISO_TIME_LEN+1]; - format_local_iso_time(buf,expires); - format_iso_time(buf2,expires); - send_control_event(EVENT_ADDRMAP, - "650 ADDRMAP %s %s \"%s\"" - " %s%sEXPIRES=\"%s\" CACHED=\"%s\"\r\n", - from, to, buf, - error?error:"", error?" ":"", - buf2, cached?"YES":"NO"); - } - - return 0; -} - /** Cached liveness for network liveness events and GETINFO */ static int network_is_live = 0; -static int +int get_cached_network_liveness(void) { return network_is_live; } -static void +void set_cached_network_liveness(int liveness) { network_is_live = liveness; } -/** The network liveness has changed; this is called from circuitstats.c - * whenever we receive a cell, or when timeout expires and we assume the - * network is down. */ -int -control_event_network_liveness_update(int liveness) -{ - if (liveness > 0) { - if (get_cached_network_liveness() <= 0) { - /* Update cached liveness */ - set_cached_network_liveness(1); - log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS UP"); - send_control_event_string(EVENT_NETWORK_LIVENESS, - "650 NETWORK_LIVENESS UP\r\n"); - } - /* else was already live, no-op */ - } else { - if (get_cached_network_liveness() > 0) { - /* Update cached liveness */ - set_cached_network_liveness(0); - log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS DOWN"); - send_control_event_string(EVENT_NETWORK_LIVENESS, - "650 NETWORK_LIVENESS DOWN\r\n"); - } - /* else was already dead, no-op */ - } - - return 0; -} - -/** Helper function for NS-style events. Constructs and sends an event - * of type <b>event</b> with string <b>event_string</b> out of the set of - * networkstatuses <b>statuses</b>. Currently it is used for NS events - * and NEWCONSENSUS events. */ -static int -control_event_networkstatus_changed_helper(smartlist_t *statuses, - uint16_t event, - const char *event_string) -{ - smartlist_t *strs; - char *s, *esc = NULL; - if (!EVENT_IS_INTERESTING(event) || !smartlist_len(statuses)) - return 0; - - strs = smartlist_new(); - smartlist_add_strdup(strs, "650+"); - smartlist_add_strdup(strs, event_string); - smartlist_add_strdup(strs, "\r\n"); - SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs, - { - s = networkstatus_getinfo_helper_single(rs); - if (!s) continue; - smartlist_add(strs, s); - }); - - s = smartlist_join_strings(strs, "", 0, NULL); - write_escaped_data(s, strlen(s), &esc); - SMARTLIST_FOREACH(strs, char *, cp, tor_free(cp)); - smartlist_free(strs); - tor_free(s); - send_control_event_string(event, esc); - send_control_event_string(event, - "650 OK\r\n"); - - tor_free(esc); - return 0; -} - -/** Called when the routerstatus_ts <b>statuses</b> have changed: sends - * an NS event to any controller that cares. */ -int -control_event_networkstatus_changed(smartlist_t *statuses) -{ - return control_event_networkstatus_changed_helper(statuses, EVENT_NS, "NS"); -} - -/** Called when we get a new consensus networkstatus. Sends a NEWCONSENSUS - * event consisting of an NS-style line for each relay in the consensus. */ -int -control_event_newconsensus(const networkstatus_t *consensus) -{ - if (!control_event_is_interesting(EVENT_NEWCONSENSUS)) - return 0; - return control_event_networkstatus_changed_helper( - consensus->routerstatus_list, EVENT_NEWCONSENSUS, "NEWCONSENSUS"); -} - -/** Called when we compute a new circuitbuildtimeout */ -int -control_event_buildtimeout_set(buildtimeout_set_event_t type, - const char *args) -{ - const char *type_string = NULL; - - if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET)) - return 0; - - switch (type) { - case BUILDTIMEOUT_SET_EVENT_COMPUTED: - type_string = "COMPUTED"; - break; - case BUILDTIMEOUT_SET_EVENT_RESET: - type_string = "RESET"; - break; - case BUILDTIMEOUT_SET_EVENT_SUSPENDED: - type_string = "SUSPENDED"; - break; - case BUILDTIMEOUT_SET_EVENT_DISCARD: - type_string = "DISCARD"; - break; - case BUILDTIMEOUT_SET_EVENT_RESUME: - type_string = "RESUME"; - break; - default: - type_string = "UNKNOWN"; - break; - } - - send_control_event(EVENT_BUILDTIMEOUT_SET, - "650 BUILDTIMEOUT_SET %s %s\r\n", - type_string, args); - - return 0; -} - -/** Called when a signal has been processed from signal_callback */ -int -control_event_signal(uintptr_t signal_num) -{ - const char *signal_string = NULL; - - if (!control_event_is_interesting(EVENT_GOT_SIGNAL)) - return 0; - - switch (signal_num) { - case SIGHUP: - signal_string = "RELOAD"; - break; - case SIGUSR1: - signal_string = "DUMP"; - break; - case SIGUSR2: - signal_string = "DEBUG"; - break; - case SIGNEWNYM: - signal_string = "NEWNYM"; - break; - case SIGCLEARDNSCACHE: - signal_string = "CLEARDNSCACHE"; - break; - case SIGHEARTBEAT: - signal_string = "HEARTBEAT"; - break; - default: - log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal", - (unsigned long)signal_num); - return -1; - } - - send_control_event(EVENT_GOT_SIGNAL, "650 SIGNAL %s\r\n", - signal_string); - return 0; -} - -/** Called when a single local_routerstatus_t has changed: Sends an NS event - * to any controller that cares. */ -int -control_event_networkstatus_changed_single(const routerstatus_t *rs) -{ - smartlist_t *statuses; - int r; - - if (!EVENT_IS_INTERESTING(EVENT_NS)) - return 0; - - statuses = smartlist_new(); - smartlist_add(statuses, (void*)rs); - r = control_event_networkstatus_changed(statuses); - smartlist_free(statuses); - return r; -} - -/** Our own router descriptor has changed; tell any controllers that care. - */ -int -control_event_my_descriptor_changed(void) -{ - send_control_event(EVENT_DESCCHANGED, "650 DESCCHANGED\r\n"); - return 0; -} - -/** Helper: sends a status event where <b>type</b> is one of - * EVENT_STATUS_{GENERAL,CLIENT,SERVER}, where <b>severity</b> is one of - * LOG_{NOTICE,WARN,ERR}, and where <b>format</b> is a printf-style format - * string corresponding to <b>args</b>. */ -static int -control_event_status(int type, int severity, const char *format, va_list args) -{ - char *user_buf = NULL; - char format_buf[160]; - const char *status, *sev; - - switch (type) { - case EVENT_STATUS_GENERAL: - status = "STATUS_GENERAL"; - break; - case EVENT_STATUS_CLIENT: - status = "STATUS_CLIENT"; - break; - case EVENT_STATUS_SERVER: - status = "STATUS_SERVER"; - break; - default: - log_warn(LD_BUG, "Unrecognized status type %d", type); - return -1; - } - switch (severity) { - case LOG_NOTICE: - sev = "NOTICE"; - break; - case LOG_WARN: - sev = "WARN"; - break; - case LOG_ERR: - sev = "ERR"; - break; - default: - log_warn(LD_BUG, "Unrecognized status severity %d", severity); - return -1; - } - if (tor_snprintf(format_buf, sizeof(format_buf), "650 %s %s", - status, sev)<0) { - log_warn(LD_BUG, "Format string too long."); - return -1; - } - tor_vasprintf(&user_buf, format, args); - - send_control_event(type, "%s %s\r\n", format_buf, user_buf); - tor_free(user_buf); - return 0; -} - -#define CONTROL_EVENT_STATUS_BODY(event, sev) \ - int r; \ - do { \ - va_list ap; \ - if (!EVENT_IS_INTERESTING(event)) \ - return 0; \ - \ - va_start(ap, format); \ - r = control_event_status((event), (sev), format, ap); \ - va_end(ap); \ - } while (0) - -/** Format and send an EVENT_STATUS_GENERAL event whose main text is obtained - * by formatting the arguments using the printf-style <b>format</b>. */ -int -control_event_general_status(int severity, const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, severity); - return r; -} - -/** Format and send an EVENT_STATUS_GENERAL LOG_ERR event, and flush it to the - * controller(s) immediately. */ -int -control_event_general_error(const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, LOG_ERR); - /* Force a flush, since we may be about to die horribly */ - queued_events_flush_all(1); - return r; -} - -/** Format and send an EVENT_STATUS_CLIENT event whose main text is obtained - * by formatting the arguments using the printf-style <b>format</b>. */ -int -control_event_client_status(int severity, const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, severity); - return r; -} - -/** Format and send an EVENT_STATUS_CLIENT LOG_ERR event, and flush it to the - * controller(s) immediately. */ -int -control_event_client_error(const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, LOG_ERR); - /* Force a flush, since we may be about to die horribly */ - queued_events_flush_all(1); - return r; -} - -/** Format and send an EVENT_STATUS_SERVER event whose main text is obtained - * by formatting the arguments using the printf-style <b>format</b>. */ -int -control_event_server_status(int severity, const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, severity); - return r; -} - -/** Format and send an EVENT_STATUS_SERVER LOG_ERR event, and flush it to the - * controller(s) immediately. */ -int -control_event_server_error(const char *format, ...) -{ - CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, LOG_ERR); - /* Force a flush, since we may be about to die horribly */ - queued_events_flush_all(1); - return r; -} - -/** Called when the status of an entry guard with the given <b>nickname</b> - * and identity <b>digest</b> has changed to <b>status</b>: tells any - * controllers that care. */ -int -control_event_guard(const char *nickname, const char *digest, - const char *status) -{ - char hbuf[HEX_DIGEST_LEN+1]; - base16_encode(hbuf, sizeof(hbuf), digest, DIGEST_LEN); - if (!EVENT_IS_INTERESTING(EVENT_GUARD)) - return 0; - - { - char buf[MAX_VERBOSE_NICKNAME_LEN+1]; - const node_t *node = node_get_by_id(digest); - if (node) { - node_get_verbose_nickname(node, buf); - } else { - tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname); - } - send_control_event(EVENT_GUARD, - "650 GUARD ENTRY %s %s\r\n", buf, status); - } - return 0; -} - -/** 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) -{ - int i; - char *result; - smartlist_t *lines; - if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) || - smartlist_len(elements) == 0) { - return 0; - } - 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); - } else { - smartlist_add_asprintf(lines, "650-%s=%s", k, v); - } - } - result = smartlist_join_strings(lines, "\r\n", 0, NULL); - send_control_event(EVENT_CONF_CHANGED, - "650-CONF_CHANGED\r\n%s\r\n650 OK\r\n", result); - tor_free(result); - SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); - smartlist_free(lines); - return 0; -} - -/** Helper: Return a newly allocated string containing a path to the - * file where we store our authentication cookie. */ -char * -get_controller_cookie_file_name(void) -{ - const or_options_t *options = get_options(); - if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { - return tor_strdup(options->CookieAuthFile); - } else { - return get_datadir_fname("control_auth_cookie"); - } -} - -/* Initialize the cookie-based authentication system of the - * ControlPort. If <b>enabled</b> is 0, then disable the cookie - * authentication system. */ -int -init_control_cookie_authentication(int enabled) -{ - char *fname = NULL; - int retval; - - if (!enabled) { - authentication_cookie_is_set = 0; - return 0; - } - - fname = get_controller_cookie_file_name(); - retval = init_cookie_authentication(fname, "", /* no header */ - AUTHENTICATION_COOKIE_LEN, - get_options()->CookieAuthFileGroupReadable, - &authentication_cookie, - &authentication_cookie_is_set); - tor_free(fname); - return retval; -} - /** A copy of the process specifier of Tor's owning controller, or * NULL if this Tor instance is not currently owned by a process. */ static char *owning_controller_process_spec = NULL; @@ -7008,895 +601,12 @@ monitor_owning_controller_process(const char *process_spec) } } -/** Convert the name of a bootstrapping phase <b>s</b> into strings - * <b>tag</b> and <b>summary</b> suitable for display by the controller. */ -static int -bootstrap_status_to_string(bootstrap_status_t s, const char **tag, - const char **summary) -{ - switch (s) { - case BOOTSTRAP_STATUS_UNDEF: - *tag = "undef"; - *summary = "Undefined"; - break; - case BOOTSTRAP_STATUS_STARTING: - *tag = "starting"; - *summary = "Starting"; - break; - case BOOTSTRAP_STATUS_CONN_DIR: - *tag = "conn_dir"; - *summary = "Connecting to directory server"; - break; - case BOOTSTRAP_STATUS_HANDSHAKE: - *tag = "status_handshake"; - *summary = "Finishing handshake"; - break; - case BOOTSTRAP_STATUS_HANDSHAKE_DIR: - *tag = "handshake_dir"; - *summary = "Finishing handshake with directory server"; - break; - case BOOTSTRAP_STATUS_ONEHOP_CREATE: - *tag = "onehop_create"; - *summary = "Establishing an encrypted directory connection"; - break; - case BOOTSTRAP_STATUS_REQUESTING_STATUS: - *tag = "requesting_status"; - *summary = "Asking for networkstatus consensus"; - break; - case BOOTSTRAP_STATUS_LOADING_STATUS: - *tag = "loading_status"; - *summary = "Loading networkstatus consensus"; - break; - case BOOTSTRAP_STATUS_LOADING_KEYS: - *tag = "loading_keys"; - *summary = "Loading authority key certs"; - break; - case BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS: - *tag = "requesting_descriptors"; - /* XXXX this appears to incorrectly report internal on most loads */ - *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? - "Asking for relay descriptors for internal paths" : - "Asking for relay descriptors"; - break; - /* If we're sure there are no exits in the consensus, - * inform the controller by adding "internal" - * to the status summaries. - * (We only check this while loading descriptors, - * so we may not know in the earlier stages.) - * But if there are exits, we can't be sure whether - * we're creating internal or exit paths/circuits. - * XXXX Or should be use different tags or statuses - * for internal and exit/all? */ - case BOOTSTRAP_STATUS_LOADING_DESCRIPTORS: - *tag = "loading_descriptors"; - *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? - "Loading relay descriptors for internal paths" : - "Loading relay descriptors"; - break; - case BOOTSTRAP_STATUS_CONN_OR: - *tag = "conn_or"; - *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? - "Connecting to the Tor network internally" : - "Connecting to the Tor network"; - break; - case BOOTSTRAP_STATUS_HANDSHAKE_OR: - *tag = "handshake_or"; - *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? - "Finishing handshake with first hop of internal circuit" : - "Finishing handshake with first hop"; - break; - case BOOTSTRAP_STATUS_CIRCUIT_CREATE: - *tag = "circuit_create"; - *summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ? - "Establishing an internal Tor circuit" : - "Establishing a Tor circuit"; - break; - case BOOTSTRAP_STATUS_DONE: - *tag = "done"; - *summary = "Done"; - break; - default: -// log_warn(LD_BUG, "Unrecognized bootstrap status code %d", s); - *tag = *summary = "unknown"; - return -1; - } - return 0; -} - -/** What percentage through the bootstrap process are we? We remember - * this so we can avoid sending redundant bootstrap status events, and - * so we can guess context for the bootstrap messages which are - * ambiguous. It starts at 'undef', but gets set to 'starting' while - * Tor initializes. */ -static int bootstrap_percent = BOOTSTRAP_STATUS_UNDEF; - -/** Like bootstrap_percent, but only takes on the enumerated values in - * bootstrap_status_t. - */ -static int bootstrap_phase = BOOTSTRAP_STATUS_UNDEF; - -/** As bootstrap_percent, but holds the bootstrapping level at which we last - * logged a NOTICE-level message. We use this, plus BOOTSTRAP_PCT_INCREMENT, - * to avoid flooding the log with a new message every time we get a few more - * microdescriptors */ -static int notice_bootstrap_percent = 0; - -/** How many problems have we had getting to the next bootstrapping phase? - * These include failure to establish a connection to a Tor relay, - * failures to finish the TLS handshake, failures to validate the - * consensus document, etc. */ -static int bootstrap_problems = 0; - -/** We only tell the controller once we've hit a threshold of problems - * for the current phase. */ -#define BOOTSTRAP_PROBLEM_THRESHOLD 10 - -/** When our bootstrapping progress level changes, but our bootstrapping - * status has not advanced, we only log at NOTICE when we have made at least - * this much progress. - */ -#define BOOTSTRAP_PCT_INCREMENT 5 - -/** Do the actual logging and notifications for - * control_event_bootstrap(). Doesn't change any state beyond that. - */ -static void -control_event_bootstrap_core(int loglevel, bootstrap_status_t status, - int progress) -{ - char buf[BOOTSTRAP_MSG_LEN]; - const char *tag, *summary; - - bootstrap_status_to_string(status, &tag, &summary); - /* Locally reset status if there's incremental progress */ - if (progress) - status = progress; - - tor_log(loglevel, LD_CONTROL, - "Bootstrapped %d%%: %s", status, summary); - tor_snprintf(buf, sizeof(buf), - "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\"", - status, tag, summary); - tor_snprintf(last_sent_bootstrap_message, - sizeof(last_sent_bootstrap_message), - "NOTICE %s", buf); - control_event_client_status(LOG_NOTICE, "%s", buf); -} - -/** Called when Tor has made progress at bootstrapping its directory - * information and initial circuits. - * - * <b>status</b> is the new status, that is, what task we will be doing - * next. <b>progress</b> is zero if we just started this task, else it - * represents progress on the task. - */ -void -control_event_bootstrap(bootstrap_status_t status, int progress) -{ - int loglevel = LOG_NOTICE; - - if (bootstrap_percent == BOOTSTRAP_STATUS_DONE) - return; /* already bootstrapped; nothing to be done here. */ - - /* special case for handshaking status, since our TLS handshaking code - * can't distinguish what the connection is going to be for. */ - if (status == BOOTSTRAP_STATUS_HANDSHAKE) { - if (bootstrap_percent < BOOTSTRAP_STATUS_CONN_OR) { - status = BOOTSTRAP_STATUS_HANDSHAKE_DIR; - } else { - status = BOOTSTRAP_STATUS_HANDSHAKE_OR; - } - } - - if (status <= bootstrap_percent) { - /* If there's no new progress, return early. */ - if (!progress || progress <= bootstrap_percent) - return; - /* Log at INFO if not enough progress happened. */ - if (progress < notice_bootstrap_percent + BOOTSTRAP_PCT_INCREMENT) - loglevel = LOG_INFO; - } - - control_event_bootstrap_core(loglevel, status, progress); - - if (status > bootstrap_percent) { - bootstrap_phase = status; /* new milestone reached */ - bootstrap_percent = status; - } - if (progress > bootstrap_percent) { - /* incremental progress within a milestone */ - bootstrap_percent = progress; - bootstrap_problems = 0; /* Progress! Reset our problem counter. */ - } - if (loglevel == LOG_NOTICE && - bootstrap_percent > notice_bootstrap_percent) { - /* Remember that we gave a notice at this level. */ - notice_bootstrap_percent = bootstrap_percent; - } -} - -/** Flag whether we've opened an OR_CONN yet */ -static int bootstrap_first_orconn = 0; - -/** Like bootstrap_phase, but for (possibly deferred) directory progress */ -static int bootstrap_dir_phase = BOOTSTRAP_STATUS_UNDEF; - -/** Like bootstrap_problems, but for (possibly deferred) directory progress */ -static int bootstrap_dir_progress = BOOTSTRAP_STATUS_UNDEF; - -/** Defer directory info bootstrap events until we have successfully - * completed our first connection to a router. */ -void -control_event_boot_dir(bootstrap_status_t status, int progress) -{ - if (status > bootstrap_dir_progress) { - bootstrap_dir_progress = status; - bootstrap_dir_phase = status; - } - if (progress && progress >= bootstrap_dir_progress) { - bootstrap_dir_progress = progress; - } - - /* Don't report unless we have successfully opened at least one OR_CONN */ - if (!bootstrap_first_orconn) - return; - - control_event_bootstrap(status, progress); -} - -/** Set a flag to allow reporting of directory bootstrap progress. - * (Code that reports completion of an OR_CONN calls this.) Also, - * report directory progress so far. */ -void -control_event_boot_first_orconn(void) -{ - bootstrap_first_orconn = 1; - control_event_bootstrap(bootstrap_dir_phase, bootstrap_dir_progress); -} - -/** Called when Tor has failed to make bootstrapping progress in a way - * that indicates a problem. <b>warn</b> gives a human-readable hint - * as to why, and <b>reason</b> provides a controller-facing short - * tag. <b>conn</b> is the connection that caused this problem and - * can be NULL if a connection cannot be easily identified. - */ -void -control_event_bootstrap_problem(const char *warn, const char *reason, - const connection_t *conn, int dowarn) -{ - int status = bootstrap_percent; - const char *tag = "", *summary = ""; - char buf[BOOTSTRAP_MSG_LEN]; - const char *recommendation = "ignore"; - int severity; - char *or_id = NULL, *hostaddr = NULL; - or_connection_t *or_conn = NULL; - - /* bootstrap_percent must not be in "undefined" state here. */ - tor_assert(status >= 0); - - if (bootstrap_percent == 100) - return; /* already bootstrapped; nothing to be done here. */ - - bootstrap_problems++; - - if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) - dowarn = 1; - - /* Don't warn about our bootstrapping status if we are hibernating or - * shutting down. */ - if (we_are_hibernating()) - dowarn = 0; - - tor_assert(bootstrap_status_to_string(bootstrap_phase, &tag, &summary) == 0); - - severity = dowarn ? LOG_WARN : LOG_INFO; - - if (dowarn) - recommendation = "warn"; - - if (conn && conn->type == CONN_TYPE_OR) { - /* XXX TO_OR_CONN can't deal with const */ - or_conn = TO_OR_CONN((connection_t *)conn); - or_id = tor_strdup(hex_str(or_conn->identity_digest, DIGEST_LEN)); - } else { - or_id = tor_strdup("?"); - } - - if (conn) - tor_asprintf(&hostaddr, "%s:%d", conn->address, (int)conn->port); - else - hostaddr = tor_strdup("?"); - - log_fn(severity, - LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " - "count %d; recommendation %s; host %s at %s)", - status, summary, warn, reason, - bootstrap_problems, recommendation, - or_id, hostaddr); - - connection_or_report_broken_states(severity, LD_HANDSHAKE); - - tor_snprintf(buf, sizeof(buf), - "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " - "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s\"", - bootstrap_percent, tag, summary, warn, reason, bootstrap_problems, - recommendation, - or_id, hostaddr); - - tor_snprintf(last_sent_bootstrap_message, - sizeof(last_sent_bootstrap_message), - "WARN %s", buf); - control_event_client_status(LOG_WARN, "%s", buf); - - tor_free(hostaddr); - tor_free(or_id); -} - -/** Called when Tor has failed to make bootstrapping progress in a way - * that indicates a problem. <b>warn</b> gives a hint as to why, and - * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> - * is the connection that caused this problem. - */ -MOCK_IMPL(void, -control_event_bootstrap_prob_or, (const char *warn, int reason, - or_connection_t *or_conn)) -{ - int dowarn = 0; - - if (or_conn->have_noted_bootstrap_problem) - return; - - or_conn->have_noted_bootstrap_problem = 1; - - if (reason == END_OR_CONN_REASON_NO_ROUTE) - dowarn = 1; - - /* If we are using bridges and all our OR connections are now - closed, it means that we totally failed to connect to our - bridges. Throw a warning. */ - if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) - dowarn = 1; - - control_event_bootstrap_problem(warn, - orconn_end_reason_to_control_string(reason), - TO_CONN(or_conn), dowarn); -} - -/** We just generated a new summary of which countries we've seen clients - * from recently. Send a copy to the controller in case it wants to - * display it for the user. */ -void -control_event_clients_seen(const char *controller_str) -{ - send_control_event(EVENT_CLIENTS_SEEN, - "650 CLIENTS_SEEN %s\r\n", controller_str); -} - -/** A new pluggable transport called <b>transport_name</b> was - * launched on <b>addr</b>:<b>port</b>. <b>mode</b> is either - * "server" or "client" depending on the mode of the pluggable - * transport. - * "650" SP "TRANSPORT_LAUNCHED" SP Mode SP Name SP Address SP Port - */ -void -control_event_transport_launched(const char *mode, const char *transport_name, - tor_addr_t *addr, uint16_t port) -{ - send_control_event(EVENT_TRANSPORT_LAUNCHED, - "650 TRANSPORT_LAUNCHED %s %s %s %u\r\n", - mode, transport_name, fmt_addr(addr), port); -} - -/** Convert rendezvous auth type to string for HS_DESC control events - */ -const char * -rend_auth_type_to_string(rend_auth_type_t auth_type) -{ - const char *str; - - switch (auth_type) { - case REND_NO_AUTH: - str = "NO_AUTH"; - break; - case REND_BASIC_AUTH: - str = "BASIC_AUTH"; - break; - case REND_STEALTH_AUTH: - str = "STEALTH_AUTH"; - break; - default: - str = "UNKNOWN"; - } - - return str; -} - -/** Return a longname the node whose identity is <b>id_digest</b>. If - * node_get_by_id() returns NULL, base 16 encoding of <b>id_digest</b> is - * returned instead. - * - * This function is not thread-safe. Each call to this function invalidates - * previous values returned by this function. - */ -MOCK_IMPL(const char *, -node_describe_longname_by_id,(const char *id_digest)) -{ - static char longname[MAX_VERBOSE_NICKNAME_LEN+1]; - node_get_verbose_nickname_by_id(id_digest, longname); - return longname; -} - -/** Return either the onion address if the given pointer is a non empty - * string else the unknown string. */ -static const char * -rend_hsaddress_str_or_unknown(const char *onion_address) -{ - static const char *str_unknown = "UNKNOWN"; - const char *str_ret = str_unknown; - - /* No valid pointer, unknown it is. */ - if (!onion_address) { - goto end; - } - /* Empty onion address thus we don't know, unknown it is. */ - if (onion_address[0] == '\0') { - goto end; - } - /* All checks are good so return the given onion address. */ - str_ret = onion_address; - - end: - return str_ret; -} - -/** send HS_DESC requested event. - * - * <b>rend_query</b> is used to fetch requested onion address and auth type. - * <b>hs_dir</b> is the description of contacting hs directory. - * <b>desc_id_base32</b> is the ID of requested hs descriptor. - * <b>hsdir_index</b> is the HSDir fetch index value for v3, an hex string. - */ -void -control_event_hs_descriptor_requested(const char *onion_address, - rend_auth_type_t auth_type, - const char *id_digest, - const char *desc_id, - const char *hsdir_index) -{ - char *hsdir_index_field = NULL; - - if (BUG(!id_digest || !desc_id)) { - return; - } - - if (hsdir_index) { - tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); - } - - send_control_event(EVENT_HS_DESC, - "650 HS_DESC REQUESTED %s %s %s %s%s\r\n", - rend_hsaddress_str_or_unknown(onion_address), - rend_auth_type_to_string(auth_type), - node_describe_longname_by_id(id_digest), - desc_id, - hsdir_index_field ? hsdir_index_field : ""); - tor_free(hsdir_index_field); -} - -/** For an HS descriptor query <b>rend_data</b>, using the - * <b>onion_address</b> and HSDir fingerprint <b>hsdir_fp</b>, find out - * which descriptor ID in the query is the right one. - * - * Return a pointer of the binary descriptor ID found in the query's object - * or NULL if not found. */ -static const char * -get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) -{ - int replica; - const char *desc_id = NULL; - const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data); - - /* Possible if the fetch was done using a descriptor ID. This means that - * the HSFETCH command was used. */ - if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) { - desc_id = rend_data_v2->desc_id_fetch; - goto end; - } - - /* Without a directory fingerprint at this stage, we can't do much. */ - if (hsdir_fp == NULL) { - goto end; - } - - /* OK, we have an onion address so now let's find which descriptor ID - * is the one associated with the HSDir fingerprint. */ - for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; - replica++) { - const char *digest = rend_data_get_desc_id(rend_data, replica, NULL); - - SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) { - if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) { - /* Found it! This descriptor ID is the right one. */ - desc_id = digest; - goto end; - } - } SMARTLIST_FOREACH_END(fingerprint); - } - - end: - return desc_id; -} - -/** send HS_DESC CREATED event when a local service generates a descriptor. - * - * <b>onion_address</b> is service address. - * <b>desc_id</b> is the descriptor ID. - * <b>replica</b> is the the descriptor replica number. If it is negative, it - * is ignored. - */ -void -control_event_hs_descriptor_created(const char *onion_address, - const char *desc_id, - int replica) -{ - char *replica_field = NULL; - - if (BUG(!onion_address || !desc_id)) { - return; - } - - if (replica >= 0) { - tor_asprintf(&replica_field, " REPLICA=%d", replica); - } - - send_control_event(EVENT_HS_DESC, - "650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s%s\r\n", - onion_address, desc_id, - replica_field ? replica_field : ""); - tor_free(replica_field); -} - -/** send HS_DESC upload event. - * - * <b>onion_address</b> is service address. - * <b>hs_dir</b> is the description of contacting hs directory. - * <b>desc_id</b> is the ID of requested hs descriptor. - */ -void -control_event_hs_descriptor_upload(const char *onion_address, - const char *id_digest, - const char *desc_id, - const char *hsdir_index) -{ - char *hsdir_index_field = NULL; - - if (BUG(!onion_address || !id_digest || !desc_id)) { - return; - } - - if (hsdir_index) { - tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); - } - - send_control_event(EVENT_HS_DESC, - "650 HS_DESC UPLOAD %s UNKNOWN %s %s%s\r\n", - onion_address, - node_describe_longname_by_id(id_digest), - desc_id, - hsdir_index_field ? hsdir_index_field : ""); - tor_free(hsdir_index_field); -} - -/** send HS_DESC event after got response from hs directory. - * - * NOTE: this is an internal function used by following functions: - * control_event_hsv2_descriptor_received - * control_event_hsv2_descriptor_failed - * control_event_hsv3_descriptor_failed - * - * So do not call this function directly. - */ -static void -event_hs_descriptor_receive_end(const char *action, - const char *onion_address, - const char *desc_id, - rend_auth_type_t auth_type, - const char *hsdir_id_digest, - const char *reason) -{ - char *reason_field = NULL; - - if (BUG(!action || !onion_address)) { - return; - } - - if (reason) { - tor_asprintf(&reason_field, " REASON=%s", reason); - } - - send_control_event(EVENT_HS_DESC, - "650 HS_DESC %s %s %s %s%s%s\r\n", - action, - rend_hsaddress_str_or_unknown(onion_address), - rend_auth_type_to_string(auth_type), - hsdir_id_digest ? - node_describe_longname_by_id(hsdir_id_digest) : - "UNKNOWN", - desc_id ? desc_id : "", - reason_field ? reason_field : ""); - - tor_free(reason_field); -} - -/** send HS_DESC event after got response from hs directory. - * - * NOTE: this is an internal function used by following functions: - * control_event_hs_descriptor_uploaded - * control_event_hs_descriptor_upload_failed - * - * So do not call this function directly. - */ -void -control_event_hs_descriptor_upload_end(const char *action, - const char *onion_address, - const char *id_digest, - const char *reason) -{ - char *reason_field = NULL; - - if (BUG(!action || !id_digest)) { - return; - } - - if (reason) { - tor_asprintf(&reason_field, " REASON=%s", reason); - } - - send_control_event(EVENT_HS_DESC, - "650 HS_DESC %s %s UNKNOWN %s%s\r\n", - action, - rend_hsaddress_str_or_unknown(onion_address), - node_describe_longname_by_id(id_digest), - reason_field ? reason_field : ""); - - tor_free(reason_field); -} - -/** send HS_DESC RECEIVED event - * - * called when we successfully received a hidden service descriptor. - */ -void -control_event_hsv2_descriptor_received(const char *onion_address, - const rend_data_t *rend_data, - const char *hsdir_id_digest) -{ - char *desc_id_field = NULL; - const char *desc_id; - - if (BUG(!rend_data || !hsdir_id_digest || !onion_address)) { - return; - } - - desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); - if (desc_id != NULL) { - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - /* Set the descriptor ID digest to base32 so we can send it. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - /* Extra whitespace is needed before the value. */ - tor_asprintf(&desc_id_field, " %s", desc_id_base32); - } - - event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, - TO_REND_DATA_V2(rend_data)->auth_type, - hsdir_id_digest, NULL); - tor_free(desc_id_field); -} - -/* Send HS_DESC RECEIVED event - * - * Called when we successfully received a hidden service descriptor. */ -void -control_event_hsv3_descriptor_received(const char *onion_address, - const char *desc_id, - const char *hsdir_id_digest) -{ - char *desc_id_field = NULL; - - if (BUG(!onion_address || !desc_id || !hsdir_id_digest)) { - return; - } - - /* Because DescriptorID is an optional positional value, we need to add a - * whitespace before in order to not be next to the HsDir value. */ - tor_asprintf(&desc_id_field, " %s", desc_id); - - event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, - REND_NO_AUTH, hsdir_id_digest, NULL); - tor_free(desc_id_field); -} - -/** send HS_DESC UPLOADED event - * - * called when we successfully uploaded a hidden service descriptor. - */ -void -control_event_hs_descriptor_uploaded(const char *id_digest, - const char *onion_address) -{ - if (BUG(!id_digest)) { - return; - } - - control_event_hs_descriptor_upload_end("UPLOADED", onion_address, - id_digest, NULL); -} - -/** Send HS_DESC event to inform controller that query <b>rend_data</b> - * failed to retrieve hidden service descriptor from directory identified by - * <b>id_digest</b>. If NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, - * add it to REASON= field. - */ -void -control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, - const char *hsdir_id_digest, - const char *reason) -{ - char *desc_id_field = NULL; - const char *desc_id; - - if (BUG(!rend_data)) { - return; - } - - desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); - if (desc_id != NULL) { - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - /* Set the descriptor ID digest to base32 so we can send it. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - /* Extra whitespace is needed before the value. */ - tor_asprintf(&desc_id_field, " %s", desc_id_base32); - } - - event_hs_descriptor_receive_end("FAILED", rend_data_get_address(rend_data), - desc_id_field, - TO_REND_DATA_V2(rend_data)->auth_type, - hsdir_id_digest, reason); - tor_free(desc_id_field); -} - -/** Send HS_DESC event to inform controller that the query to - * <b>onion_address</b> failed to retrieve hidden service descriptor - * <b>desc_id</b> from directory identified by <b>hsdir_id_digest</b>. If - * NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, add it to REASON= - * field. */ -void -control_event_hsv3_descriptor_failed(const char *onion_address, - const char *desc_id, - const char *hsdir_id_digest, - const char *reason) -{ - char *desc_id_field = NULL; - - if (BUG(!onion_address || !desc_id || !reason)) { - return; - } - - /* Because DescriptorID is an optional positional value, we need to add a - * whitespace before in order to not be next to the HsDir value. */ - tor_asprintf(&desc_id_field, " %s", desc_id); - - event_hs_descriptor_receive_end("FAILED", onion_address, desc_id_field, - REND_NO_AUTH, hsdir_id_digest, reason); - tor_free(desc_id_field); -} - -/** Send HS_DESC_CONTENT event after completion of a successful fetch - * from hs directory. If <b>hsdir_id_digest</b> is NULL, it is replaced - * by "UNKNOWN". If <b>content</b> is NULL, it is replaced by an empty - * string. The <b>onion_address</b> or <b>desc_id</b> set to NULL will - * not trigger the control event. */ -void -control_event_hs_descriptor_content(const char *onion_address, - const char *desc_id, - const char *hsdir_id_digest, - const char *content) -{ - static const char *event_name = "HS_DESC_CONTENT"; - char *esc_content = NULL; - - if (!onion_address || !desc_id) { - log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, ", - onion_address, desc_id); - return; - } - - if (content == NULL) { - /* Point it to empty content so it can still be escaped. */ - content = ""; - } - write_escaped_data(content, strlen(content), &esc_content); - - send_control_event(EVENT_HS_DESC_CONTENT, - "650+%s %s %s %s\r\n%s650 OK\r\n", - event_name, - rend_hsaddress_str_or_unknown(onion_address), - desc_id, - hsdir_id_digest ? - node_describe_longname_by_id(hsdir_id_digest) : - "UNKNOWN", - esc_content); - tor_free(esc_content); -} - -/** Send HS_DESC event to inform controller upload of hidden service - * descriptor identified by <b>id_digest</b> failed. If <b>reason</b> - * is not NULL, add it to REASON= field. - */ -void -control_event_hs_descriptor_upload_failed(const char *id_digest, - const char *onion_address, - const char *reason) -{ - if (BUG(!id_digest)) { - return; - } - control_event_hs_descriptor_upload_end("FAILED", onion_address, - id_digest, reason); -} - /** Free any leftover allocated memory of the control.c subsystem. */ void control_free_all(void) { - smartlist_t *queued_events = NULL; - - stats_prev_n_read = stats_prev_n_written = 0; - - if (authentication_cookie) /* Free the auth cookie */ - tor_free(authentication_cookie); - if (detached_onion_services) { /* Free the detached onion services */ - SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); - smartlist_free(detached_onion_services); - } - - if (queued_control_events_lock) { - tor_mutex_acquire(queued_control_events_lock); - flush_queued_event_pending = 0; - queued_events = queued_control_events; - queued_control_events = NULL; - tor_mutex_release(queued_control_events_lock); - } - if (queued_events) { - SMARTLIST_FOREACH(queued_events, queued_event_t *, ev, - queued_event_free(ev)); - smartlist_free(queued_events); - } - if (flush_queued_events_event) { - mainloop_event_free(flush_queued_events_event); - flush_queued_events_event = NULL; - } - bootstrap_percent = BOOTSTRAP_STATUS_UNDEF; - bootstrap_phase = BOOTSTRAP_STATUS_UNDEF; - notice_bootstrap_percent = 0; - bootstrap_problems = 0; - bootstrap_first_orconn = 0; - bootstrap_dir_progress = BOOTSTRAP_STATUS_UNDEF; - bootstrap_dir_phase = BOOTSTRAP_STATUS_UNDEF; - authentication_cookie_is_set = 0; - global_event_mask = 0; - disable_log_messages = 0; - memset(last_sent_bootstrap_message, 0, sizeof(last_sent_bootstrap_message)); -} - -#ifdef TOR_UNIT_TESTS -/* For testing: change the value of global_event_mask */ -void -control_testing_set_global_event_mask(uint64_t mask) -{ - global_event_mask = mask; + control_auth_free_all(); + control_events_free_all(); + control_cmd_free_all(); + control_event_bootstrap_reset(); } -#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/control/control.h b/src/feature/control/control.h index a09c1cd11b..7e72b2736b 100644 --- a/src/feature/control/control.h +++ b/src/feature/control/control.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,76 +12,6 @@ #ifndef TOR_CONTROL_H #define TOR_CONTROL_H -/** Used to indicate the type of a circuit event passed to the controller. - * The various types are defined in control-spec.txt */ -typedef enum circuit_status_event_t { - CIRC_EVENT_LAUNCHED = 0, - CIRC_EVENT_BUILT = 1, - CIRC_EVENT_EXTENDED = 2, - CIRC_EVENT_FAILED = 3, - CIRC_EVENT_CLOSED = 4, -} circuit_status_event_t; - -/** Used to indicate the type of a CIRC_MINOR event passed to the controller. - * The various types are defined in control-spec.txt . */ -typedef enum circuit_status_minor_event_t { - CIRC_MINOR_EVENT_PURPOSE_CHANGED, - CIRC_MINOR_EVENT_CANNIBALIZED, -} circuit_status_minor_event_t; - -/** 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 { - STREAM_EVENT_SENT_CONNECT = 0, - STREAM_EVENT_SENT_RESOLVE = 1, - STREAM_EVENT_SUCCEEDED = 2, - STREAM_EVENT_FAILED = 3, - STREAM_EVENT_CLOSED = 4, - STREAM_EVENT_NEW = 5, - STREAM_EVENT_NEW_RESOLVE = 6, - STREAM_EVENT_FAILED_RETRIABLE = 7, - STREAM_EVENT_REMAP = 8 -} stream_status_event_t; - -/** Used to indicate the type of an OR connection event passed to the - * controller. The various types are defined in control-spec.txt */ -typedef enum or_conn_status_event_t { - OR_CONN_EVENT_LAUNCHED = 0, - OR_CONN_EVENT_CONNECTED = 1, - OR_CONN_EVENT_FAILED = 2, - OR_CONN_EVENT_CLOSED = 3, - OR_CONN_EVENT_NEW = 4, -} or_conn_status_event_t; - -/** Used to indicate the type of a buildtime event */ -typedef enum buildtimeout_set_event_t { - BUILDTIMEOUT_SET_EVENT_COMPUTED = 0, - BUILDTIMEOUT_SET_EVENT_RESET = 1, - BUILDTIMEOUT_SET_EVENT_SUSPENDED = 2, - BUILDTIMEOUT_SET_EVENT_DISCARD = 3, - BUILDTIMEOUT_SET_EVENT_RESUME = 4 -} buildtimeout_set_event_t; - -/** Enum describing various stages of bootstrapping, for use with controller - * bootstrap status events. The values range from 0 to 100. */ -typedef enum { - BOOTSTRAP_STATUS_UNDEF=-1, - BOOTSTRAP_STATUS_STARTING=0, - BOOTSTRAP_STATUS_CONN_DIR=5, - BOOTSTRAP_STATUS_HANDSHAKE=-2, - BOOTSTRAP_STATUS_HANDSHAKE_DIR=10, - BOOTSTRAP_STATUS_ONEHOP_CREATE=15, - BOOTSTRAP_STATUS_REQUESTING_STATUS=20, - BOOTSTRAP_STATUS_LOADING_STATUS=25, - BOOTSTRAP_STATUS_LOADING_KEYS=40, - BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS=45, - BOOTSTRAP_STATUS_LOADING_DESCRIPTORS=50, - BOOTSTRAP_STATUS_CONN_OR=80, - BOOTSTRAP_STATUS_HANDSHAKE_OR=85, - BOOTSTRAP_STATUS_CIRCUIT_CREATE=90, - BOOTSTRAP_STATUS_DONE=100 -} bootstrap_status_t; - control_connection_t *TO_CONTROL_CONN(connection_t *); #define CONTROL_CONN_STATE_MIN_ 1 @@ -92,18 +22,6 @@ control_connection_t *TO_CONTROL_CONN(connection_t *); #define CONTROL_CONN_STATE_NEEDAUTH 2 #define CONTROL_CONN_STATE_MAX_ 2 -/** Reason for remapping an AP connection's address: we have a cached - * answer. */ -#define REMAP_STREAM_SOURCE_CACHE 1 -/** Reason for remapping an AP connection's address: the exit node told us an - * answer. */ -#define REMAP_STREAM_SOURCE_EXIT 2 - -void control_initialize_event_queue(void); - -void control_update_global_event_mask(void); -void control_adjust_event_log_severity(void); - void control_ports_write_to_file(void); /** Log information about the connection <b>conn</b>, protecting it as with @@ -124,294 +42,28 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -#define EVENT_NS 0x000F -int control_event_is_interesting(int event); - -void control_per_second_events(void); -int control_any_per_second_event_enabled(void); - -int control_event_circuit_status(origin_circuit_t *circ, - circuit_status_event_t e, int reason); -int control_event_circuit_purpose_changed(origin_circuit_t *circ, - int old_purpose); -int control_event_circuit_cannibalized(origin_circuit_t *circ, - int old_purpose, - const struct timeval *old_tv_created); -int control_event_stream_status(entry_connection_t *conn, - stream_status_event_t e, - int reason); -int control_event_or_conn_status(or_connection_t *conn, - or_conn_status_event_t e, int reason); -int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); -int control_event_stream_bandwidth(edge_connection_t *edge_conn); -int control_event_stream_bandwidth_used(void); -int control_event_circ_bandwidth_used(void); -int control_event_circ_bandwidth_used_for_circ(origin_circuit_t *ocirc); -int control_event_conn_bandwidth(connection_t *conn); -int control_event_conn_bandwidth_used(void); -int control_event_circuit_cell_stats(void); -void control_event_logmsg(int severity, uint32_t domain, const char *msg); -void control_event_logmsg_pending(void); -int control_event_descriptors_changed(smartlist_t *routers); -int control_event_address_mapped(const char *from, const char *to, - time_t expires, const char *error, - const int cached); -int control_event_my_descriptor_changed(void); -int control_event_network_liveness_update(int liveness); -int control_event_networkstatus_changed(smartlist_t *statuses); - -int control_event_newconsensus(const networkstatus_t *consensus); -int control_event_networkstatus_changed_single(const routerstatus_t *rs); -int control_event_general_status(int severity, const char *format, ...) - CHECK_PRINTF(2,3); -int control_event_client_status(int severity, const char *format, ...) - CHECK_PRINTF(2,3); -int control_event_server_status(int severity, const char *format, ...) - CHECK_PRINTF(2,3); - -int control_event_general_error(const char *format, ...) - CHECK_PRINTF(1,2); -int control_event_client_error(const char *format, ...) - CHECK_PRINTF(1,2); -int control_event_server_error(const char *format, ...) - CHECK_PRINTF(1,2); - -int control_event_guard(const char *nickname, const char *digest, - const char *status); -int control_event_conf_changed(const smartlist_t *elements); -int control_event_buildtimeout_set(buildtimeout_set_event_t type, - const char *args); -int control_event_signal(uintptr_t signal); - -int init_control_cookie_authentication(int enabled); -char *get_controller_cookie_file_name(void); -struct config_line_t; -smartlist_t *decode_hashed_passwords(struct config_line_t *passwords); void disable_control_logging(void); void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); -void control_event_bootstrap(bootstrap_status_t status, int progress); -MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, - int reason, - or_connection_t *or_conn)); -void control_event_boot_dir(bootstrap_status_t status, int progress); -void control_event_boot_first_orconn(void); -void control_event_bootstrap_problem(const char *warn, const char *reason, - const connection_t *conn, int dowarn); - -void control_event_clients_seen(const char *controller_str); -void control_event_transport_launched(const char *mode, - const char *transport_name, - tor_addr_t *addr, uint16_t port); const char *rend_auth_type_to_string(rend_auth_type_t auth_type); -MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); -void control_event_hs_descriptor_requested(const char *onion_address, - rend_auth_type_t auth_type, - const char *id_digest, - const char *desc_id, - const char *hsdir_index); -void control_event_hs_descriptor_created(const char *onion_address, - const char *desc_id, - int replica); -void control_event_hs_descriptor_upload(const char *onion_address, - const char *desc_id, - const char *hs_dir, - const char *hsdir_index); -void control_event_hs_descriptor_upload_end(const char *action, - const char *onion_address, - const char *hs_dir, - const char *reason); -void control_event_hs_descriptor_uploaded(const char *hs_dir, - const char *onion_address); -/* Hidden service v2 HS_DESC specific. */ -void control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, - const char *id_digest, - const char *reason); -void control_event_hsv2_descriptor_received(const char *onion_address, - const rend_data_t *rend_data, - const char *id_digest); -/* Hidden service v3 HS_DESC specific. */ -void control_event_hsv3_descriptor_failed(const char *onion_address, - const char *desc_id, - const char *hsdir_id_digest, - const char *reason); -void control_event_hsv3_descriptor_received(const char *onion_address, - const char *desc_id, - const char *hsdir_id_digest); -void control_event_hs_descriptor_upload_failed(const char *hs_dir, - const char *onion_address, - const char *reason); -void control_event_hs_descriptor_content(const char *onion_address, - const char *desc_id, - const char *hsdir_fp, - const char *content); void control_free_all(void); -#ifdef CONTROL_PRIVATE -#include "lib/crypt_ops/crypto_ed25519.h" - -/* Recognized asynchronous event types. It's okay to expand this list - * because it is used both as a list of v0 event types, and as indices - * into the bitfield to determine which controllers want which events. - */ -/* This bitfield has no event zero 0x0000 */ -#define EVENT_MIN_ 0x0001 -#define EVENT_CIRCUIT_STATUS 0x0001 -#define EVENT_STREAM_STATUS 0x0002 -#define EVENT_OR_CONN_STATUS 0x0003 -#define EVENT_BANDWIDTH_USED 0x0004 -#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 -#define EVENT_NEW_DESC 0x0006 -#define EVENT_DEBUG_MSG 0x0007 -#define EVENT_INFO_MSG 0x0008 -#define EVENT_NOTICE_MSG 0x0009 -#define EVENT_WARN_MSG 0x000A -#define EVENT_ERR_MSG 0x000B -#define EVENT_ADDRMAP 0x000C -/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We - can reclaim 0x000D. */ -#define EVENT_DESCCHANGED 0x000E -/* Exposed above */ -// #define EVENT_NS 0x000F -#define EVENT_STATUS_CLIENT 0x0010 -#define EVENT_STATUS_SERVER 0x0011 -#define EVENT_STATUS_GENERAL 0x0012 -#define EVENT_GUARD 0x0013 -#define EVENT_STREAM_BANDWIDTH_USED 0x0014 -#define EVENT_CLIENTS_SEEN 0x0015 -#define EVENT_NEWCONSENSUS 0x0016 -#define EVENT_BUILDTIMEOUT_SET 0x0017 -#define EVENT_GOT_SIGNAL 0x0018 -#define EVENT_CONF_CHANGED 0x0019 -#define EVENT_CONN_BW 0x001A -#define EVENT_CELL_STATS 0x001B -/* UNUSED : 0x001C */ -#define EVENT_CIRC_BANDWIDTH_USED 0x001D -#define EVENT_TRANSPORT_LAUNCHED 0x0020 -#define EVENT_HS_DESC 0x0021 -#define EVENT_HS_DESC_CONTENT 0x0022 -#define EVENT_NETWORK_LIVENESS 0x0023 -#define EVENT_MAX_ 0x0023 +#ifdef CONTROL_MODULE_PRIVATE +struct signal_name_t { + int sig; + const char *signal_name; +}; +extern const struct signal_name_t signal_table[]; +int get_cached_network_liveness(void); +void set_cached_network_liveness(int liveness); +#endif /* defined(CONTROL_MODULE_PRIVATE) */ -/* sizeof(control_connection_t.event_mask) in bits, currently a uint64_t */ -#define EVENT_CAPACITY_ 0x0040 - -/* If EVENT_MAX_ ever hits 0x0040, we need to make the mask into a - * different structure, as it can only handle a maximum left shift of 1<<63. */ - -#if EVENT_MAX_ >= EVENT_CAPACITY_ -#error control_connection_t.event_mask has an event greater than its capacity +#ifdef CONTROL_PRIVATE +STATIC char *control_split_incoming_command(char *incoming_cmd, + size_t *data_len, + char **current_cmd_out); #endif -#define EVENT_MASK_(e) (((uint64_t)1)<<(e)) - -#define EVENT_MASK_NONE_ ((uint64_t)0x0) - -#define EVENT_MASK_ABOVE_MIN_ ((~((uint64_t)0x0)) << EVENT_MIN_) -#define EVENT_MASK_BELOW_MAX_ ((~((uint64_t)0x0)) \ - >> (EVENT_CAPACITY_ - EVENT_MAX_ \ - - EVENT_MIN_)) - -#define EVENT_MASK_ALL_ (EVENT_MASK_ABOVE_MIN_ \ - & EVENT_MASK_BELOW_MAX_) - -/* Used only by control.c and test.c */ -STATIC size_t write_escaped_data(const char *data, size_t len, char **out); -STATIC size_t read_escaped_data(const char *data, size_t len, char **out); - -#ifdef TOR_UNIT_TESTS -MOCK_DECL(STATIC void, - send_control_event_string,(uint16_t event, const char *msg)); - -MOCK_DECL(STATIC void, - queue_control_event_string,(uint16_t event, char *msg)); - -void control_testing_set_global_event_mask(uint64_t mask); -#endif /* defined(TOR_UNIT_TESTS) */ - -/** Helper structure: temporarily stores cell statistics for a circuit. */ -typedef struct cell_stats_t { - /** Number of cells added in app-ward direction by command. */ - uint64_t added_cells_appward[CELL_COMMAND_MAX_ + 1]; - /** Number of cells added in exit-ward direction by command. */ - uint64_t added_cells_exitward[CELL_COMMAND_MAX_ + 1]; - /** Number of cells removed in app-ward direction by command. */ - uint64_t removed_cells_appward[CELL_COMMAND_MAX_ + 1]; - /** Number of cells removed in exit-ward direction by command. */ - uint64_t removed_cells_exitward[CELL_COMMAND_MAX_ + 1]; - /** Total waiting time of cells in app-ward direction by command. */ - uint64_t total_time_appward[CELL_COMMAND_MAX_ + 1]; - /** Total waiting time of cells in exit-ward direction by command. */ - uint64_t total_time_exitward[CELL_COMMAND_MAX_ + 1]; -} cell_stats_t; -void sum_up_cell_stats_by_command(circuit_t *circ, - cell_stats_t *cell_stats); -void append_cell_stats_by_command(smartlist_t *event_parts, - const char *key, - const uint64_t *include_if_non_zero, - const uint64_t *number_to_include); -void format_cell_stats(char **event_string, circuit_t *circ, - cell_stats_t *cell_stats); -STATIC char *get_bw_samples(void); - -/* ADD_ONION secret key to create an ephemeral service. The command supports - * multiple versions so this union stores the key and passes it to the HS - * subsystem depending on the requested version. */ -typedef union add_onion_secret_key_t { - /* Hidden service v2 secret key. */ - crypto_pk_t *v2; - /* Hidden service v3 secret key. */ - ed25519_secret_key_t *v3; -} add_onion_secret_key_t; - -STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, - const char **key_new_alg_out, - char **key_new_blob_out, - add_onion_secret_key_t *decoded_key, - int *hs_version, char **err_msg_out); - -STATIC rend_authorized_client_t * -add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out); - -STATIC int getinfo_helper_onions( - control_connection_t *control_conn, - const char *question, - char **answer, - const char **errmsg); -STATIC void getinfo_helper_downloads_networkstatus( - const char *flavor, - download_status_t **dl_to_emit, - const char **errmsg); -STATIC void getinfo_helper_downloads_cert( - const char *fp_sk_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg); -STATIC void getinfo_helper_downloads_desc( - const char *desc_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg); -STATIC void getinfo_helper_downloads_bridge( - const char *bridge_req, - download_status_t **dl_to_emit, - smartlist_t **digest_list, - const char **errmsg); -STATIC int getinfo_helper_downloads( - control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg); -STATIC int getinfo_helper_dir( - control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg); -STATIC int getinfo_helper_current_time( - control_connection_t *control_conn, - const char *question, char **answer, - const char **errmsg); - -#endif /* defined(CONTROL_PRIVATE) */ - #endif /* !defined(TOR_CONTROL_H) */ diff --git a/src/feature/control/control_auth.c b/src/feature/control/control_auth.c new file mode 100644 index 0000000000..b60623ab5c --- /dev/null +++ b/src/feature/control/control_auth.c @@ -0,0 +1,441 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_auth.c + * \brief Authentication for Tor's control-socket interface. + **/ + +#include "core/or/or.h" +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "feature/control/control.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_cmd_args_st.h" +#include "feature/control/control_connection_st.h" +#include "feature/control/control_proto.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" +#include "lib/encoding/qstring.h" + +#include "lib/crypt_ops/crypto_s2k.h" + +/** If we're using cookie-type authentication, how long should our cookies be? + */ +#define AUTHENTICATION_COOKIE_LEN 32 + +/** If true, we've set authentication_cookie to a secret code and + * stored it to disk. */ +static int authentication_cookie_is_set = 0; +/** If authentication_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 *authentication_cookie = NULL; + +#define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ + "Tor safe cookie authentication server-to-controller hash" +#define SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT \ + "Tor safe cookie authentication controller-to-server hash" +#define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_controller_cookie_file_name(void) +{ + const or_options_t *options = get_options(); + if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { + return tor_strdup(options->CookieAuthFile); + } else { + return get_datadir_fname("control_auth_cookie"); + } +} + +/* Initialize the cookie-based authentication system of the + * ControlPort. If <b>enabled</b> is 0, then disable the cookie + * authentication system. */ +int +init_control_cookie_authentication(int enabled) +{ + char *fname = NULL; + int retval; + + if (!enabled) { + authentication_cookie_is_set = 0; + return 0; + } + + fname = get_controller_cookie_file_name(); + retval = init_cookie_authentication(fname, "", /* no header */ + AUTHENTICATION_COOKIE_LEN, + get_options()->CookieAuthFileGroupReadable, + &authentication_cookie, + &authentication_cookie_is_set); + tor_free(fname); + return retval; +} + +/** Decode the hashed, base64'd passwords stored in <b>passwords</b>. + * Return a smartlist of acceptable passwords (unterminated strings of + * length S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on + * failure. + */ +smartlist_t * +decode_hashed_passwords(config_line_t *passwords) +{ + char decoded[64]; + config_line_t *cl; + smartlist_t *sl = smartlist_new(); + + tor_assert(passwords); + + for (cl = passwords; cl; cl = cl->next) { + const char *hashed = cl->value; + + if (!strcmpstart(hashed, "16:")) { + if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3)) + != S2K_RFC2440_SPECIFIER_LEN + DIGEST_LEN + || strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) { + goto err; + } + } else { + if (base64_decode(decoded, sizeof(decoded), hashed, strlen(hashed)) + != S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) { + goto err; + } + } + smartlist_add(sl, + tor_memdup(decoded, S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)); + } + + return sl; + + err: + SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp)); + smartlist_free(sl); + return NULL; +} + +const control_cmd_syntax_t authchallenge_syntax = { + .min_args = 1, + .max_args = 1, + .accept_keywords=true, + .kvline_flags=KV_OMIT_KEYS|KV_QUOTED_QSTRING, + .store_raw_body=true +}; + +/** Called when we get an AUTHCHALLENGE command. */ +int +handle_control_authchallenge(control_connection_t *conn, + const control_cmd_args_t *args) +{ + char *client_nonce; + size_t client_nonce_len; + char server_hash[DIGEST256_LEN]; + char server_hash_encoded[HEX_DIGEST256_LEN+1]; + char server_nonce[SAFECOOKIE_SERVER_NONCE_LEN]; + char server_nonce_encoded[(2*SAFECOOKIE_SERVER_NONCE_LEN) + 1]; + + if (strcasecmp(smartlist_get(args->args, 0), "SAFECOOKIE")) { + control_write_endreply(conn, 513, + "AUTHCHALLENGE only supports SAFECOOKIE " + "authentication"); + goto fail; + } + if (!authentication_cookie_is_set) { + control_write_endreply(conn, 515, "Cookie authentication is disabled"); + goto fail; + } + if (args->kwargs == NULL || args->kwargs->next != NULL) { + control_write_endreply(conn, 512, + "Wrong number of arguments for AUTHCHALLENGE"); + goto fail; + } + if (strcmp(args->kwargs->key, "")) { + control_write_endreply(conn, 512, + "AUTHCHALLENGE does not accept keyword " + "arguments."); + goto fail; + } + + bool contains_quote = strchr(args->raw_body, '\"'); + if (contains_quote) { + /* The nonce was quoted */ + client_nonce = tor_strdup(args->kwargs->value); + client_nonce_len = strlen(client_nonce); + } else { + /* The nonce was should be in hex. */ + const char *hex_nonce = args->kwargs->value; + client_nonce_len = strlen(hex_nonce) / 2; + client_nonce = tor_malloc(client_nonce_len); + if (base16_decode(client_nonce, client_nonce_len, hex_nonce, + strlen(hex_nonce)) != (int)client_nonce_len) { + control_write_endreply(conn, 513, "Invalid base16 client nonce"); + tor_free(client_nonce); + goto fail; + } + } + + crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); + + /* Now compute and send the server-to-controller response, and the + * server's nonce. */ + tor_assert(authentication_cookie != NULL); + + { + size_t tmp_len = (AUTHENTICATION_COOKIE_LEN + + client_nonce_len + + SAFECOOKIE_SERVER_NONCE_LEN); + char *tmp = tor_malloc_zero(tmp_len); + char *client_hash = tor_malloc_zero(DIGEST256_LEN); + memcpy(tmp, authentication_cookie, AUTHENTICATION_COOKIE_LEN); + memcpy(tmp + AUTHENTICATION_COOKIE_LEN, client_nonce, client_nonce_len); + memcpy(tmp + AUTHENTICATION_COOKIE_LEN + client_nonce_len, + server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT, + strlen(SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT), + tmp, + tmp_len); + + crypto_hmac_sha256(client_hash, + SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT, + strlen(SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT), + tmp, + tmp_len); + + conn->safecookie_client_hash = client_hash; + + tor_free(tmp); + } + + 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)); + + control_printf_endreply(conn, 250, + "AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s", + server_hash_encoded, + server_nonce_encoded); + + tor_free(client_nonce); + return 0; + fail: + connection_mark_for_close(TO_CONN(conn)); + return -1; +} + +const control_cmd_syntax_t authenticate_syntax = { + .max_args = 0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_KEYS|KV_QUOTED_QSTRING, + .store_raw_body=true +}; + +/** Called when we get an AUTHENTICATE message. Check whether the + * authentication is valid, and if so, update the connection's state to + * OPEN. Reply with DONE or ERROR. + */ +int +handle_control_authenticate(control_connection_t *conn, + const control_cmd_args_t *args) +{ + bool used_quoted_string = false; + const or_options_t *options = get_options(); + const char *errstr = "Unknown error"; + char *password; + size_t password_len; + int bad_cookie=0, bad_password=0; + smartlist_t *sl = NULL; + + if (args->kwargs == NULL) { + password = tor_strdup(""); + password_len = 0; + } else if (args->kwargs->next) { + control_write_endreply(conn, 512, "Too many arguments to AUTHENTICATE."); + connection_mark_for_close(TO_CONN(conn)); + return 0; + } else if (strcmp(args->kwargs->key, "")) { + control_write_endreply(conn, 512, + "AUTHENTICATE does not accept keyword arguments."); + connection_mark_for_close(TO_CONN(conn)); + return 0; + } else if (strchr(args->raw_body, '\"')) { + used_quoted_string = true; + password = tor_strdup(args->kwargs->value); + password_len = strlen(password); + } else { + const char *hex_passwd = args->kwargs->value; + password_len = strlen(hex_passwd) / 2; + password = tor_malloc(password_len+1); + if (base16_decode(password, password_len+1, hex_passwd, strlen(hex_passwd)) + != (int) password_len) { + control_write_endreply(conn, 551, + "Invalid hexadecimal encoding. Maybe you tried a plain text " + "password? If so, the standard requires that you put it in " + "double quotes."); + connection_mark_for_close(TO_CONN(conn)); + tor_free(password); + return 0; + } + } + + if (conn->safecookie_client_hash != NULL) { + /* The controller has chosen safe cookie authentication; the only + * acceptable authentication value is the controller-to-server + * response. */ + + tor_assert(authentication_cookie_is_set); + + if (password_len != DIGEST256_LEN) { + log_warn(LD_CONTROL, + "Got safe cookie authentication response with wrong length " + "(%d)", (int)password_len); + errstr = "Wrong length for safe cookie response."; + goto err; + } + + if (tor_memneq(conn->safecookie_client_hash, password, DIGEST256_LEN)) { + log_warn(LD_CONTROL, + "Got incorrect safe cookie authentication response"); + errstr = "Safe cookie response did not match expected value."; + goto err; + } + + tor_free(conn->safecookie_client_hash); + goto ok; + } + + if (!options->CookieAuthentication && !options->HashedControlPassword && + !options->HashedControlSessionPassword) { + /* if Tor doesn't demand any stronger authentication, then + * the controller can get in with anything. */ + goto ok; + } + + if (options->CookieAuthentication) { + int also_password = options->HashedControlPassword != NULL || + options->HashedControlSessionPassword != NULL; + if (password_len != AUTHENTICATION_COOKIE_LEN) { + if (!also_password) { + log_warn(LD_CONTROL, "Got authentication cookie with wrong length " + "(%d)", (int)password_len); + errstr = "Wrong length on authentication cookie."; + goto err; + } + bad_cookie = 1; + } else if (tor_memneq(authentication_cookie, password, password_len)) { + if (!also_password) { + log_warn(LD_CONTROL, "Got mismatched authentication cookie"); + errstr = "Authentication cookie did not match expected value."; + goto err; + } + bad_cookie = 1; + } else { + goto ok; + } + } + + if (options->HashedControlPassword || + options->HashedControlSessionPassword) { + int bad = 0; + smartlist_t *sl_tmp; + char received[DIGEST_LEN]; + int also_cookie = options->CookieAuthentication; + sl = smartlist_new(); + if (options->HashedControlPassword) { + sl_tmp = decode_hashed_passwords(options->HashedControlPassword); + if (!sl_tmp) + bad = 1; + else { + smartlist_add_all(sl, sl_tmp); + smartlist_free(sl_tmp); + } + } + if (options->HashedControlSessionPassword) { + sl_tmp = decode_hashed_passwords(options->HashedControlSessionPassword); + if (!sl_tmp) + bad = 1; + else { + smartlist_add_all(sl, sl_tmp); + smartlist_free(sl_tmp); + } + } + if (bad) { + if (!also_cookie) { + log_warn(LD_BUG, + "Couldn't decode HashedControlPassword: invalid base16"); + errstr="Couldn't decode HashedControlPassword value in configuration."; + goto err; + } + bad_password = 1; + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + sl = NULL; + } else { + SMARTLIST_FOREACH(sl, char *, expected, + { + secret_to_key_rfc2440(received,DIGEST_LEN, + password,password_len,expected); + if (tor_memeq(expected + S2K_RFC2440_SPECIFIER_LEN, + received, DIGEST_LEN)) + goto ok; + }); + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + sl = NULL; + + if (used_quoted_string) + errstr = "Password did not match HashedControlPassword value from " + "configuration"; + else + errstr = "Password did not match HashedControlPassword value from " + "configuration. Maybe you tried a plain text password? " + "If so, the standard requires that you put it in double quotes."; + bad_password = 1; + if (!also_cookie) + goto err; + } + } + + /** We only get here if both kinds of authentication failed. */ + tor_assert(bad_password && bad_cookie); + log_warn(LD_CONTROL, "Bad password or authentication cookie on controller."); + errstr = "Password did not match HashedControlPassword *or* authentication " + "cookie."; + + err: + tor_free(password); + control_printf_endreply(conn, 515, "Authentication failed: %s", errstr); + connection_mark_for_close(TO_CONN(conn)); + if (sl) { /* clean up */ + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + } + return 0; + ok: + log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT + ")", conn->base_.s); + send_control_done(conn); + conn->base_.state = CONTROL_CONN_STATE_OPEN; + tor_free(password); + if (sl) { /* clean up */ + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + } + return 0; +} + +void +control_auth_free_all(void) +{ + if (authentication_cookie) /* Free the auth cookie */ + tor_free(authentication_cookie); + authentication_cookie_is_set = 0; +} diff --git a/src/feature/control/control_auth.h b/src/feature/control/control_auth.h new file mode 100644 index 0000000000..d4c1dd78a7 --- /dev/null +++ b/src/feature/control/control_auth.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_auth.h + * \brief Header file for control_auth.c. + **/ + +#ifndef TOR_CONTROL_AUTH_H +#define TOR_CONTROL_AUTH_H + +struct control_cmd_args_t; +struct control_cmd_syntax_t; + +int init_control_cookie_authentication(int enabled); +char *get_controller_cookie_file_name(void); +struct config_line_t; +smartlist_t *decode_hashed_passwords(struct config_line_t *passwords); + +int handle_control_authchallenge(control_connection_t *conn, + const struct control_cmd_args_t *args); +int handle_control_authenticate(control_connection_t *conn, + const struct control_cmd_args_t *args); +void control_auth_free_all(void); + +extern const struct control_cmd_syntax_t authchallenge_syntax; +extern const struct control_cmd_syntax_t authenticate_syntax; + +#endif /* !defined(TOR_CONTROL_AUTH_H) */ diff --git a/src/feature/control/control_bootstrap.c b/src/feature/control/control_bootstrap.c new file mode 100644 index 0000000000..2e78fad690 --- /dev/null +++ b/src/feature/control/control_bootstrap.c @@ -0,0 +1,383 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_bootstrap.c + * \brief Provide bootstrap progress events for the control port. + */ +#include "core/or/or.h" + +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "core/or/connection_or.h" +#include "core/or/connection_st.h" +#include "core/or/or_connection_st.h" +#include "core/or/reasons.h" +#include "feature/control/control_events.h" +#include "feature/hibernate/hibernate.h" +#include "lib/malloc/malloc.h" + +/** A sufficiently large size to record the last bootstrap phase string. */ +#define BOOTSTRAP_MSG_LEN 1024 + +/** What was the last bootstrap phase message we sent? We keep track + * of this so we can respond to getinfo status/bootstrap-phase queries. */ +static char last_sent_bootstrap_message[BOOTSTRAP_MSG_LEN]; + +/** Table to convert bootstrap statuses to strings. */ +static const struct { + bootstrap_status_t status; + const char *tag; + const char *summary; +} boot_to_str_tab[] = { + { BOOTSTRAP_STATUS_UNDEF, "undef", "Undefined" }, + { BOOTSTRAP_STATUS_STARTING, "starting", "Starting" }, + + /* Initial connection to any relay */ + + { BOOTSTRAP_STATUS_CONN_PT, "conn_pt", "Connecting to pluggable transport" }, + { BOOTSTRAP_STATUS_CONN_DONE_PT, "conn_done_pt", + "Connected to pluggable transport" }, + { BOOTSTRAP_STATUS_CONN_PROXY, "conn_proxy", "Connecting to proxy" }, + { BOOTSTRAP_STATUS_CONN_DONE_PROXY, "conn_done_proxy", + "Connected to proxy" }, + { BOOTSTRAP_STATUS_CONN, "conn", "Connecting to a relay" }, + { BOOTSTRAP_STATUS_CONN_DONE, "conn_done", "Connected to a relay" }, + { BOOTSTRAP_STATUS_HANDSHAKE, "handshake", + "Handshaking with a relay" }, + { BOOTSTRAP_STATUS_HANDSHAKE_DONE, "handshake_done", + "Handshake with a relay done" }, + + /* Loading directory info */ + + { BOOTSTRAP_STATUS_ONEHOP_CREATE, "onehop_create", + "Establishing an encrypted directory connection" }, + { BOOTSTRAP_STATUS_REQUESTING_STATUS, "requesting_status", + "Asking for networkstatus consensus" }, + { BOOTSTRAP_STATUS_LOADING_STATUS, "loading_status", + "Loading networkstatus consensus" }, + { BOOTSTRAP_STATUS_LOADING_KEYS, "loading_keys", + "Loading authority key certs" }, + { BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, "requesting_descriptors", + "Asking for relay descriptors" }, + { BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, "loading_descriptors", + "Loading relay descriptors" }, + { BOOTSTRAP_STATUS_ENOUGH_DIRINFO, "enough_dirinfo", + "Loaded enough directory info to build circuits" }, + + /* Connecting to a relay for AP circuits */ + + { BOOTSTRAP_STATUS_AP_CONN_PT, "ap_conn_pt", + "Connecting to pluggable transport to build circuits" }, + { BOOTSTRAP_STATUS_AP_CONN_DONE_PT, "ap_conn_done_pt", + "Connected to pluggable transport to build circuits" }, + { BOOTSTRAP_STATUS_AP_CONN_PROXY, "ap_conn_proxy", + "Connecting to proxy to build circuits" }, + { BOOTSTRAP_STATUS_AP_CONN_DONE_PROXY, "ap_conn_done_proxy", + "Connected to proxy to build circuits" }, + { BOOTSTRAP_STATUS_AP_CONN, "ap_conn", + "Connecting to a relay to build circuits" }, + { BOOTSTRAP_STATUS_AP_CONN_DONE, "ap_conn_done", + "Connected to a relay to build circuits" }, + { BOOTSTRAP_STATUS_AP_HANDSHAKE, "ap_handshake", + "Finishing handshake with a relay to build circuits" }, + { BOOTSTRAP_STATUS_AP_HANDSHAKE_DONE, "ap_handshake_done", + "Handshake finished with a relay to build circuits" }, + + /* Creating AP circuits */ + + { BOOTSTRAP_STATUS_CIRCUIT_CREATE, "circuit_create", + "Establishing a Tor circuit" }, + { BOOTSTRAP_STATUS_DONE, "done", "Done" }, +}; +#define N_BOOT_TO_STR (sizeof(boot_to_str_tab)/sizeof(boot_to_str_tab[0])) + +/** Convert the name of a bootstrapping phase <b>s</b> into strings + * <b>tag</b> and <b>summary</b> suitable for display by the controller. */ +static int +bootstrap_status_to_string(bootstrap_status_t s, const char **tag, + const char **summary) +{ + for (size_t i = 0; i < N_BOOT_TO_STR; i++) { + if (s == boot_to_str_tab[i].status) { + *tag = boot_to_str_tab[i].tag; + *summary = boot_to_str_tab[i].summary; + return 0; + } + } + + *tag = *summary = "unknown"; + return -1; +} + +/** What percentage through the bootstrap process are we? We remember + * this so we can avoid sending redundant bootstrap status events, and + * so we can guess context for the bootstrap messages which are + * ambiguous. It starts at 'undef', but gets set to 'starting' while + * Tor initializes. */ +static int bootstrap_percent = BOOTSTRAP_STATUS_UNDEF; + +/** Like bootstrap_percent, but only takes on the enumerated values in + * bootstrap_status_t. + */ +static int bootstrap_phase = BOOTSTRAP_STATUS_UNDEF; + +/** As bootstrap_percent, but holds the bootstrapping level at which we last + * logged a NOTICE-level message. We use this, plus BOOTSTRAP_PCT_INCREMENT, + * to avoid flooding the log with a new message every time we get a few more + * microdescriptors */ +static int notice_bootstrap_percent = 0; + +/** How many problems have we had getting to the next bootstrapping phase? + * These include failure to establish a connection to a Tor relay, + * failures to finish the TLS handshake, failures to validate the + * consensus document, etc. */ +static int bootstrap_problems = 0; + +/** We only tell the controller once we've hit a threshold of problems + * for the current phase. */ +#define BOOTSTRAP_PROBLEM_THRESHOLD 10 + +/** When our bootstrapping progress level changes, but our bootstrapping + * status has not advanced, we only log at NOTICE when we have made at least + * this much progress. + */ +#define BOOTSTRAP_PCT_INCREMENT 5 + +/** Do the actual logging and notifications for + * control_event_bootstrap(). Doesn't change any state beyond that. + */ +static void +control_event_bootstrap_core(int loglevel, bootstrap_status_t status, + int progress) +{ + char buf[BOOTSTRAP_MSG_LEN]; + const char *tag, *summary; + + bootstrap_status_to_string(status, &tag, &summary); + /* Locally reset status if there's incremental progress */ + if (progress) + status = progress; + + tor_log(loglevel, LD_CONTROL, + "Bootstrapped %d%% (%s): %s", status, tag, summary); + tor_snprintf(buf, sizeof(buf), + "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\"", + status, tag, summary); + tor_snprintf(last_sent_bootstrap_message, + sizeof(last_sent_bootstrap_message), + "NOTICE %s", buf); + control_event_client_status(LOG_NOTICE, "%s", buf); +} + +/** Called when Tor has made progress at bootstrapping its directory + * information and initial circuits. + * + * <b>status</b> is the new status, that is, what task we will be doing + * next. <b>progress</b> is zero if we just started this task, else it + * represents progress on the task. + */ +void +control_event_bootstrap(bootstrap_status_t status, int progress) +{ + int loglevel = LOG_NOTICE; + + if (bootstrap_percent == BOOTSTRAP_STATUS_DONE) + return; /* already bootstrapped; nothing to be done here. */ + + if (status <= bootstrap_percent) { + /* If there's no new progress, return early. */ + if (!progress || progress <= bootstrap_percent) + return; + /* Log at INFO if not enough progress happened. */ + if (progress < notice_bootstrap_percent + BOOTSTRAP_PCT_INCREMENT) + loglevel = LOG_INFO; + } + + control_event_bootstrap_core(loglevel, status, progress); + + if (status > bootstrap_percent) { + bootstrap_phase = status; /* new milestone reached */ + bootstrap_percent = status; + } + if (progress > bootstrap_percent) { + /* incremental progress within a milestone */ + bootstrap_percent = progress; + bootstrap_problems = 0; /* Progress! Reset our problem counter. */ + } + if (loglevel == LOG_NOTICE && + bootstrap_percent > notice_bootstrap_percent) { + /* Remember that we gave a notice at this level. */ + notice_bootstrap_percent = bootstrap_percent; + } +} + +/** Flag whether we've opened an OR_CONN yet */ +static int bootstrap_first_orconn = 0; + +/** Like bootstrap_phase, but for (possibly deferred) directory progress */ +static int bootstrap_dir_phase = BOOTSTRAP_STATUS_UNDEF; + +/** Like bootstrap_problems, but for (possibly deferred) directory progress */ +static int bootstrap_dir_progress = BOOTSTRAP_STATUS_UNDEF; + +/** Defer directory info bootstrap events until we have successfully + * completed our first connection to a router. */ +void +control_event_boot_dir(bootstrap_status_t status, int progress) +{ + if (status > bootstrap_dir_progress) { + bootstrap_dir_progress = status; + bootstrap_dir_phase = status; + } + if (progress && progress >= bootstrap_dir_progress) { + bootstrap_dir_progress = progress; + } + + /* Don't report unless we have successfully opened at least one OR_CONN */ + if (!bootstrap_first_orconn) + return; + + control_event_bootstrap(status, progress); +} + +/** Set a flag to allow reporting of directory bootstrap progress. + * (Code that reports completion of an OR_CONN calls this.) Also, + * report directory progress so far. */ +void +control_event_boot_first_orconn(void) +{ + bootstrap_first_orconn = 1; + control_event_bootstrap(bootstrap_dir_phase, bootstrap_dir_progress); +} + +/** Called when Tor has failed to make bootstrapping progress in a way + * that indicates a problem. <b>warn</b> gives a human-readable hint + * as to why, and <b>reason</b> provides a controller-facing short + * tag. <b>conn</b> is the connection that caused this problem and + * can be NULL if a connection cannot be easily identified. + */ +void +control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn) +{ + int status = bootstrap_percent; + const char *tag = "", *summary = ""; + char buf[BOOTSTRAP_MSG_LEN]; + const char *recommendation = "ignore"; + int severity; + char *or_id = NULL, *hostaddr = NULL; + or_connection_t *or_conn = NULL; + + /* bootstrap_percent must not be in "undefined" state here. */ + tor_assert(status >= 0); + + if (bootstrap_percent == 100) + return; /* already bootstrapped; nothing to be done here. */ + + bootstrap_problems++; + + if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) + dowarn = 1; + + /* Don't warn about our bootstrapping status if we are hibernating or + * shutting down. */ + if (we_are_hibernating()) + dowarn = 0; + + tor_assert(bootstrap_status_to_string(bootstrap_phase, &tag, &summary) == 0); + + severity = dowarn ? LOG_WARN : LOG_INFO; + + if (dowarn) + recommendation = "warn"; + + if (conn && conn->type == CONN_TYPE_OR) { + /* XXX TO_OR_CONN can't deal with const */ + or_conn = TO_OR_CONN((connection_t *)conn); + or_id = tor_strdup(hex_str(or_conn->identity_digest, DIGEST_LEN)); + } else { + or_id = tor_strdup("?"); + } + + if (conn) + tor_asprintf(&hostaddr, "%s:%d", conn->address, (int)conn->port); + else + hostaddr = tor_strdup("?"); + + log_fn(severity, + LD_CONTROL, "Problem bootstrapping. Stuck at %d%% (%s): %s. (%s; %s; " + "count %d; recommendation %s; host %s at %s)", + status, tag, summary, warn, reason, + bootstrap_problems, recommendation, + or_id, hostaddr); + + connection_or_report_broken_states(severity, LD_HANDSHAKE); + + tor_snprintf(buf, sizeof(buf), + "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " + "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s\"", + bootstrap_percent, tag, summary, warn, reason, bootstrap_problems, + recommendation, + or_id, hostaddr); + + tor_snprintf(last_sent_bootstrap_message, + sizeof(last_sent_bootstrap_message), + "WARN %s", buf); + control_event_client_status(LOG_WARN, "%s", buf); + + tor_free(hostaddr); + tor_free(or_id); +} + +/** Called when Tor has failed to make bootstrapping progress in a way + * that indicates a problem. <b>warn</b> gives a hint as to why, and + * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> + * is the connection that caused this problem. + */ +MOCK_IMPL(void, +control_event_bootstrap_prob_or, (const char *warn, int reason, + or_connection_t *or_conn)) +{ + int dowarn = 0; + + if (or_conn->have_noted_bootstrap_problem) + return; + + or_conn->have_noted_bootstrap_problem = 1; + + if (reason == END_OR_CONN_REASON_NO_ROUTE) + dowarn = 1; + + /* If we are using bridges and all our OR connections are now + closed, it means that we totally failed to connect to our + bridges. Throw a warning. */ + if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) + dowarn = 1; + + control_event_bootstrap_problem(warn, + orconn_end_reason_to_control_string(reason), + TO_CONN(or_conn), dowarn); +} + +/** Return a copy of the last sent bootstrap message. */ +char * +control_event_boot_last_msg(void) +{ + return tor_strdup(last_sent_bootstrap_message); +} + +/** Reset bootstrap tracking state. */ +void +control_event_bootstrap_reset(void) +{ + bootstrap_percent = BOOTSTRAP_STATUS_UNDEF; + bootstrap_phase = BOOTSTRAP_STATUS_UNDEF; + notice_bootstrap_percent = 0; + bootstrap_problems = 0; + bootstrap_first_orconn = 0; + bootstrap_dir_progress = BOOTSTRAP_STATUS_UNDEF; + bootstrap_dir_phase = BOOTSTRAP_STATUS_UNDEF; + memset(last_sent_bootstrap_message, 0, sizeof(last_sent_bootstrap_message)); +} diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c new file mode 100644 index 0000000000..b3f61549ad --- /dev/null +++ b/src/feature/control/control_cmd.c @@ -0,0 +1,2422 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_cmd.c + * \brief Implement various commands for Tor's control-socket interface. + **/ + +#define CONTROL_MODULE_PRIVATE +#define CONTROL_CMD_PRIVATE +#define CONTROL_EVENTS_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" +#include "lib/confmgt/confmgt.h" +#include "app/main/main.h" +#include "core/mainloop/connection.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/circuituse.h" +#include "core/or/connection_edge.h" +#include "feature/client/addressmap.h" +#include "feature/client/dnsserv.h" +#include "feature/client/entrynodes.h" +#include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_hs.h" +#include "feature/control/control_events.h" +#include "feature/control/control_getinfo.h" +#include "feature/control/control_proto.h" +#include "feature/hs/hs_control.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerinfo.h" +#include "feature/nodelist/routerlist.h" +#include "feature/rend/rendclient.h" +#include "feature/rend/rendcommon.h" +#include "feature/rend/rendparse.h" +#include "feature/rend/rendservice.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" + +#include "core/or/cpath_build_state_st.h" +#include "core/or/entry_connection_st.h" +#include "core/or/origin_circuit_st.h" +#include "core/or/socks_request_st.h" +#include "feature/control/control_cmd_args_st.h" +#include "feature/control/control_connection_st.h" +#include "feature/nodelist/node_st.h" +#include "feature/nodelist/routerinfo_st.h" +#include "feature/rend/rend_authorized_client_st.h" +#include "feature/rend/rend_encoded_v2_service_descriptor_st.h" +#include "feature/rend/rend_service_descriptor_st.h" + +static int control_setconf_helper(control_connection_t *conn, + const control_cmd_args_t *args, + int use_defaults); + +/** Yield true iff <b>s</b> is the state of a control_connection_t that has + * finished authentication and is accepting commands. */ +#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) + +/** + * Release all storage held in <b>args</b> + **/ +void +control_cmd_args_free_(control_cmd_args_t *args) +{ + if (! args) + return; + + if (args->args) { + SMARTLIST_FOREACH(args->args, char *, c, tor_free(c)); + smartlist_free(args->args); + } + config_free_lines(args->kwargs); + tor_free(args->cmddata); + + tor_free(args); +} + +/** Erase all memory held in <b>args</b>. */ +void +control_cmd_args_wipe(control_cmd_args_t *args) +{ + if (!args) + return; + + if (args->args) { + SMARTLIST_FOREACH(args->args, char *, c, memwipe(c, 0, strlen(c))); + } + for (config_line_t *line = args->kwargs; line; line = line->next) { + memwipe(line->key, 0, strlen(line->key)); + memwipe(line->value, 0, strlen(line->value)); + } + if (args->cmddata) + memwipe(args->cmddata, 0, args->cmddata_len); +} + +/** + * Return true iff any element of the NULL-terminated <b>array</b> matches + * <b>kwd</b>. Case-insensitive. + **/ +static bool +string_array_contains_keyword(const char **array, const char *kwd) +{ + for (unsigned i = 0; array[i]; ++i) { + if (! strcasecmp(array[i], kwd)) + return true; + } + return false; +} + +/** Helper for argument parsing: check whether the keyword arguments just + * parsed in <b>result</b> were well-formed according to <b>syntax</b>. + * + * On success, return 0. On failure, return -1 and set *<b>error_out</b> + * to a newly allocated error string. + **/ +static int +kvline_check_keyword_args(const control_cmd_args_t *result, + const control_cmd_syntax_t *syntax, + char **error_out) +{ + if (result->kwargs == NULL) { + tor_asprintf(error_out, "Cannot parse keyword argument(s)"); + return -1; + } + + if (! syntax->allowed_keywords) { + /* All keywords are permitted. */ + return 0; + } + + /* Check for unpermitted arguments */ + const config_line_t *line; + for (line = result->kwargs; line; line = line->next) { + if (! string_array_contains_keyword(syntax->allowed_keywords, + line->key)) { + tor_asprintf(error_out, "Unrecognized keyword argument %s", + escaped(line->key)); + return -1; + } + } + + return 0; +} + +/** + * Helper: parse the arguments to a command according to <b>syntax</b>. On + * success, set *<b>error_out</b> to NULL and return a newly allocated + * control_cmd_args_t. On failure, set *<b>error_out</b> to newly allocated + * error string, and return NULL. + **/ +STATIC control_cmd_args_t * +control_cmd_parse_args(const char *command, + const control_cmd_syntax_t *syntax, + size_t body_len, + const char *body, + char **error_out) +{ + *error_out = NULL; + control_cmd_args_t *result = tor_malloc_zero(sizeof(control_cmd_args_t)); + const char *cmdline; + char *cmdline_alloc = NULL; + tor_assert(syntax->max_args < INT_MAX || syntax->max_args == UINT_MAX); + + result->command = command; + + if (syntax->store_raw_body) { + tor_assert(body[body_len] == 0); + result->raw_body = body; + } + + const char *eol = memchr(body, '\n', body_len); + if (syntax->want_cmddata) { + if (! eol || (eol+1) == body+body_len) { + *error_out = tor_strdup("Empty body"); + goto err; + } + cmdline_alloc = tor_memdup_nulterm(body, eol-body); + cmdline = cmdline_alloc; + ++eol; + result->cmddata_len = read_escaped_data(eol, (body+body_len)-eol, + &result->cmddata); + } else { + if (eol && (eol+1) != body+body_len) { + *error_out = tor_strdup("Unexpected body"); + goto err; + } + cmdline = body; + } + + result->args = smartlist_new(); + smartlist_split_string(result->args, cmdline, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, + (int)(syntax->max_args+1)); + size_t n_args = smartlist_len(result->args); + if (n_args < syntax->min_args) { + tor_asprintf(error_out, "Need at least %u argument(s)", + syntax->min_args); + goto err; + } else if (n_args > syntax->max_args && ! syntax->accept_keywords) { + tor_asprintf(error_out, "Cannot accept more than %u argument(s)", + syntax->max_args); + goto err; + } + + if (n_args > syntax->max_args) { + /* We have extra arguments after the positional arguments, and we didn't + treat them as an error, so they must count as keyword arguments: Either + K=V pairs, or flags, or both. */ + tor_assert(n_args == syntax->max_args + 1); + tor_assert(syntax->accept_keywords); + char *remainder = smartlist_pop_last(result->args); + result->kwargs = kvline_parse(remainder, syntax->kvline_flags); + tor_free(remainder); + if (kvline_check_keyword_args(result, syntax, error_out) < 0) { + goto err; + } + } + + tor_assert_nonfatal(*error_out == NULL); + goto done; + err: + tor_assert_nonfatal(*error_out != NULL); + control_cmd_args_free(result); + done: + tor_free(cmdline_alloc); + return result; +} + +/** + * Return true iff <b>lines</b> contains <b>flags</b> as a no-value + * (keyword-only) entry. + **/ +static bool +config_lines_contain_flag(const config_line_t *lines, const char *flag) +{ + const config_line_t *line = config_line_find_case(lines, flag); + return line && !strcmp(line->value, ""); +} + +static const control_cmd_syntax_t setconf_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS|KV_QUOTED, +}; + +/** Called when we receive a SETCONF message: parse the body and try + * to update our configuration. Reply with a DONE or ERROR message. + * Modifies the contents of body.*/ +static int +handle_control_setconf(control_connection_t *conn, + const control_cmd_args_t *args) +{ + return control_setconf_helper(conn, args, 0); +} + +static const control_cmd_syntax_t resetconf_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS|KV_QUOTED, +}; + +/** Called when we receive a RESETCONF message: parse the body and try + * to update our configuration. Reply with a DONE or ERROR message. + * Modifies the contents of body. */ +static int +handle_control_resetconf(control_connection_t *conn, + const control_cmd_args_t *args) +{ + return control_setconf_helper(conn, args, 1); +} + +static const control_cmd_syntax_t getconf_syntax = { + .max_args=UINT_MAX +}; + +/** Called when we receive a GETCONF message. Parse the request, and + * reply with a CONFVALUE or an ERROR message */ +static int +handle_control_getconf(control_connection_t *conn, + const control_cmd_args_t *args) +{ + const smartlist_t *questions = args->args; + smartlist_t *answers = smartlist_new(); + smartlist_t *unrecognized = smartlist_new(); + const or_options_t *options = get_options(); + + SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { + if (!option_is_recognized(q)) { + control_reply_add_printf(unrecognized, 552, + "Unrecognized configuration key \"%s\"", q); + } else { + config_line_t *answer = option_get_assignment(options,q); + if (!answer) { + const char *name = option_get_canonical_name(q); + control_reply_add_one_kv(answers, 250, KV_OMIT_VALS, name, ""); + } + + while (answer) { + config_line_t *next; + control_reply_add_one_kv(answers, 250, KV_RAW, answer->key, + answer->value); + next = answer->next; + tor_free(answer->key); + tor_free(answer->value); + tor_free(answer); + answer = next; + } + } + } SMARTLIST_FOREACH_END(q); + + if (smartlist_len(unrecognized)) { + control_write_reply_lines(conn, unrecognized); + } else if (smartlist_len(answers)) { + control_write_reply_lines(conn, answers); + } else { + send_control_done(conn); + } + + control_reply_free(answers); + control_reply_free(unrecognized); + return 0; +} + +static const control_cmd_syntax_t loadconf_syntax = { + .want_cmddata = true +}; + +/** Called when we get a +LOADCONF message. */ +static int +handle_control_loadconf(control_connection_t *conn, + const control_cmd_args_t *args) +{ + setopt_err_t retval; + char *errstring = NULL; + + retval = options_init_from_string(NULL, args->cmddata, + CMD_RUN_TOR, NULL, &errstring); + + if (retval != SETOPT_OK) + log_warn(LD_CONTROL, + "Controller gave us config file that didn't validate: %s", + errstring); + +#define SEND_ERRMSG(code, msg) \ + control_printf_endreply(conn, code, msg "%s%s", \ + errstring ? ": " : "", \ + errstring ? errstring : "") + switch (retval) { + case SETOPT_ERR_PARSE: + SEND_ERRMSG(552, "Invalid config file"); + break; + case SETOPT_ERR_TRANSITION: + SEND_ERRMSG(553, "Transition not allowed"); + break; + case SETOPT_ERR_SETTING: + SEND_ERRMSG(553, "Unable to set option"); + break; + case SETOPT_ERR_MISC: + default: + SEND_ERRMSG(550, "Unable to load config"); + break; + case SETOPT_OK: + send_control_done(conn); + break; + } +#undef SEND_ERRMSG + tor_free(errstring); + return 0; +} + +static const control_cmd_syntax_t setevents_syntax = { + .max_args = UINT_MAX +}; + +/** Called when we get a SETEVENTS message: update conn->event_mask, + * and reply with DONE or ERROR. */ +static int +handle_control_setevents(control_connection_t *conn, + const control_cmd_args_t *args) +{ + int event_code; + event_mask_t event_mask = 0; + const smartlist_t *events = args->args; + + SMARTLIST_FOREACH_BEGIN(events, const char *, ev) + { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); + continue; + } else { + int i; + event_code = -1; + + for (i = 0; control_event_table[i].event_name != NULL; ++i) { + if (!strcasecmp(ev, control_event_table[i].event_name)) { + event_code = control_event_table[i].event_code; + break; + } + } + + if (event_code == -1) { + control_printf_endreply(conn, 552, "Unrecognized event \"%s\"", ev); + return 0; + } + } + event_mask |= (((event_mask_t)1) << event_code); + } + SMARTLIST_FOREACH_END(ev); + + conn->event_mask = event_mask; + + control_update_global_event_mask(); + send_control_done(conn); + return 0; +} + +static const control_cmd_syntax_t saveconf_syntax = { + .max_args = 0, + .accept_keywords = true, + .kvline_flags=KV_OMIT_VALS, +}; + +/** Called when we get a SAVECONF command. Try to flush the current options to + * disk, and report success or failure. */ +static int +handle_control_saveconf(control_connection_t *conn, + const control_cmd_args_t *args) +{ + bool force = config_lines_contain_flag(args->kwargs, "FORCE"); + const or_options_t *options = get_options(); + if ((!force && options->IncludeUsed) || options_save_current() < 0) { + control_write_endreply(conn, 551, + "Unable to write configuration to disk."); + } else { + send_control_done(conn); + } + return 0; +} + +static const control_cmd_syntax_t signal_syntax = { + .min_args = 1, + .max_args = 1, +}; + +/** Called when we get a SIGNAL command. React to the provided signal, and + * report success or failure. (If the signal results in a shutdown, success + * may not be reported.) */ +static int +handle_control_signal(control_connection_t *conn, + const control_cmd_args_t *args) +{ + int sig = -1; + int i; + + tor_assert(smartlist_len(args->args) == 1); + const char *s = smartlist_get(args->args, 0); + + for (i = 0; signal_table[i].signal_name != NULL; ++i) { + if (!strcasecmp(s, signal_table[i].signal_name)) { + sig = signal_table[i].sig; + break; + } + } + + if (sig < 0) + control_printf_endreply(conn, 552, "Unrecognized signal code \"%s\"", s); + if (sig < 0) + return 0; + + send_control_done(conn); + /* Flush the "done" first if the signal might make us shut down. */ + if (sig == SIGTERM || sig == SIGINT) + connection_flush(TO_CONN(conn)); + + activate_signal(sig); + + return 0; +} + +static const control_cmd_syntax_t takeownership_syntax = { + .max_args = UINT_MAX, // This should probably become zero. XXXXX +}; + +/** Called when we get a TAKEOWNERSHIP command. Mark this connection + * as an owning connection, so that we will exit if the connection + * closes. */ +static int +handle_control_takeownership(control_connection_t *conn, + const control_cmd_args_t *args) +{ + (void)args; + + conn->is_owning_control_connection = 1; + + log_info(LD_CONTROL, "Control connection %d has taken ownership of this " + "Tor instance.", + (int)(conn->base_.s)); + + send_control_done(conn); + return 0; +} + +static const control_cmd_syntax_t dropownership_syntax = { + .max_args = UINT_MAX, // This should probably become zero. XXXXX +}; + +/** Called when we get a DROPOWNERSHIP command. Mark this connection + * as a non-owning connection, so that we will not exit if the connection + * closes. */ +static int +handle_control_dropownership(control_connection_t *conn, + const control_cmd_args_t *args) +{ + (void)args; + + conn->is_owning_control_connection = 0; + + log_info(LD_CONTROL, "Control connection %d has dropped ownership of this " + "Tor instance.", + (int)(conn->base_.s)); + + send_control_done(conn); + return 0; +} + +/** Given a text circuit <b>id</b>, return the corresponding circuit. */ +static origin_circuit_t * +get_circ(const char *id) +{ + uint32_t n_id; + int ok; + n_id = (uint32_t) tor_parse_ulong(id, 10, 0, UINT32_MAX, &ok, NULL); + if (!ok) + return NULL; + return circuit_get_by_global_id(n_id); +} + +/** Given a text stream <b>id</b>, return the corresponding AP connection. */ +static entry_connection_t * +get_stream(const char *id) +{ + uint64_t n_id; + int ok; + connection_t *conn; + n_id = tor_parse_uint64(id, 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) + return NULL; + conn = connection_get_by_global_id(n_id); + if (!conn || conn->type != CONN_TYPE_AP || conn->marked_for_close) + return NULL; + return TO_ENTRY_CONN(conn); +} + +/** Helper for setconf and resetconf. Acts like setconf, except + * it passes <b>use_defaults</b> on to options_trial_assign(). Modifies the + * contents of body. + */ +static int +control_setconf_helper(control_connection_t *conn, + const control_cmd_args_t *args, + int use_defaults) +{ + setopt_err_t opt_err; + char *errstring = NULL; + const unsigned flags = + CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0); + + // We need a copy here, since confmgt.c wants to canonicalize cases. + config_line_t *lines = config_lines_dup(args->kwargs); + + opt_err = options_trial_assign(lines, flags, &errstring); + { +#define SEND_ERRMSG(code, msg) \ + control_printf_endreply(conn, code, msg ": %s", errstring); + + switch (opt_err) { + case SETOPT_ERR_MISC: + SEND_ERRMSG(552, "Unrecognized option"); + break; + case SETOPT_ERR_PARSE: + SEND_ERRMSG(513, "Unacceptable option value"); + break; + case SETOPT_ERR_TRANSITION: + SEND_ERRMSG(553, "Transition not allowed"); + break; + case SETOPT_ERR_SETTING: + default: + SEND_ERRMSG(553, "Unable to set option"); + break; + case SETOPT_OK: + config_free_lines(lines); + send_control_done(conn); + return 0; + } +#undef SEND_ERRMSG + log_warn(LD_CONTROL, + "Controller gave us config lines that didn't validate: %s", + errstring); + config_free_lines(lines); + tor_free(errstring); + return 0; + } +} + +/** Return true iff <b>addr</b> is unusable as a mapaddress target because of + * containing funny characters. */ +static int +address_is_invalid_mapaddress_target(const char *addr) +{ + if (!strcmpstart(addr, "*.")) + return address_is_invalid_destination(addr+2, 1); + else + return address_is_invalid_destination(addr, 1); +} + +static const control_cmd_syntax_t mapaddress_syntax = { + // no positional arguments are expected + .max_args=0, + // an arbitrary number of K=V entries are supported. + .accept_keywords=true, +}; + +/** Called when we get a MAPADDRESS command; try to bind all listed addresses, + * and report success or failure. */ +static int +handle_control_mapaddress(control_connection_t *conn, + const control_cmd_args_t *args) +{ + smartlist_t *reply; + char *r; + size_t sz; + + reply = smartlist_new(); + const config_line_t *line; + for (line = args->kwargs; line; line = line->next) { + const char *from = line->key; + const char *to = line->value; + { + if (address_is_invalid_mapaddress_target(to)) { + smartlist_add_asprintf(reply, + "512-syntax error: invalid address '%s'", to); + log_warn(LD_CONTROL, + "Skipping invalid argument '%s' in MapAddress msg", to); + } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0") || + !strcmp(from, "::")) { + const char type = + !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : + (!strcmp(from, "0.0.0.0") ? RESOLVED_TYPE_IPV4 : RESOLVED_TYPE_IPV6); + const char *address = addressmap_register_virtual_address( + type, tor_strdup(to)); + if (!address) { + smartlist_add_asprintf(reply, + "451-resource exhausted: skipping '%s=%s'", from,to); + log_warn(LD_CONTROL, + "Unable to allocate address for '%s' in MapAddress msg", + safe_str_client(to)); + } else { + smartlist_add_asprintf(reply, "250-%s=%s", address, to); + } + } else { + const char *msg; + if (addressmap_register_auto(from, to, 1, + ADDRMAPSRC_CONTROLLER, &msg) < 0) { + smartlist_add_asprintf(reply, + "512-syntax error: invalid address mapping " + " '%s=%s': %s", from, to, msg); + log_warn(LD_CONTROL, + "Skipping invalid argument '%s=%s' in MapAddress msg: %s", + from, to, msg); + } else { + smartlist_add_asprintf(reply, "250-%s=%s", from, to); + } + } + } + } + + if (smartlist_len(reply)) { + ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; + r = smartlist_join_strings(reply, "\r\n", 1, &sz); + connection_buf_add(r, sz, TO_CONN(conn)); + tor_free(r); + } else { + control_write_endreply(conn, 512, "syntax error: " + "not enough arguments to mapaddress."); + } + + SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); + smartlist_free(reply); + return 0; +} + +/** Given a string, convert it to a circuit purpose. */ +static uint8_t +circuit_purpose_from_string(const char *string) +{ + if (!strcasecmpstart(string, "purpose=")) + string += strlen("purpose="); + + if (!strcasecmp(string, "general")) + return CIRCUIT_PURPOSE_C_GENERAL; + else if (!strcasecmp(string, "controller")) + return CIRCUIT_PURPOSE_CONTROLLER; + else + return CIRCUIT_PURPOSE_UNKNOWN; +} + +static const control_cmd_syntax_t extendcircuit_syntax = { + .min_args=1, + .max_args=1, // see note in function + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS +}; + +/** Called when we get an EXTENDCIRCUIT message. Try to extend the listed + * circuit, and report success or failure. */ +static int +handle_control_extendcircuit(control_connection_t *conn, + const control_cmd_args_t *args) +{ + smartlist_t *router_nicknames=smartlist_new(), *nodes=NULL; + origin_circuit_t *circ = NULL; + uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; + const config_line_t *kwargs = args->kwargs; + const char *circ_id = smartlist_get(args->args, 0); + const char *path_str = NULL; + char *path_str_alloc = NULL; + + /* The syntax for this command is unfortunate. The second argument is + optional, and is a comma-separated list long-format fingerprints, which + can (historically!) contain an equals sign. + + Here we check the second argument to see if it's a path, and if so we + remove it from the kwargs list and put it in path_str. + */ + if (kwargs) { + const config_line_t *arg1 = kwargs; + if (!strcmp(arg1->value, "")) { + path_str = arg1->key; + kwargs = kwargs->next; + } else if (arg1->key[0] == '$') { + tor_asprintf(&path_str_alloc, "%s=%s", arg1->key, arg1->value); + path_str = path_str_alloc; + kwargs = kwargs->next; + } + } + + const config_line_t *purpose_line = config_line_find_case(kwargs, "PURPOSE"); + bool zero_circ = !strcmp("0", circ_id); + + if (purpose_line) { + intended_purpose = circuit_purpose_from_string(purpose_line->value); + if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { + control_printf_endreply(conn, 552, "Unknown purpose \"%s\"", + purpose_line->value); + goto done; + } + } + + if (zero_circ) { + if (!path_str) { + // "EXTENDCIRCUIT 0" with no path. + circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); + if (!circ) { + control_write_endreply(conn, 551, "Couldn't start circuit"); + } else { + control_printf_endreply(conn, 250, "EXTENDED %lu", + (unsigned long)circ->global_identifier); + } + goto done; + } + } + + if (!zero_circ && !(circ = get_circ(circ_id))) { + control_printf_endreply(conn, 552, "Unknown circuit \"%s\"", circ_id); + goto done; + } + + if (!path_str) { + control_write_endreply(conn, 512, "syntax error: path required."); + goto done; + } + + smartlist_split_string(router_nicknames, path_str, ",", 0, 0); + + nodes = smartlist_new(); + bool first_node = zero_circ; + SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { + const node_t *node = node_get_by_nickname(n, 0); + if (!node) { + control_printf_endreply(conn, 552, "No such router \"%s\"", n); + goto done; + } + if (!node_has_preferred_descriptor(node, first_node)) { + control_printf_endreply(conn, 552, "No descriptor for \"%s\"", n); + goto done; + } + smartlist_add(nodes, (void*)node); + first_node = false; + } SMARTLIST_FOREACH_END(n); + + if (!smartlist_len(nodes)) { + control_write_endreply(conn, 512, "No router names provided"); + goto done; + } + + if (zero_circ) { + /* start a new circuit */ + circ = origin_circuit_init(intended_purpose, 0); + } + + /* now circ refers to something that is ready to be extended */ + first_node = zero_circ; + SMARTLIST_FOREACH(nodes, const node_t *, node, + { + extend_info_t *info = extend_info_from_node(node, first_node); + if (!info) { + tor_assert_nonfatal(first_node); + log_warn(LD_CONTROL, + "controller tried to connect to a node that lacks a suitable " + "descriptor, or which doesn't have any " + "addresses that are allowed by the firewall configuration; " + "circuit marked for closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); + control_write_endreply(conn, 551, "Couldn't start circuit"); + goto done; + } + circuit_append_new_exit(circ, info); + if (circ->build_state->desired_path_len > 1) { + circ->build_state->onehop_tunnel = 0; + } + extend_info_free(info); + first_node = 0; + }); + + /* now that we've populated the cpath, start extending */ + if (zero_circ) { + int err_reason = 0; + if ((err_reason = circuit_handle_first_hop(circ)) < 0) { + circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); + control_write_endreply(conn, 551, "Couldn't start circuit"); + goto done; + } + } else { + if (circ->base_.state == CIRCUIT_STATE_OPEN || + circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) { + int err_reason = 0; + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); + if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { + log_info(LD_CONTROL, + "send_next_onion_skin failed; circuit marked for closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); + control_write_endreply(conn, 551, "Couldn't send onion skin"); + goto done; + } + } + } + + control_printf_endreply(conn, 250, "EXTENDED %lu", + (unsigned long)circ->global_identifier); + if (zero_circ) /* send a 'launched' event, for completeness */ + circuit_event_status(circ, CIRC_EVENT_LAUNCHED, 0); + done: + SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); + smartlist_free(router_nicknames); + smartlist_free(nodes); + tor_free(path_str_alloc); + return 0; +} + +static const control_cmd_syntax_t setcircuitpurpose_syntax = { + .max_args=1, + .accept_keywords=true, +}; + +/** Called when we get a SETCIRCUITPURPOSE message. If we can find the + * circuit and it's a valid purpose, change it. */ +static int +handle_control_setcircuitpurpose(control_connection_t *conn, + const control_cmd_args_t *args) +{ + origin_circuit_t *circ = NULL; + uint8_t new_purpose; + const char *circ_id = smartlist_get(args->args,0); + + if (!(circ = get_circ(circ_id))) { + control_printf_endreply(conn, 552, "Unknown circuit \"%s\"", circ_id); + goto done; + } + + { + const config_line_t *purp = config_line_find_case(args->kwargs, "PURPOSE"); + if (!purp) { + control_write_endreply(conn, 552, "No purpose given"); + goto done; + } + new_purpose = circuit_purpose_from_string(purp->value); + if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { + control_printf_endreply(conn, 552, "Unknown purpose \"%s\"", + purp->value); + goto done; + } + } + + circuit_change_purpose(TO_CIRCUIT(circ), new_purpose); + send_control_done(conn); + + done: + return 0; +} + +static const char *attachstream_keywords[] = { + "HOP", NULL +}; +static const control_cmd_syntax_t attachstream_syntax = { + .min_args=2, .max_args=2, + .accept_keywords=true, + .allowed_keywords=attachstream_keywords +}; + +/** Called when we get an ATTACHSTREAM message. Try to attach the requested + * stream, and report success or failure. */ +static int +handle_control_attachstream(control_connection_t *conn, + const control_cmd_args_t *args) +{ + entry_connection_t *ap_conn = NULL; + origin_circuit_t *circ = NULL; + crypt_path_t *cpath=NULL; + int hop=0, hop_line_ok=1; + const char *stream_id = smartlist_get(args->args, 0); + const char *circ_id = smartlist_get(args->args, 1); + int zero_circ = !strcmp(circ_id, "0"); + const config_line_t *hoparg = config_line_find_case(args->kwargs, "HOP"); + + if (!(ap_conn = get_stream(stream_id))) { + control_printf_endreply(conn, 552, "Unknown stream \"%s\"", stream_id); + return 0; + } else if (!zero_circ && !(circ = get_circ(circ_id))) { + control_printf_endreply(conn, 552, "Unknown circuit \"%s\"", circ_id); + return 0; + } else if (circ) { + if (hoparg) { + hop = (int) tor_parse_ulong(hoparg->value, 10, 0, INT_MAX, + &hop_line_ok, NULL); + if (!hop_line_ok) { /* broken hop line */ + control_printf_endreply(conn, 552, "Bad value hop=%s", + hoparg->value); + return 0; + } + } + } + + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_RESOLVE_WAIT) { + control_write_endreply(conn, 555, + "Connection is not managed by controller."); + return 0; + } + + /* Do we need to detach it first? */ + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + circuit_t *tmpcirc = circuit_get_by_edge_conn(edge_conn); + connection_edge_end(edge_conn, END_STREAM_REASON_TIMEOUT); + /* Un-mark it as ending, since we're going to reuse it. */ + edge_conn->edge_has_sent_end = 0; + edge_conn->end_reason = 0; + if (tmpcirc) + circuit_detach_stream(tmpcirc, edge_conn); + CONNECTION_AP_EXPECT_NONPENDING(ap_conn); + TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; + } + + if (circ && (circ->base_.state != CIRCUIT_STATE_OPEN)) { + control_write_endreply(conn, 551, + "Can't attach stream to non-open origin circuit"); + return 0; + } + /* Is this a single hop circuit? */ + if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { + control_write_endreply(conn, 551, + "Can't attach stream to this one-hop circuit."); + return 0; + } + + if (circ && hop>0) { + /* find this hop in the circuit, and set cpath */ + cpath = circuit_get_cpath_hop(circ, hop); + if (!cpath) { + control_printf_endreply(conn, 551, "Circuit doesn't have %d hops.", hop); + return 0; + } + } + if (connection_ap_handshake_rewrite_and_attach(ap_conn, circ, cpath) < 0) { + control_write_endreply(conn, 551, "Unable to attach stream"); + return 0; + } + send_control_done(conn); + return 0; +} + +static const char *postdescriptor_keywords[] = { + "cache", "purpose", NULL, +}; + +static const control_cmd_syntax_t postdescriptor_syntax = { + .max_args = 0, + .accept_keywords = true, + .allowed_keywords = postdescriptor_keywords, + .want_cmddata = true, +}; + +/** Called when we get a POSTDESCRIPTOR message. Try to learn the provided + * descriptor, and report success or failure. */ +static int +handle_control_postdescriptor(control_connection_t *conn, + const control_cmd_args_t *args) +{ + const char *msg=NULL; + uint8_t purpose = ROUTER_PURPOSE_GENERAL; + int cache = 0; /* eventually, we may switch this to 1 */ + const config_line_t *line; + + line = config_line_find_case(args->kwargs, "purpose"); + if (line) { + purpose = router_purpose_from_string(line->value); + if (purpose == ROUTER_PURPOSE_UNKNOWN) { + control_printf_endreply(conn, 552, "Unknown purpose \"%s\"", + line->value); + goto done; + } + } + line = config_line_find_case(args->kwargs, "cache"); + if (line) { + if (!strcasecmp(line->value, "no")) + cache = 0; + else if (!strcasecmp(line->value, "yes")) + cache = 1; + else { + control_printf_endreply(conn, 552, "Unknown cache request \"%s\"", + line->value); + goto done; + } + } + + switch (router_load_single_router(args->cmddata, purpose, cache, &msg)) { + case -1: + if (!msg) msg = "Could not parse descriptor"; + control_write_endreply(conn, 554, msg); + break; + case 0: + if (!msg) msg = "Descriptor not added"; + control_write_endreply(conn, 251, msg); + break; + case 1: + send_control_done(conn); + break; + } + + done: + return 0; +} + +static const control_cmd_syntax_t redirectstream_syntax = { + .min_args = 2, + .max_args = UINT_MAX, // XXX should be 3. +}; + +/** Called when we receive a REDIRECTSTERAM command. Try to change the target + * address of the named AP stream, and report success or failure. */ +static int +handle_control_redirectstream(control_connection_t *conn, + const control_cmd_args_t *cmd_args) +{ + entry_connection_t *ap_conn = NULL; + char *new_addr = NULL; + uint16_t new_port = 0; + const smartlist_t *args = cmd_args->args; + + if (!(ap_conn = get_stream(smartlist_get(args, 0))) + || !ap_conn->socks_request) { + control_printf_endreply(conn, 552, "Unknown stream \"%s\"", + (char*)smartlist_get(args, 0)); + } else { + int ok = 1; + if (smartlist_len(args) > 2) { /* they included a port too */ + new_port = (uint16_t) tor_parse_ulong(smartlist_get(args, 2), + 10, 1, 65535, &ok, NULL); + } + if (!ok) { + control_printf_endreply(conn, 512, "Cannot parse port \"%s\"", + (char*)smartlist_get(args, 2)); + } else { + new_addr = tor_strdup(smartlist_get(args, 1)); + } + } + + if (!new_addr) + return 0; + + strlcpy(ap_conn->socks_request->address, new_addr, + sizeof(ap_conn->socks_request->address)); + if (new_port) + ap_conn->socks_request->port = new_port; + tor_free(new_addr); + send_control_done(conn); + return 0; +} + +static const control_cmd_syntax_t closestream_syntax = { + .min_args = 2, + .max_args = UINT_MAX, /* XXXX This is the original behavior, but + * maybe we should change the spec. */ +}; + +/** Called when we get a CLOSESTREAM command; try to close the named stream + * and report success or failure. */ +static int +handle_control_closestream(control_connection_t *conn, + const control_cmd_args_t *cmd_args) +{ + entry_connection_t *ap_conn=NULL; + uint8_t reason=0; + int ok; + const smartlist_t *args = cmd_args->args; + + tor_assert(smartlist_len(args) >= 2); + + if (!(ap_conn = get_stream(smartlist_get(args, 0)))) + control_printf_endreply(conn, 552, "Unknown stream \"%s\"", + (char*)smartlist_get(args, 0)); + else { + reason = (uint8_t) tor_parse_ulong(smartlist_get(args,1), 10, 0, 255, + &ok, NULL); + if (!ok) { + control_printf_endreply(conn, 552, "Unrecognized reason \"%s\"", + (char*)smartlist_get(args, 1)); + ap_conn = NULL; + } + } + if (!ap_conn) + return 0; + + connection_mark_unattached_ap(ap_conn, reason); + send_control_done(conn); + return 0; +} + +static const control_cmd_syntax_t closecircuit_syntax = { + .min_args=1, .max_args=1, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, + // XXXX we might want to exclude unrecognized flags, but for now we + // XXXX just ignore them for backward compatibility. +}; + +/** Called when we get a CLOSECIRCUIT command; try to close the named circuit + * and report success or failure. */ +static int +handle_control_closecircuit(control_connection_t *conn, + const control_cmd_args_t *args) +{ + const char *circ_id = smartlist_get(args->args, 0); + origin_circuit_t *circ = NULL; + + if (!(circ=get_circ(circ_id))) { + control_printf_endreply(conn, 552, "Unknown circuit \"%s\"", circ_id); + return 0; + } + + bool safe = config_lines_contain_flag(args->kwargs, "IfUnused"); + + if (!safe || !circ->p_streams) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); + } + + send_control_done(conn); + return 0; +} + +static const control_cmd_syntax_t resolve_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, +}; + +/** Called when we get a RESOLVE command: start trying to resolve + * the listed addresses. */ +static int +handle_control_resolve(control_connection_t *conn, + const control_cmd_args_t *args) +{ + smartlist_t *failed; + int is_reverse = 0; + + if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { + log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " + "isn't listening for ADDRMAP events. It probably won't see " + "the answer."); + } + + { + const config_line_t *modearg = config_line_find_case(args->kwargs, "mode"); + if (modearg && !strcasecmp(modearg->value, "reverse")) + is_reverse = 1; + } + failed = smartlist_new(); + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strlen(line->value)) { + const char *addr = line->key; + if (dnsserv_launch_request(addr, is_reverse, conn)<0) + smartlist_add(failed, (char*)addr); + } else { + // XXXX arguably we should reject unrecognized keyword arguments, + // XXXX but the old implementation didn't do that. + } + } + + send_control_done(conn); + SMARTLIST_FOREACH(failed, const char *, arg, { + control_event_address_mapped(arg, arg, time(NULL), + "internal", 0); + }); + + smartlist_free(failed); + return 0; +} + +static const control_cmd_syntax_t protocolinfo_syntax = { + .max_args = UINT_MAX +}; + +/** Return a comma-separated list of authentication methods for + handle_control_protocolinfo(). Caller must free this string. */ +static char * +get_authmethods(const or_options_t *options) +{ + int cookies = options->CookieAuthentication; + char *methods; + int passwd = (options->HashedControlPassword != NULL || + options->HashedControlSessionPassword != NULL); + smartlist_t *mlist = smartlist_new(); + + if (cookies) { + smartlist_add(mlist, (char*)"COOKIE"); + smartlist_add(mlist, (char*)"SAFECOOKIE"); + } + if (passwd) + smartlist_add(mlist, (char*)"HASHEDPASSWORD"); + if (!cookies && !passwd) + smartlist_add(mlist, (char*)"NULL"); + methods = smartlist_join_strings(mlist, ",", 0, NULL); + smartlist_free(mlist); + + return methods; +} + +/** Return escaped cookie filename. Caller must free this string. + Return NULL if cookie authentication is disabled. */ +static char * +get_esc_cfile(const or_options_t *options) +{ + char *cfile = NULL, *abs_cfile = NULL, *esc_cfile = NULL; + + if (!options->CookieAuthentication) + return NULL; + + cfile = get_controller_cookie_file_name(); + abs_cfile = make_path_absolute(cfile); + esc_cfile = esc_for_log(abs_cfile); + tor_free(cfile); + tor_free(abs_cfile); + return esc_cfile; +} + +/** Compose the auth methods line of a PROTOCOLINFO reply. */ +static void +add_authmethods(smartlist_t *reply) +{ + const or_options_t *options = get_options(); + char *methods = get_authmethods(options); + char *esc_cfile = get_esc_cfile(options); + + control_reply_add_str(reply, 250, "AUTH"); + control_reply_append_kv(reply, "METHODS", methods); + if (esc_cfile) + control_reply_append_kv(reply, "COOKIEFILE", esc_cfile); + + tor_free(methods); + tor_free(esc_cfile); +} + +/** Called when we get a PROTOCOLINFO command: send back a reply. */ +static int +handle_control_protocolinfo(control_connection_t *conn, + const control_cmd_args_t *cmd_args) +{ + const char *bad_arg = NULL; + const smartlist_t *args = cmd_args->args; + smartlist_t *reply = NULL; + + conn->have_sent_protocolinfo = 1; + + SMARTLIST_FOREACH(args, const char *, arg, { + int ok; + tor_parse_long(arg, 10, 0, LONG_MAX, &ok, NULL); + if (!ok) { + bad_arg = arg; + break; + } + }); + if (bad_arg) { + control_printf_endreply(conn, 513, "No such version %s", + escaped(bad_arg)); + /* Don't tolerate bad arguments when not authenticated. */ + if (!STATE_IS_OPEN(TO_CONN(conn)->state)) + connection_mark_for_close(TO_CONN(conn)); + return 0; + } + reply = smartlist_new(); + control_reply_add_str(reply, 250, "PROTOCOLINFO 1"); + add_authmethods(reply); + control_reply_add_str(reply, 250, "VERSION"); + control_reply_append_kv(reply, "Tor", escaped(VERSION)); + control_reply_add_done(reply); + + control_write_reply_lines(conn, reply); + control_reply_free(reply); + return 0; +} + +static const control_cmd_syntax_t usefeature_syntax = { + .max_args = UINT_MAX +}; + +/** Called when we get a USEFEATURE command: parse the feature list, and + * set up the control_connection's options properly. */ +static int +handle_control_usefeature(control_connection_t *conn, + const control_cmd_args_t *cmd_args) +{ + const smartlist_t *args = cmd_args->args; + int bad = 0; + SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { + if (!strcasecmp(arg, "VERBOSE_NAMES")) + ; + else if (!strcasecmp(arg, "EXTENDED_EVENTS")) + ; + else { + control_printf_endreply(conn, 552, "Unrecognized feature \"%s\"", + arg); + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(arg); + + if (!bad) { + send_control_done(conn); + } + + return 0; +} + +static const control_cmd_syntax_t dropguards_syntax = { + .max_args = 0, +}; + +/** Implementation for the DROPGUARDS command. */ +static int +handle_control_dropguards(control_connection_t *conn, + const control_cmd_args_t *args) +{ + (void) args; /* We don't take arguments. */ + + static int have_warned = 0; + if (! have_warned) { + log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand " + "the risks before using it. It may be removed in a future " + "version of Tor."); + have_warned = 1; + } + + remove_all_entry_guards(); + send_control_done(conn); + + return 0; +} + +static const char *hsfetch_keywords[] = { + "SERVER", NULL, +}; +static const control_cmd_syntax_t hsfetch_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = hsfetch_keywords, +}; + +/** Implementation for the HSFETCH command. */ +static int +handle_control_hsfetch(control_connection_t *conn, + const control_cmd_args_t *args) + +{ + char digest[DIGEST_LEN], *desc_id = NULL; + smartlist_t *hsdirs = NULL; + static const char *v2_str = "v2-"; + const size_t v2_str_len = strlen(v2_str); + rend_data_t *rend_query = NULL; + ed25519_public_key_t v3_pk; + uint32_t version; + const char *hsaddress = NULL; + + /* Extract the first argument (either HSAddress or DescID). */ + const char *arg1 = smartlist_get(args->args, 0); + /* Test if it's an HS address without the .onion part. */ + if (rend_valid_v2_service_id(arg1)) { + hsaddress = arg1; + version = HS_VERSION_TWO; + } else if (strcmpstart(arg1, v2_str) == 0 && + rend_valid_descriptor_id(arg1 + v2_str_len) && + base32_decode(digest, sizeof(digest), arg1 + v2_str_len, + REND_DESC_ID_V2_LEN_BASE32) == + sizeof(digest)) { + /* We have a well formed version 2 descriptor ID. Keep the decoded value + * of the id. */ + desc_id = digest; + version = HS_VERSION_TWO; + } else if (hs_address_is_valid(arg1)) { + hsaddress = arg1; + version = HS_VERSION_THREE; + hs_parse_address(hsaddress, &v3_pk, NULL, NULL); + } else { + control_printf_endreply(conn, 513, "Invalid argument \"%s\"", arg1); + goto done; + } + + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strcasecmp(line->key, "SERVER")) { + const char *server = line->value; + + const node_t *node = node_get_by_hex_id(server, 0); + if (!node) { + control_printf_endreply(conn, 552, "Server \"%s\" not found", server); + goto done; + } + if (!hsdirs) { + /* Stores routerstatus_t cmddata for each specified server. */ + hsdirs = smartlist_new(); + } + /* Valid server, add it to our local list. */ + smartlist_add(hsdirs, node->rs); + } else { + tor_assert_nonfatal_unreached(); + } + } + + if (version == HS_VERSION_TWO) { + rend_query = rend_data_client_create(hsaddress, desc_id, NULL, + REND_NO_AUTH); + if (rend_query == NULL) { + control_write_endreply(conn, 551, "Error creating the HS query"); + goto done; + } + } + + /* Using a descriptor ID, we force the user to provide at least one + * hsdir server using the SERVER= option. */ + if (desc_id && (!hsdirs || !smartlist_len(hsdirs))) { + control_write_endreply(conn, 512, "SERVER option is required"); + goto done; + } + + /* We are about to trigger HSDir fetch so send the OK now because after + * that 650 event(s) are possible so better to have the 250 OK before them + * to avoid out of order replies. */ + send_control_done(conn); + + /* Trigger the fetch using the built rend query and possibly a list of HS + * directory to use. This function ignores the client cache thus this will + * always send a fetch command. */ + if (version == HS_VERSION_TWO) { + rend_client_fetch_v2_desc(rend_query, hsdirs); + } else if (version == HS_VERSION_THREE) { + hs_control_hsfetch_command(&v3_pk, hsdirs); + } + + done: + /* Contains data pointer that we don't own thus no cleanup. */ + smartlist_free(hsdirs); + rend_data_free(rend_query); + return 0; +} + +static const char *hspost_keywords[] = { + "SERVER", "HSADDRESS", NULL +}; +static const control_cmd_syntax_t hspost_syntax = { + .min_args = 0, .max_args = 0, + .accept_keywords = true, + .want_cmddata = true, + .allowed_keywords = hspost_keywords +}; + +/** Implementation for the HSPOST command. */ +static int +handle_control_hspost(control_connection_t *conn, + const control_cmd_args_t *args) +{ + smartlist_t *hs_dirs = NULL; + const char *encoded_desc = args->cmddata; + size_t encoded_desc_len = args->cmddata_len; + const char *onion_address = NULL; + const config_line_t *line; + + for (line = args->kwargs; line; line = line->next) { + if (!strcasecmpstart(line->key, "SERVER")) { + const char *server = line->value; + const node_t *node = node_get_by_hex_id(server, 0); + + if (!node || !node->rs) { + control_printf_endreply(conn, 552, "Server \"%s\" not found", + server); + goto done; + } + /* Valid server, add it to our local list. */ + if (!hs_dirs) + hs_dirs = smartlist_new(); + smartlist_add(hs_dirs, node->rs); + } else if (!strcasecmpstart(line->key, "HSADDRESS")) { + const char *address = line->value; + if (!hs_address_is_valid(address)) { + control_write_endreply(conn, 512, "Malformed onion address"); + goto done; + } + onion_address = address; + } else { + tor_assert_nonfatal_unreached(); + } + } + + /* Handle the v3 case. */ + if (onion_address) { + if (hs_control_hspost_command(encoded_desc, onion_address, hs_dirs) < 0) { + control_write_endreply(conn, 554, "Invalid descriptor"); + } else { + send_control_done(conn); + } + goto done; + } + + /* From this point on, it is only v2. */ + + /* parse it. */ + rend_encoded_v2_service_descriptor_t *desc = + tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); + desc->desc_str = tor_memdup_nulterm(encoded_desc, encoded_desc_len); + + rend_service_descriptor_t *parsed = NULL; + char *intro_content = NULL; + size_t intro_size; + size_t encoded_size; + const char *next_desc; + if (!rend_parse_v2_service_descriptor(&parsed, desc->desc_id, &intro_content, + &intro_size, &encoded_size, + &next_desc, desc->desc_str, 1)) { + /* Post the descriptor. */ + char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; + if (!rend_get_service_id(parsed->pk, serviceid)) { + smartlist_t *descs = smartlist_new(); + smartlist_add(descs, desc); + + /* We are about to trigger HS descriptor upload so send the OK now + * because after that 650 event(s) are possible so better to have the + * 250 OK before them to avoid out of order replies. */ + send_control_done(conn); + + /* Trigger the descriptor upload */ + directory_post_to_hs_dir(parsed, descs, hs_dirs, serviceid, 0); + smartlist_free(descs); + } + + rend_service_descriptor_free(parsed); + } else { + control_write_endreply(conn, 554, "Invalid descriptor"); + } + + tor_free(intro_content); + rend_encoded_v2_service_descriptor_free(desc); + done: + smartlist_free(hs_dirs); /* Contents belong to the rend service code. */ + return 0; +} + +/* Helper function for ADD_ONION that adds an ephemeral service depending on + * the given hs_version. + * + * The secret key in pk depends on the hs_version. The ownership of the key + * used in pk is given to the HS subsystem so the caller must stop accessing + * it after. + * + * The port_cfgs is a list of service port. Ownership transferred to service. + * The max_streams refers to the MaxStreams= key. + * The max_streams_close_circuit refers to the MaxStreamsCloseCircuit key. + * The auth_type is the authentication type of the clients in auth_clients. + * The ownership of that list is transferred to the service. + * + * On success (RSAE_OKAY), the address_out points to a newly allocated string + * containing the onion address without the .onion part. On error, address_out + * is untouched. */ +static hs_service_add_ephemeral_status_t +add_onion_helper_add_service(int hs_version, + add_onion_secret_key_t *pk, + smartlist_t *port_cfgs, int max_streams, + int max_streams_close_circuit, int auth_type, + smartlist_t *auth_clients, char **address_out) +{ + hs_service_add_ephemeral_status_t ret; + + tor_assert(pk); + tor_assert(port_cfgs); + tor_assert(address_out); + + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_add_ephemeral(pk->v2, port_cfgs, max_streams, + max_streams_close_circuit, auth_type, + auth_clients, address_out); + break; + case HS_VERSION_THREE: + ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, + max_streams_close_circuit, address_out); + break; + default: + tor_assert_unreached(); + } + + return ret; +} + +/** The list of onion services that have been added via ADD_ONION that do not + * belong to any particular control connection. + */ +static smartlist_t *detached_onion_services = NULL; + +/** + * Return a list of detached onion services, or NULL if none exist. + **/ +smartlist_t * +get_detached_onion_services(void) +{ + return detached_onion_services; +} + +static const char *add_onion_keywords[] = { + "Port", "Flags", "MaxStreams", "ClientAuth", NULL +}; +static const control_cmd_syntax_t add_onion_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = add_onion_keywords +}; + +/** Called when we get a ADD_ONION command; parse the body, and set up + * the new ephemeral Onion Service. */ +static int +handle_control_add_onion(control_connection_t *conn, + const control_cmd_args_t *args) +{ + /* Parse all of the arguments that do not involve handling cryptographic + * material first, since there's no reason to touch that at all if any of + * the other arguments are malformed. + */ + smartlist_t *port_cfgs = smartlist_new(); + smartlist_t *auth_clients = NULL; + smartlist_t *auth_created_clients = NULL; + int discard_pk = 0; + int detach = 0; + int max_streams = 0; + int max_streams_close_circuit = 0; + rend_auth_type_t auth_type = REND_NO_AUTH; + int non_anonymous = 0; + const config_line_t *arg; + + for (arg = args->kwargs; arg; arg = arg->next) { + if (!strcasecmp(arg->key, "Port")) { + /* "Port=VIRTPORT[,TARGET]". */ + rend_service_port_config_t *cfg = + rend_service_parse_port_config(arg->value, ",", NULL); + if (!cfg) { + control_write_endreply(conn, 512, "Invalid VIRTPORT/TARGET"); + goto out; + } + smartlist_add(port_cfgs, cfg); + } else if (!strcasecmp(arg->key, "MaxStreams")) { + /* "MaxStreams=[0..65535]". */ + int ok = 0; + max_streams = (int)tor_parse_long(arg->value, 10, 0, 65535, &ok, NULL); + if (!ok) { + control_write_endreply(conn, 512, "Invalid MaxStreams"); + goto out; + } + } else if (!strcasecmp(arg->key, "Flags")) { + /* "Flags=Flag[,Flag]", where Flag can be: + * * 'DiscardPK' - If tor generates the keypair, do not include it in + * the response. + * * 'Detach' - Do not tie this onion service to any particular control + * connection. + * * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is + * exceeded. + * * 'BasicAuth' - Client authorization using the 'basic' method. + * * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this + * flag is present, tor must be in non-anonymous + * hidden service mode. If this flag is absent, + * tor must be in anonymous hidden service mode. + */ + static const char *discard_flag = "DiscardPK"; + static const char *detach_flag = "Detach"; + static const char *max_s_close_flag = "MaxStreamsCloseCircuit"; + static const char *basicauth_flag = "BasicAuth"; + static const char *non_anonymous_flag = "NonAnonymous"; + + smartlist_t *flags = smartlist_new(); + int bad = 0; + + smartlist_split_string(flags, arg->value, ",", SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(flags) < 1) { + control_write_endreply(conn, 512, "Invalid 'Flags' argument"); + bad = 1; + } + SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) + { + if (!strcasecmp(flag, discard_flag)) { + discard_pk = 1; + } else if (!strcasecmp(flag, detach_flag)) { + detach = 1; + } else if (!strcasecmp(flag, max_s_close_flag)) { + max_streams_close_circuit = 1; + } else if (!strcasecmp(flag, basicauth_flag)) { + auth_type = REND_BASIC_AUTH; + } else if (!strcasecmp(flag, non_anonymous_flag)) { + non_anonymous = 1; + } else { + control_printf_endreply(conn, 512, "Invalid 'Flags' argument: %s", + escaped(flag)); + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(flag); + SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); + smartlist_free(flags); + if (bad) + goto out; + + } else if (!strcasecmp(arg->key, "ClientAuth")) { + int created = 0; + rend_authorized_client_t *client = + add_onion_helper_clientauth(arg->value, &created, conn); + if (!client) { + goto out; + } + + if (auth_clients != NULL) { + int bad = 0; + SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) { + if (strcmp(ac->client_name, client->client_name) == 0) { + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(ac); + if (bad) { + control_write_endreply(conn, 512, "Duplicate name in ClientAuth"); + rend_authorized_client_free(client); + goto out; + } + } else { + auth_clients = smartlist_new(); + auth_created_clients = smartlist_new(); + } + smartlist_add(auth_clients, client); + if (created) { + smartlist_add(auth_created_clients, client); + } + } else { + tor_assert_nonfatal_unreached(); + goto out; + } + } + if (smartlist_len(port_cfgs) == 0) { + control_write_endreply(conn, 512, "Missing 'Port' argument"); + goto out; + } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) { + control_write_endreply(conn, 512, "No auth type specified"); + goto out; + } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) { + control_write_endreply(conn, 512, "No auth clients specified"); + goto out; + } else if ((auth_type == REND_BASIC_AUTH && + smartlist_len(auth_clients) > 512) || + (auth_type == REND_STEALTH_AUTH && + smartlist_len(auth_clients) > 16)) { + control_write_endreply(conn, 512, "Too many auth clients"); + goto out; + } else if (non_anonymous != rend_service_non_anonymous_mode_enabled( + get_options())) { + /* If we failed, and the non-anonymous flag is set, Tor must be in + * anonymous hidden service mode. + * The error message changes based on the current Tor config: + * 512 Tor is in anonymous hidden service mode + * 512 Tor is in non-anonymous hidden service mode + * (I've deliberately written them out in full here to aid searchability.) + */ + control_printf_endreply(conn, 512, + "Tor is in %sanonymous hidden service " "mode", + non_anonymous ? "" : "non-"); + goto out; + } + + /* Parse the "keytype:keyblob" argument. */ + int hs_version = 0; + add_onion_secret_key_t pk = { NULL }; + const char *key_new_alg = NULL; + char *key_new_blob = NULL; + + const char *onionkey = smartlist_get(args->args, 0); + if (add_onion_helper_keyarg(onionkey, discard_pk, + &key_new_alg, &key_new_blob, &pk, &hs_version, + conn) < 0) { + goto out; + } + + /* Hidden service version 3 don't have client authentication support so if + * ClientAuth was given, send back an error. */ + if (hs_version == HS_VERSION_THREE && auth_clients) { + control_write_endreply(conn, 513, "ClientAuth not supported"); + goto out; + } + + /* Create the HS, using private key pk, client authentication auth_type, + * the list of auth_clients, and port config port_cfg. + * rend_service_add_ephemeral() will take ownership of pk and port_cfg, + * regardless of success/failure. + */ + char *service_id = NULL; + int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, + max_streams, + max_streams_close_circuit, auth_type, + auth_clients, &service_id); + port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ + auth_clients = NULL; /* so is auth_clients */ + switch (ret) { + case RSAE_OKAY: + { + if (detach) { + if (!detached_onion_services) + detached_onion_services = smartlist_new(); + smartlist_add(detached_onion_services, service_id); + } else { + if (!conn->ephemeral_onion_services) + conn->ephemeral_onion_services = smartlist_new(); + smartlist_add(conn->ephemeral_onion_services, service_id); + } + + tor_assert(service_id); + control_printf_midreply(conn, 250, "ServiceID=%s", service_id); + if (key_new_alg) { + tor_assert(key_new_blob); + control_printf_midreply(conn, 250, "PrivateKey=%s:%s", + key_new_alg, key_new_blob); + } + if (auth_created_clients) { + SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, { + char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie, + auth_type); + tor_assert(encoded); + control_printf_midreply(conn, 250, "ClientAuth=%s:%s", + ac->client_name, encoded); + memwipe(encoded, 0, strlen(encoded)); + tor_free(encoded); + }); + } + + send_control_done(conn); + break; + } + case RSAE_BADPRIVKEY: + control_write_endreply(conn, 551, "Failed to generate onion address"); + break; + case RSAE_ADDREXISTS: + control_write_endreply(conn, 550, "Onion address collision"); + break; + case RSAE_BADVIRTPORT: + control_write_endreply(conn, 512, "Invalid VIRTPORT/TARGET"); + break; + case RSAE_BADAUTH: + control_write_endreply(conn, 512, "Invalid client authorization"); + break; + case RSAE_INTERNAL: FALLTHROUGH; + default: + control_write_endreply(conn, 551, "Failed to add Onion Service"); + } + if (key_new_blob) { + memwipe(key_new_blob, 0, strlen(key_new_blob)); + tor_free(key_new_blob); + } + + out: + if (port_cfgs) { + SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, + rend_service_port_config_free(p)); + smartlist_free(port_cfgs); + } + + if (auth_clients) { + SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac, + rend_authorized_client_free(ac)); + smartlist_free(auth_clients); + } + if (auth_created_clients) { + // Do not free entries; they are the same as auth_clients + smartlist_free(auth_created_clients); + } + return 0; +} + +/** Helper function to handle parsing the KeyType:KeyBlob argument to the + * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated + * and the private key not discarded, the algorithm and serialized private key, + * or NULL and an optional control protocol error message on failure. The + * caller is responsible for freeing the returned key_new_blob. + * + * Note: The error messages returned are deliberately vague to avoid echoing + * key material. + * + * Note: conn is only used for writing control replies. For testing + * purposes, it can be NULL if control_write_reply() is appropriately + * mocked. + */ +STATIC int +add_onion_helper_keyarg(const char *arg, int discard_pk, + const char **key_new_alg_out, char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, int *hs_version, + control_connection_t *conn) +{ + smartlist_t *key_args = smartlist_new(); + crypto_pk_t *pk = NULL; + const char *key_new_alg = NULL; + char *key_new_blob = NULL; + int ret = -1; + + smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(key_args) != 2) { + control_write_endreply(conn, 512, "Invalid key type/blob"); + goto err; + } + + /* The format is "KeyType:KeyBlob". */ + static const char *key_type_new = "NEW"; + static const char *key_type_best = "BEST"; + static const char *key_type_rsa1024 = "RSA1024"; + static const char *key_type_ed25519_v3 = "ED25519-V3"; + + const char *key_type = smartlist_get(key_args, 0); + const char *key_blob = smartlist_get(key_args, 1); + + if (!strcasecmp(key_type_rsa1024, key_type)) { + /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ + pk = crypto_pk_base64_decode_private(key_blob, strlen(key_blob)); + if (!pk) { + control_write_endreply(conn, 512, "Failed to decode RSA key"); + goto err; + } + if (crypto_pk_num_bits(pk) != PK_BYTES*8) { + crypto_pk_free(pk); + control_write_endreply(conn, 512, "Invalid RSA key size"); + goto err; + } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_type)) { + /* parsing of private ed25519 key */ + /* "ED25519-V3:<Base64 Blob>" - Loading a pre-existing ed25519 key. */ + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, + strlen(key_blob)) != sizeof(sk->seckey)) { + tor_free(sk); + control_write_endreply(conn, 512, "Failed to decode ED25519-V3 key"); + goto err; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; + } else if (!strcasecmp(key_type_new, key_type)) { + /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ + if (!strcasecmp(key_type_rsa1024, key_blob)) { + /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ + pk = crypto_pk_new(); + if (crypto_pk_generate_key(pk)) { + control_printf_endreply(conn, 551, "Failed to generate %s key", + key_type_rsa1024); + goto err; + } + if (!discard_pk) { + if (crypto_pk_base64_encode_private(pk, &key_new_blob)) { + crypto_pk_free(pk); + control_printf_endreply(conn, 551, "Failed to encode %s key", + key_type_rsa1024); + goto err; + } + key_new_alg = key_type_rsa1024; + } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_blob) || + !strcasecmp(key_type_best, key_blob)) { + /* "ED25519-V3", ed25519 key, also currently "BEST" by default. */ + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (ed25519_secret_key_generate(sk, 1) < 0) { + tor_free(sk); + control_printf_endreply(conn, 551, "Failed to generate %s key", + key_type_ed25519_v3); + goto err; + } + if (!discard_pk) { + ssize_t len = base64_encode_size(sizeof(sk->seckey), 0) + 1; + key_new_blob = tor_malloc_zero(len); + if (base64_encode(key_new_blob, len, (const char *) sk->seckey, + sizeof(sk->seckey), 0) != (len - 1)) { + tor_free(sk); + tor_free(key_new_blob); + control_printf_endreply(conn, 551, "Failed to encode %s key", + key_type_ed25519_v3); + goto err; + } + key_new_alg = key_type_ed25519_v3; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; + } else { + control_write_endreply(conn, 513, "Invalid key type"); + goto err; + } + } else { + control_write_endreply(conn, 513, "Invalid key type"); + goto err; + } + + /* Succeeded in loading or generating a private key. */ + ret = 0; + + err: + SMARTLIST_FOREACH(key_args, char *, cp, { + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + }); + smartlist_free(key_args); + + *key_new_alg_out = key_new_alg; + *key_new_blob_out = key_new_blob; + + return ret; +} + +/** Helper function to handle parsing a ClientAuth argument to the + * ADD_ONION command. Return a new rend_authorized_client_t, or NULL + * and an optional control protocol error message on failure. The + * caller is responsible for freeing the returned auth_client. + * + * If 'created' is specified, it will be set to 1 when a new cookie has + * been generated. + * + * Note: conn is only used for writing control replies. For testing + * purposes, it can be NULL if control_write_reply() is appropriately + * mocked. + */ +STATIC rend_authorized_client_t * +add_onion_helper_clientauth(const char *arg, int *created, + control_connection_t *conn) +{ + int ok = 0; + + tor_assert(arg); + tor_assert(created); + + smartlist_t *auth_args = smartlist_new(); + rend_authorized_client_t *client = + tor_malloc_zero(sizeof(rend_authorized_client_t)); + smartlist_split_string(auth_args, arg, ":", 0, 0); + if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) { + control_write_endreply(conn, 512, "Invalid ClientAuth syntax"); + goto err; + } + client->client_name = tor_strdup(smartlist_get(auth_args, 0)); + if (smartlist_len(auth_args) == 2) { + char *decode_err_msg = NULL; + if (rend_auth_decode_cookie(smartlist_get(auth_args, 1), + client->descriptor_cookie, + NULL, &decode_err_msg) < 0) { + tor_assert(decode_err_msg); + control_write_endreply(conn, 512, decode_err_msg); + tor_free(decode_err_msg); + goto err; + } + *created = 0; + } else { + crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN); + *created = 1; + } + + if (!rend_valid_client_name(client->client_name)) { + control_write_endreply(conn, 512, "Invalid name in ClientAuth"); + goto err; + } + + ok = 1; + err: + SMARTLIST_FOREACH(auth_args, char *, item, tor_free(item)); + smartlist_free(auth_args); + if (!ok) { + rend_authorized_client_free(client); + client = NULL; + } + return client; +} + +static const control_cmd_syntax_t del_onion_syntax = { + .min_args = 1, .max_args = 1, +}; + +/** Called when we get a DEL_ONION command; parse the body, and remove + * the existing ephemeral Onion Service. */ +static int +handle_control_del_onion(control_connection_t *conn, + const control_cmd_args_t *cmd_args) +{ + int hs_version = 0; + smartlist_t *args = cmd_args->args; + tor_assert(smartlist_len(args) == 1); + + const char *service_id = smartlist_get(args, 0); + if (rend_valid_v2_service_id(service_id)) { + hs_version = HS_VERSION_TWO; + } else if (hs_address_is_valid(service_id)) { + hs_version = HS_VERSION_THREE; + } else { + control_write_endreply(conn, 512, "Malformed Onion Service id"); + goto out; + } + + /* Determine if the onion service belongs to this particular control + * connection, or if it is in the global list of detached services. If it + * is in neither, either the service ID is invalid in some way, or it + * explicitly belongs to a different control connection, and an error + * should be returned. + */ + smartlist_t *services[2] = { + conn->ephemeral_onion_services, + detached_onion_services + }; + smartlist_t *onion_services = NULL; + int idx = -1; + for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { + idx = smartlist_string_pos(services[i], service_id); + if (idx != -1) { + onion_services = services[i]; + break; + } + } + if (onion_services == NULL) { + control_write_endreply(conn, 552, "Unknown Onion Service id"); + } else { + int ret = -1; + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_del_ephemeral(service_id); + break; + case HS_VERSION_THREE: + ret = hs_service_del_ephemeral(service_id); + break; + default: + /* The ret value will be -1 thus hitting the warning below. This should + * never happen because of the check at the start of the function. */ + break; + } + if (ret < 0) { + /* This should *NEVER* fail, since the service is on either the + * per-control connection list, or the global one. + */ + log_warn(LD_BUG, "Failed to remove Onion Service %s.", + escaped(service_id)); + tor_fragile_assert(); + } + + /* Remove/scrub the service_id from the appropriate list. */ + char *cp = smartlist_get(onion_services, idx); + smartlist_del(onion_services, idx); + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + + send_control_done(conn); + } + + out: + return 0; +} + +static const control_cmd_syntax_t obsolete_syntax = { + .max_args = UINT_MAX +}; + +/** + * Called when we get an obsolete command: tell the controller that it is + * obsolete. + */ +static int +handle_control_obsolete(control_connection_t *conn, + const control_cmd_args_t *args) +{ + (void)args; + char *command = tor_strdup(conn->current_cmd); + tor_strupper(command); + control_printf_endreply(conn, 511, "%s is obsolete.", command); + tor_free(command); + return 0; +} + +/** + * Function pointer to a handler function for a controller command. + **/ +typedef int (*handler_fn_t) (control_connection_t *conn, + const control_cmd_args_t *args); + +/** + * Definition for a controller command. + */ +typedef struct control_cmd_def_t { + /** + * The name of the command. If the command is multiline, the name must + * begin with "+". This is not case-sensitive. */ + const char *name; + /** + * A function to execute the command. + */ + handler_fn_t handler; + /** + * Zero or more CMD_FL_* flags, or'd together. + */ + unsigned flags; + /** + * For parsed command: a syntax description. + */ + const control_cmd_syntax_t *syntax; +} control_cmd_def_t; + +/** + * Indicates that the command's arguments are sensitive, and should be + * memwiped after use. + */ +#define CMD_FL_WIPE (1u<<0) + +#ifndef COCCI +/** Macro: declare a command with a one-line argument, a given set of flags, + * and a syntax definition. + **/ +#define ONE_LINE(name, flags) \ + { \ + #name, \ + handle_control_ ##name, \ + flags, \ + &name##_syntax, \ + } + +/** + * Macro: declare a command with a multi-line argument and a given set of + * flags. + **/ +#define MULTLINE(name, flags) \ + { "+"#name, \ + handle_control_ ##name, \ + flags, \ + &name##_syntax \ + } + +/** + * Macro: declare an obsolete command. (Obsolete commands give a different + * error than non-existent ones.) + **/ +#define OBSOLETE(name) \ + { #name, \ + handle_control_obsolete, \ + 0, \ + &obsolete_syntax, \ + } +#endif /* !defined(COCCI) */ + +/** + * An array defining all the recognized controller commands. + **/ +static const control_cmd_def_t CONTROL_COMMANDS[] = +{ + ONE_LINE(setconf, 0), + ONE_LINE(resetconf, 0), + ONE_LINE(getconf, 0), + MULTLINE(loadconf, 0), + ONE_LINE(setevents, 0), + ONE_LINE(authenticate, CMD_FL_WIPE), + ONE_LINE(saveconf, 0), + ONE_LINE(signal, 0), + ONE_LINE(takeownership, 0), + ONE_LINE(dropownership, 0), + ONE_LINE(mapaddress, 0), + ONE_LINE(getinfo, 0), + ONE_LINE(extendcircuit, 0), + ONE_LINE(setcircuitpurpose, 0), + OBSOLETE(setrouterpurpose), + ONE_LINE(attachstream, 0), + MULTLINE(postdescriptor, 0), + ONE_LINE(redirectstream, 0), + ONE_LINE(closestream, 0), + ONE_LINE(closecircuit, 0), + ONE_LINE(usefeature, 0), + ONE_LINE(resolve, 0), + ONE_LINE(protocolinfo, 0), + ONE_LINE(authchallenge, CMD_FL_WIPE), + ONE_LINE(dropguards, 0), + ONE_LINE(hsfetch, 0), + MULTLINE(hspost, 0), + ONE_LINE(add_onion, CMD_FL_WIPE), + ONE_LINE(del_onion, CMD_FL_WIPE), + ONE_LINE(onion_client_auth_add, CMD_FL_WIPE), + ONE_LINE(onion_client_auth_remove, 0), + ONE_LINE(onion_client_auth_view, 0), +}; + +/** + * The number of entries in CONTROL_COMMANDS. + **/ +static const size_t N_CONTROL_COMMANDS = ARRAY_LENGTH(CONTROL_COMMANDS); + +/** + * Run a single control command, as defined by a control_cmd_def_t, + * with a given set of arguments. + */ +static int +handle_single_control_command(const control_cmd_def_t *def, + control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + int rv = 0; + + control_cmd_args_t *parsed_args; + char *err=NULL; + tor_assert(def->syntax); + parsed_args = control_cmd_parse_args(conn->current_cmd, + def->syntax, + cmd_data_len, args, + &err); + if (!parsed_args) { + control_printf_endreply(conn, 512, "Bad arguments to %s: %s", + conn->current_cmd, err?err:""); + tor_free(err); + } else { + if (BUG(err)) + tor_free(err); + if (def->handler(conn, parsed_args)) + rv = 0; + + if (def->flags & CMD_FL_WIPE) + control_cmd_args_wipe(parsed_args); + + control_cmd_args_free(parsed_args); + } + + if (def->flags & CMD_FL_WIPE) + memwipe(args, 0, cmd_data_len); + + return rv; +} + +/** + * Run a given controller command, as selected by the current_cmd field of + * <b>conn</b>. + */ +int +handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + tor_assert(conn); + tor_assert(args); + tor_assert(args[cmd_data_len] == '\0'); + + for (unsigned i = 0; i < N_CONTROL_COMMANDS; ++i) { + const control_cmd_def_t *def = &CONTROL_COMMANDS[i]; + if (!strcasecmp(conn->current_cmd, def->name)) { + return handle_single_control_command(def, conn, cmd_data_len, args); + } + } + + control_printf_endreply(conn, 510, "Unrecognized command \"%s\"", + conn->current_cmd); + + return 0; +} + +void +control_cmd_free_all(void) +{ + if (detached_onion_services) { /* Free the detached onion services */ + SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); + smartlist_free(detached_onion_services); + } +} diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h new file mode 100644 index 0000000000..0ff0f0755f --- /dev/null +++ b/src/feature/control/control_cmd.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_cmd.h + * \brief Header file for control_cmd.c. + **/ + +#ifndef TOR_CONTROL_CMD_H +#define TOR_CONTROL_CMD_H + +#include "lib/malloc/malloc.h" + +int handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args); +void control_cmd_free_all(void); + +typedef struct control_cmd_args_t control_cmd_args_t; +void control_cmd_args_free_(control_cmd_args_t *args); +void control_cmd_args_wipe(control_cmd_args_t *args); + +#define control_cmd_args_free(v) \ + FREE_AND_NULL(control_cmd_args_t, control_cmd_args_free_, (v)) + +/** + * Definition for the syntax of a controller command, as parsed by + * control_cmd_parse_args. + * + * WORK IN PROGRESS: This structure is going to get more complex as this + * branch goes on. + **/ +typedef struct control_cmd_syntax_t { + /** + * Lowest number of positional arguments that this command accepts. + * 0 for "it's okay not to have positional arguments." + **/ + unsigned int min_args; + /** + * Highest number of positional arguments that this command accepts. + * UINT_MAX for no limit. + **/ + unsigned int max_args; + /** + * If true, we should parse options after the positional arguments + * as a set of unordered flags and key=value arguments. + * + * Requires that max_args is not UINT_MAX. + **/ + bool accept_keywords; + /** + * If accept_keywords is true, then only the keywords listed in this + * (NULL-terminated) array are valid keywords for this command. + **/ + const char **allowed_keywords; + /** + * If accept_keywords is true, this option is passed to kvline_parse() as + * its flags. + **/ + unsigned kvline_flags; + /** + * True iff this command wants to be followed by a multiline object. + **/ + bool want_cmddata; + /** + * True iff this command needs access to the raw body of the input. + * + * This should not be needed for pure commands; it is purely a legacy + * option. + **/ + bool store_raw_body; +} control_cmd_syntax_t; + +#ifdef CONTROL_CMD_PRIVATE +#include "lib/crypt_ops/crypto_ed25519.h" + +/* ADD_ONION secret key to create an ephemeral service. The command supports + * multiple versions so this union stores the key and passes it to the HS + * subsystem depending on the requested version. */ +typedef union add_onion_secret_key_t { + /* Hidden service v2 secret key. */ + crypto_pk_t *v2; + /* Hidden service v3 secret key. */ + ed25519_secret_key_t *v3; +} add_onion_secret_key_t; + +STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, + const char **key_new_alg_out, + char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, + int *hs_version, + control_connection_t *conn); + +STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg, + int *created, control_connection_t *conn); + +STATIC control_cmd_args_t *control_cmd_parse_args( + const char *command, + const control_cmd_syntax_t *syntax, + size_t body_len, + const char *body, + char **error_out); + +#endif /* defined(CONTROL_CMD_PRIVATE) */ + +#ifdef CONTROL_MODULE_PRIVATE +smartlist_t * get_detached_onion_services(void); +#endif /* defined(CONTROL_MODULE_PRIVATE) */ + +#endif /* !defined(TOR_CONTROL_CMD_H) */ diff --git a/src/feature/control/control_cmd_args_st.h b/src/feature/control/control_cmd_args_st.h new file mode 100644 index 0000000000..e7d064c6fe --- /dev/null +++ b/src/feature/control/control_cmd_args_st.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_cmd_args_st.h + * \brief Definition for control_cmd_args_t + **/ + +#ifndef TOR_CONTROL_CMD_ST_H +#define TOR_CONTROL_CMD_ST_H + +struct smartlist_t; +struct config_line_t; + +/** + * Parsed arguments for a control command. + * + * WORK IN PROGRESS: This structure is going to get more complex as this + * branch goes on. + **/ +struct control_cmd_args_t { + /** + * The command itself, as provided by the controller. Not owned by this + * structure. + **/ + const char *command; + /** + * Positional arguments to the command. + **/ + struct smartlist_t *args; + /** + * Keyword arguments to the command. + **/ + struct config_line_t *kwargs; + /** + * Number of bytes in <b>cmddata</b>; 0 if <b>cmddata</b> is not set. + **/ + size_t cmddata_len; + /** + * A multiline object passed with this command. + **/ + char *cmddata; + /** + * If set, a nul-terminated string containing the raw unparsed arguments. + **/ + const char *raw_body; +}; + +#endif /* !defined(TOR_CONTROL_CMD_ST_H) */ diff --git a/src/feature/control/control_connection_st.h b/src/feature/control/control_connection_st.h index 177a916257..9e410324e0 100644 --- a/src/feature/control/control_connection_st.h +++ b/src/feature/control/control_connection_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file control_connection_st.h + * @brief Controller connection structure. + **/ + #ifndef CONTROL_CONNECTION_ST_H #define CONTROL_CONNECTION_ST_H @@ -40,7 +45,8 @@ struct control_connection_t { /** A control command that we're reading from the inbuf, but which has not * yet arrived completely. */ char *incoming_cmd; + /** The control command that we are currently processing. */ + char *current_cmd; }; -#endif - +#endif /* !defined(CONTROL_CONNECTION_ST_H) */ diff --git a/src/feature/control/control_events.c b/src/feature/control/control_events.c new file mode 100644 index 0000000000..916ccea875 --- /dev/null +++ b/src/feature/control/control_events.c @@ -0,0 +1,2314 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_events.c + * \brief Implement the event-reporting part of the controller API. + **/ + +#define CONTROL_MODULE_PRIVATE +#define CONTROL_EVENTS_PRIVATE +#define OCIRC_EVENT_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "core/mainloop/mainloop.h" +#include "core/or/channeltls.h" +#include "core/or/circuitlist.h" +#include "core/or/command.h" +#include "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/reasons.h" +#include "feature/control/control.h" +#include "feature/control/control_events.h" +#include "feature/control/control_fmt.h" +#include "feature/control/control_proto.h" +#include "feature/dircommon/directory.h" +#include "feature/nodelist/describe.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" + +#include "feature/control/control_connection_st.h" +#include "core/or/entry_connection_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "core/or/or_connection_st.h" +#include "core/or/or_circuit_st.h" +#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); + +/** Yield true iff <b>s</b> is the state of a control_connection_t that has + * finished authentication and is accepting commands. */ +#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) + +/** An event mask of all the events that any controller is interested in + * receiving. */ +static event_mask_t global_event_mask = 0; + +/** True iff we have disabled log messages from being sent to the controller */ +static int disable_log_messages = 0; + +/** Macro: true if any control connection is interested in events of type + * <b>e</b>. */ +#define EVENT_IS_INTERESTING(e) \ + (!! (global_event_mask & EVENT_MASK_(e))) + +/** Macro: true if any event from the bitfield 'e' is interesting. */ +#define ANY_EVENT_IS_INTERESTING(e) \ + (!! (global_event_mask & (e))) + +static void send_control_event_impl(uint16_t event, + const char *format, va_list ap) + CHECK_PRINTF(2,0); +static int control_event_status(int type, int severity, const char *format, + va_list args) + CHECK_PRINTF(3,0); + +static void send_control_event(uint16_t event, + const char *format, ...) + CHECK_PRINTF(2,3); + +/** Table mapping event values to their names. Used to implement SETEVENTS + * and GETINFO events/names, and to keep they in sync. */ +const struct control_event_t control_event_table[] = { + { EVENT_CIRCUIT_STATUS, "CIRC" }, + { EVENT_CIRCUIT_STATUS_MINOR, "CIRC_MINOR" }, + { EVENT_STREAM_STATUS, "STREAM" }, + { EVENT_OR_CONN_STATUS, "ORCONN" }, + { EVENT_BANDWIDTH_USED, "BW" }, + { EVENT_DEBUG_MSG, "DEBUG" }, + { EVENT_INFO_MSG, "INFO" }, + { EVENT_NOTICE_MSG, "NOTICE" }, + { EVENT_WARN_MSG, "WARN" }, + { EVENT_ERR_MSG, "ERR" }, + { EVENT_NEW_DESC, "NEWDESC" }, + { EVENT_ADDRMAP, "ADDRMAP" }, + { EVENT_DESCCHANGED, "DESCCHANGED" }, + { EVENT_NS, "NS" }, + { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, + { EVENT_STATUS_CLIENT, "STATUS_CLIENT" }, + { EVENT_STATUS_SERVER, "STATUS_SERVER" }, + { EVENT_GUARD, "GUARD" }, + { EVENT_STREAM_BANDWIDTH_USED, "STREAM_BW" }, + { EVENT_CLIENTS_SEEN, "CLIENTS_SEEN" }, + { EVENT_NEWCONSENSUS, "NEWCONSENSUS" }, + { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" }, + { EVENT_GOT_SIGNAL, "SIGNAL" }, + { EVENT_CONF_CHANGED, "CONF_CHANGED"}, + { EVENT_CONN_BW, "CONN_BW" }, + { EVENT_CELL_STATS, "CELL_STATS" }, + { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" }, + { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" }, + { EVENT_HS_DESC, "HS_DESC" }, + { EVENT_HS_DESC_CONTENT, "HS_DESC_CONTENT" }, + { EVENT_NETWORK_LIVENESS, "NETWORK_LIVENESS" }, + { 0, NULL }, +}; + +/** Given a log severity, return the corresponding control event code. */ +static inline int +log_severity_to_event(int severity) +{ + switch (severity) { + case LOG_DEBUG: return EVENT_DEBUG_MSG; + case LOG_INFO: return EVENT_INFO_MSG; + case LOG_NOTICE: return EVENT_NOTICE_MSG; + case LOG_WARN: return EVENT_WARN_MSG; + case LOG_ERR: return EVENT_ERR_MSG; + default: return -1; + } +} + +/** Helper: clear bandwidth counters of all origin circuits. */ +static void +clear_circ_bw_fields(void) +{ + origin_circuit_t *ocirc; + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + ocirc = TO_ORIGIN_CIRCUIT(circ); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0; + ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0; + } + SMARTLIST_FOREACH_END(circ); +} + +/** Set <b>global_event_mask*</b> to the bitwise OR of each live control + * connection's event_mask field. */ +void +control_update_global_event_mask(void) +{ + smartlist_t *conns = get_connection_array(); + event_mask_t old_mask, new_mask; + old_mask = global_event_mask; + int any_old_per_sec_events = control_any_per_second_event_enabled(); + + global_event_mask = 0; + SMARTLIST_FOREACH(conns, connection_t *, _conn, + { + if (_conn->type == CONN_TYPE_CONTROL && + STATE_IS_OPEN(_conn->state)) { + control_connection_t *conn = TO_CONTROL_CONN(_conn); + global_event_mask |= conn->event_mask; + } + }); + + new_mask = global_event_mask; + + /* Handle the aftermath. Set up the log callback to tell us only what + * we want to hear...*/ + control_adjust_event_log_severity(); + + /* Macro: true if ev was false before and is true now. */ +#define NEWLY_ENABLED(ev) \ + (! (old_mask & (ev)) && (new_mask & (ev))) + + /* ...then, if we've started logging stream or circ bw, clear the + * appropriate fields. */ + if (NEWLY_ENABLED(EVENT_STREAM_BANDWIDTH_USED)) { + SMARTLIST_FOREACH(conns, connection_t *, conn, + { + if (conn->type == CONN_TYPE_AP) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + edge_conn->n_written = edge_conn->n_read = 0; + } + }); + } + if (NEWLY_ENABLED(EVENT_CIRC_BANDWIDTH_USED)) { + clear_circ_bw_fields(); + } + if (NEWLY_ENABLED(EVENT_BANDWIDTH_USED)) { + uint64_t r, w; + control_get_bytes_rw_last_sec(&r, &w); + } + if (any_old_per_sec_events != control_any_per_second_event_enabled()) { + rescan_periodic_events(get_options()); + } + +#undef NEWLY_ENABLED +} + +/** Given a control event code for a message event, return the corresponding + * log severity. */ +static inline int +event_to_log_severity(int event) +{ + switch (event) { + case EVENT_DEBUG_MSG: return LOG_DEBUG; + case EVENT_INFO_MSG: return LOG_INFO; + case EVENT_NOTICE_MSG: return LOG_NOTICE; + case EVENT_WARN_MSG: return LOG_WARN; + case EVENT_ERR_MSG: return LOG_ERR; + default: return -1; + } +} + +/** Adjust the log severities that result in control_event_logmsg being called + * to match the severity of log messages that any controllers are interested + * in. */ +void +control_adjust_event_log_severity(void) +{ + int i; + int min_log_event=EVENT_ERR_MSG, max_log_event=EVENT_DEBUG_MSG; + + for (i = EVENT_DEBUG_MSG; i <= EVENT_ERR_MSG; ++i) { + if (EVENT_IS_INTERESTING(i)) { + min_log_event = i; + break; + } + } + for (i = EVENT_ERR_MSG; i >= EVENT_DEBUG_MSG; --i) { + if (EVENT_IS_INTERESTING(i)) { + max_log_event = i; + break; + } + } + if (EVENT_IS_INTERESTING(EVENT_STATUS_GENERAL)) { + if (min_log_event > EVENT_NOTICE_MSG) + min_log_event = EVENT_NOTICE_MSG; + if (max_log_event < EVENT_ERR_MSG) + max_log_event = EVENT_ERR_MSG; + } + if (min_log_event <= max_log_event) + change_callback_log_severity(event_to_log_severity(min_log_event), + event_to_log_severity(max_log_event), + control_event_logmsg); + else + change_callback_log_severity(LOG_ERR, LOG_ERR, + control_event_logmsg); +} + +/** Return true iff the event with code <b>c</b> is being sent to any current + * control connection. This is useful if the amount of work needed to prepare + * to call the appropriate control_event_...() function is high. + */ +int +control_event_is_interesting(int event) +{ + return EVENT_IS_INTERESTING(event); +} + +/** Return true if any event that needs to fire once a second is enabled. */ +int +control_any_per_second_event_enabled(void) +{ + return ANY_EVENT_IS_INTERESTING( + EVENT_MASK_(EVENT_BANDWIDTH_USED) | + EVENT_MASK_(EVENT_CELL_STATS) | + EVENT_MASK_(EVENT_CIRC_BANDWIDTH_USED) | + EVENT_MASK_(EVENT_CONN_BW) | + EVENT_MASK_(EVENT_STREAM_BANDWIDTH_USED) + ); +} + +/* The value of 'get_bytes_read()' the previous time that + * control_get_bytes_rw_last_sec() as called. */ +static uint64_t stats_prev_n_read = 0; +/* The value of 'get_bytes_written()' the previous time that + * control_get_bytes_rw_last_sec() as called. */ +static uint64_t stats_prev_n_written = 0; + +/** + * Set <b>n_read</b> and <b>n_written</b> to the total number of bytes read + * and written by Tor since the last call to this function. + * + * Call this only from the main thread. + */ +static void +control_get_bytes_rw_last_sec(uint64_t *n_read, + uint64_t *n_written) +{ + const uint64_t stats_n_bytes_read = get_bytes_read(); + const uint64_t stats_n_bytes_written = get_bytes_written(); + + *n_read = stats_n_bytes_read - stats_prev_n_read; + *n_written = stats_n_bytes_written - stats_prev_n_written; + stats_prev_n_read = stats_n_bytes_read; + stats_prev_n_written = stats_n_bytes_written; +} + +/** + * Run all the controller events (if any) that are scheduled to trigger once + * per second. + */ +void +control_per_second_events(void) +{ + if (!control_any_per_second_event_enabled()) + return; + + uint64_t bytes_read, bytes_written; + control_get_bytes_rw_last_sec(&bytes_read, &bytes_written); + control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); + + control_event_stream_bandwidth_used(); + control_event_conn_bandwidth_used(); + control_event_circ_bandwidth_used(); + control_event_circuit_cell_stats(); +} + +/** Represents an event that's queued to be sent to one or more + * controllers. */ +typedef struct queued_event_t { + uint16_t event; + char *msg; +} queued_event_t; + +/** Pointer to int. If this is greater than 0, we don't allow new events to be + * queued. */ +static tor_threadlocal_t block_event_queue_flag; + +/** Holds a smartlist of queued_event_t objects that may need to be sent + * to one or more controllers */ +static smartlist_t *queued_control_events = NULL; + +/** True if the flush_queued_events_event is pending. */ +static int flush_queued_event_pending = 0; + +/** Lock to protect the above fields. */ +static tor_mutex_t *queued_control_events_lock = NULL; + +/** An event that should fire in order to flush the contents of + * queued_control_events. */ +static mainloop_event_t *flush_queued_events_event = NULL; + +void +control_initialize_event_queue(void) +{ + if (queued_control_events == NULL) { + queued_control_events = smartlist_new(); + } + + if (flush_queued_events_event == NULL) { + struct event_base *b = tor_libevent_get_base(); + if (b) { + flush_queued_events_event = + mainloop_event_new(flush_queued_events_cb, NULL); + tor_assert(flush_queued_events_event); + } + } + + if (queued_control_events_lock == NULL) { + queued_control_events_lock = tor_mutex_new(); + tor_threadlocal_init(&block_event_queue_flag); + } +} + +static int * +get_block_event_queue(void) +{ + int *val = tor_threadlocal_get(&block_event_queue_flag); + if (PREDICT_UNLIKELY(val == NULL)) { + val = tor_malloc_zero(sizeof(int)); + tor_threadlocal_set(&block_event_queue_flag, val); + } + return val; +} + +/** Helper: inserts an event on the list of events queued to be sent to + * one or more controllers, and schedules the events to be flushed if needed. + * + * This function takes ownership of <b>msg</b>, and may free it. + * + * We queue these events rather than send them immediately in order to break + * the dependency in our callgraph from code that generates events for the + * controller, and the network layer at large. Otherwise, nearly every + * interesting part of Tor would potentially call every other interesting part + * of Tor. + */ +MOCK_IMPL(STATIC void, +queue_control_event_string,(uint16_t event, char *msg)) +{ + /* This is redundant with checks done elsewhere, but it's a last-ditch + * attempt to avoid queueing something we shouldn't have to queue. */ + if (PREDICT_UNLIKELY( ! EVENT_IS_INTERESTING(event) )) { + tor_free(msg); + return; + } + + int *block_event_queue = get_block_event_queue(); + if (*block_event_queue) { + tor_free(msg); + return; + } + + queued_event_t *ev = tor_malloc(sizeof(*ev)); + ev->event = event; + ev->msg = msg; + + /* No queueing an event while queueing an event */ + ++*block_event_queue; + + tor_mutex_acquire(queued_control_events_lock); + tor_assert(queued_control_events); + smartlist_add(queued_control_events, ev); + + int activate_event = 0; + if (! flush_queued_event_pending && in_main_thread()) { + activate_event = 1; + flush_queued_event_pending = 1; + } + + tor_mutex_release(queued_control_events_lock); + + --*block_event_queue; + + /* We just put an event on the queue; mark the queue to be + * flushed. We only do this from the main thread for now; otherwise, + * we'd need to incur locking overhead in Libevent or use a socket. + */ + if (activate_event) { + tor_assert(flush_queued_events_event); + mainloop_event_activate(flush_queued_events_event); + } +} + +#define queued_event_free(ev) \ + FREE_AND_NULL(queued_event_t, queued_event_free_, (ev)) + +/** Release all storage held by <b>ev</b>. */ +static void +queued_event_free_(queued_event_t *ev) +{ + if (ev == NULL) + return; + + tor_free(ev->msg); + tor_free(ev); +} + +/** Send every queued event to every controller that's interested in it, + * and remove the events from the queue. If <b>force</b> is true, + * then make all controllers send their data out immediately, since we + * may be about to shut down. */ +static void +queued_events_flush_all(int force) +{ + /* Make sure that we get all the pending log events, if there are any. */ + flush_pending_log_callbacks(); + + if (PREDICT_UNLIKELY(queued_control_events == NULL)) { + return; + } + smartlist_t *all_conns = get_connection_array(); + smartlist_t *controllers = smartlist_new(); + smartlist_t *queued_events; + + int *block_event_queue = get_block_event_queue(); + ++*block_event_queue; + + tor_mutex_acquire(queued_control_events_lock); + /* No queueing an event while flushing events. */ + flush_queued_event_pending = 0; + queued_events = queued_control_events; + queued_control_events = smartlist_new(); + tor_mutex_release(queued_control_events_lock); + + /* Gather all the controllers that will care... */ + SMARTLIST_FOREACH_BEGIN(all_conns, connection_t *, conn) { + if (conn->type == CONN_TYPE_CONTROL && + !conn->marked_for_close && + conn->state == CONTROL_CONN_STATE_OPEN) { + control_connection_t *control_conn = TO_CONTROL_CONN(conn); + + smartlist_add(controllers, control_conn); + } + } SMARTLIST_FOREACH_END(conn); + + SMARTLIST_FOREACH_BEGIN(queued_events, queued_event_t *, ev) { + const event_mask_t bit = ((event_mask_t)1) << ev->event; + const size_t msg_len = strlen(ev->msg); + SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, + control_conn) { + if (control_conn->event_mask & bit) { + connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); + } + } SMARTLIST_FOREACH_END(control_conn); + + queued_event_free(ev); + } SMARTLIST_FOREACH_END(ev); + + if (force) { + SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, + control_conn) { + connection_flush(TO_CONN(control_conn)); + } SMARTLIST_FOREACH_END(control_conn); + } + + smartlist_free(queued_events); + smartlist_free(controllers); + + --*block_event_queue; +} + +/** Libevent callback: Flushes pending events to controllers that are + * interested in them. */ +static void +flush_queued_events_cb(mainloop_event_t *event, void *arg) +{ + (void) event; + (void) arg; + queued_events_flush_all(0); +} + +/** Send an event to all v1 controllers that are listening for code + * <b>event</b>. The event's body is given by <b>msg</b>. + * + * The EXTENDED_FORMAT and NONEXTENDED_FORMAT flags behave similarly with + * respect to the EXTENDED_EVENTS feature. */ +MOCK_IMPL(STATIC void, +send_control_event_string,(uint16_t event, + const char *msg)) +{ + tor_assert(event >= EVENT_MIN_ && event <= EVENT_MAX_); + queue_control_event_string(event, tor_strdup(msg)); +} + +/** Helper for send_control_event and control_event_status: + * Send an event to all v1 controllers that are listening for code + * <b>event</b>. The event's body is created by the printf-style format in + * <b>format</b>, and other arguments as provided. */ +static void +send_control_event_impl(uint16_t event, + const char *format, va_list ap) +{ + char *buf = NULL; + int len; + + len = tor_vasprintf(&buf, format, ap); + if (len < 0) { + log_warn(LD_BUG, "Unable to format event for controller."); + return; + } + + queue_control_event_string(event, buf); +} + +/** Send an event to all v1 controllers that are listening for code + * <b>event</b>. The event's body is created by the printf-style format in + * <b>format</b>, and other arguments as provided. */ +static void +send_control_event(uint16_t event, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + send_control_event_impl(event, format, ap); + va_end(ap); +} + +/** Something major has happened to circuit <b>circ</b>: tell any + * interested control connections. */ +int +control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp, + int reason_code) +{ + const char *status; + char reasons[64] = ""; + + if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS)) + return 0; + tor_assert(circ); + + switch (tp) + { + case CIRC_EVENT_LAUNCHED: status = "LAUNCHED"; break; + case CIRC_EVENT_BUILT: status = "BUILT"; break; + case CIRC_EVENT_EXTENDED: status = "EXTENDED"; break; + case CIRC_EVENT_FAILED: status = "FAILED"; break; + case CIRC_EVENT_CLOSED: status = "CLOSED"; break; + default: + log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); + tor_fragile_assert(); + return 0; + } + + if (tp == CIRC_EVENT_FAILED || tp == CIRC_EVENT_CLOSED) { + const char *reason_str = circuit_end_reason_to_control_string(reason_code); + char unk_reason_buf[16]; + if (!reason_str) { + tor_snprintf(unk_reason_buf, 16, "UNKNOWN_%d", reason_code); + reason_str = unk_reason_buf; + } + if (reason_code > 0 && reason_code & END_CIRC_REASON_FLAG_REMOTE) { + tor_snprintf(reasons, sizeof(reasons), + " REASON=DESTROYED REMOTE_REASON=%s", reason_str); + } else { + tor_snprintf(reasons, sizeof(reasons), + " REASON=%s", reason_str); + } + } + + { + char *circdesc = circuit_describe_status_for_controller(circ); + const char *sp = strlen(circdesc) ? " " : ""; + send_control_event(EVENT_CIRCUIT_STATUS, + "650 CIRC %lu %s%s%s%s\r\n", + (unsigned long)circ->global_identifier, + status, sp, + circdesc, + reasons); + tor_free(circdesc); + } + + return 0; +} + +/** Something minor has happened to circuit <b>circ</b>: tell any + * interested control connections. */ +static int +control_event_circuit_status_minor(origin_circuit_t *circ, + circuit_status_minor_event_t e, + int purpose, const struct timeval *tv) +{ + const char *event_desc; + char event_tail[160] = ""; + if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS_MINOR)) + return 0; + tor_assert(circ); + + switch (e) + { + case CIRC_MINOR_EVENT_PURPOSE_CHANGED: + event_desc = "PURPOSE_CHANGED"; + + { + /* event_tail can currently be up to 68 chars long */ + const char *hs_state_str = + circuit_purpose_to_controller_hs_state_string(purpose); + tor_snprintf(event_tail, sizeof(event_tail), + " OLD_PURPOSE=%s%s%s", + circuit_purpose_to_controller_string(purpose), + (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", + (hs_state_str != NULL) ? hs_state_str : ""); + } + + break; + case CIRC_MINOR_EVENT_CANNIBALIZED: + event_desc = "CANNIBALIZED"; + + { + /* event_tail can currently be up to 130 chars long */ + const char *hs_state_str = + circuit_purpose_to_controller_hs_state_string(purpose); + const struct timeval *old_timestamp_began = tv; + char tbuf[ISO_TIME_USEC_LEN+1]; + format_iso_time_nospace_usec(tbuf, old_timestamp_began); + + tor_snprintf(event_tail, sizeof(event_tail), + " OLD_PURPOSE=%s%s%s OLD_TIME_CREATED=%s", + circuit_purpose_to_controller_string(purpose), + (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", + (hs_state_str != NULL) ? hs_state_str : "", + tbuf); + } + + break; + default: + log_warn(LD_BUG, "Unrecognized status code %d", (int)e); + tor_fragile_assert(); + return 0; + } + + { + char *circdesc = circuit_describe_status_for_controller(circ); + const char *sp = strlen(circdesc) ? " " : ""; + send_control_event(EVENT_CIRCUIT_STATUS_MINOR, + "650 CIRC_MINOR %lu %s%s%s%s\r\n", + (unsigned long)circ->global_identifier, + event_desc, sp, + circdesc, + event_tail); + tor_free(circdesc); + } + + return 0; +} + +/** + * <b>circ</b> has changed its purpose from <b>old_purpose</b>: tell any + * interested controllers. + */ +int +control_event_circuit_purpose_changed(origin_circuit_t *circ, + int old_purpose) +{ + return control_event_circuit_status_minor(circ, + CIRC_MINOR_EVENT_PURPOSE_CHANGED, + old_purpose, + NULL); +} + +/** + * <b>circ</b> has changed its purpose from <b>old_purpose</b>, and its + * created-time from <b>old_tv_created</b>: tell any interested controllers. + */ +int +control_event_circuit_cannibalized(origin_circuit_t *circ, + int old_purpose, + const struct timeval *old_tv_created) +{ + return control_event_circuit_status_minor(circ, + CIRC_MINOR_EVENT_CANNIBALIZED, + old_purpose, + old_tv_created); +} + +/** Something has happened to the stream associated with AP connection + * <b>conn</b>: tell any interested control connections. */ +int +control_event_stream_status(entry_connection_t *conn, stream_status_event_t tp, + int reason_code) +{ + char reason_buf[64]; + char addrport_buf[64]; + const char *status; + circuit_t *circ; + origin_circuit_t *origin_circ = NULL; + char buf[256]; + const char *purpose = ""; + tor_assert(conn->socks_request); + + if (!EVENT_IS_INTERESTING(EVENT_STREAM_STATUS)) + return 0; + + if (tp == STREAM_EVENT_CLOSED && + (reason_code & END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED)) + return 0; + + write_stream_target_to_buf(conn, buf, sizeof(buf)); + + reason_buf[0] = '\0'; + switch (tp) + { + case STREAM_EVENT_SENT_CONNECT: status = "SENTCONNECT"; break; + case STREAM_EVENT_SENT_RESOLVE: status = "SENTRESOLVE"; break; + case STREAM_EVENT_SUCCEEDED: status = "SUCCEEDED"; break; + case STREAM_EVENT_FAILED: status = "FAILED"; break; + case STREAM_EVENT_CLOSED: status = "CLOSED"; break; + case STREAM_EVENT_NEW: status = "NEW"; break; + case STREAM_EVENT_NEW_RESOLVE: status = "NEWRESOLVE"; break; + case STREAM_EVENT_FAILED_RETRIABLE: status = "DETACHED"; break; + case STREAM_EVENT_REMAP: status = "REMAP"; break; + default: + log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); + return 0; + } + if (reason_code && (tp == STREAM_EVENT_FAILED || + tp == STREAM_EVENT_CLOSED || + tp == STREAM_EVENT_FAILED_RETRIABLE)) { + const char *reason_str = stream_end_reason_to_control_string(reason_code); + char *r = NULL; + if (!reason_str) { + tor_asprintf(&r, " UNKNOWN_%d", reason_code); + reason_str = r; + } + if (reason_code & END_STREAM_REASON_FLAG_REMOTE) + tor_snprintf(reason_buf, sizeof(reason_buf), + " REASON=END REMOTE_REASON=%s", reason_str); + else + tor_snprintf(reason_buf, sizeof(reason_buf), + " REASON=%s", reason_str); + tor_free(r); + } else if (reason_code && tp == STREAM_EVENT_REMAP) { + switch (reason_code) { + case REMAP_STREAM_SOURCE_CACHE: + strlcpy(reason_buf, " SOURCE=CACHE", sizeof(reason_buf)); + break; + case REMAP_STREAM_SOURCE_EXIT: + strlcpy(reason_buf, " SOURCE=EXIT", sizeof(reason_buf)); + break; + default: + tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=UNKNOWN_%d", + reason_code); + /* XXX do we want SOURCE=UNKNOWN_%d above instead? -RD */ + break; + } + } + + if (tp == STREAM_EVENT_NEW || tp == STREAM_EVENT_NEW_RESOLVE) { + /* + * When the control conn is an AF_UNIX socket and we have no address, + * it gets set to "(Tor_internal)"; see dnsserv_launch_request() in + * dnsserv.c. + */ + if (strcmp(ENTRY_TO_CONN(conn)->address, "(Tor_internal)") != 0) { + tor_snprintf(addrport_buf,sizeof(addrport_buf), " SOURCE_ADDR=%s:%d", + ENTRY_TO_CONN(conn)->address, ENTRY_TO_CONN(conn)->port); + } else { + /* + * else leave it blank so control on AF_UNIX doesn't need to make + * something up. + */ + addrport_buf[0] = '\0'; + } + } else { + addrport_buf[0] = '\0'; + } + + if (tp == STREAM_EVENT_NEW_RESOLVE) { + purpose = " PURPOSE=DNS_REQUEST"; + } else if (tp == STREAM_EVENT_NEW) { + if (conn->use_begindir) { + connection_t *linked = ENTRY_TO_CONN(conn)->linked_conn; + int linked_dir_purpose = -1; + if (linked && linked->type == CONN_TYPE_DIR) + linked_dir_purpose = linked->purpose; + if (DIR_PURPOSE_IS_UPLOAD(linked_dir_purpose)) + purpose = " PURPOSE=DIR_UPLOAD"; + else + purpose = " PURPOSE=DIR_FETCH"; + } else + purpose = " PURPOSE=USER"; + } + + circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); + if (circ && CIRCUIT_IS_ORIGIN(circ)) + origin_circ = TO_ORIGIN_CIRCUIT(circ); + + { + char *conndesc = entry_connection_describe_status_for_controller(conn); + const char *sp = strlen(conndesc) ? " " : ""; + send_control_event(EVENT_STREAM_STATUS, + "650 STREAM %"PRIu64" %s %lu %s%s%s%s%s%s\r\n", + (ENTRY_TO_CONN(conn)->global_identifier), + status, + origin_circ? + (unsigned long)origin_circ->global_identifier : 0ul, + buf, reason_buf, addrport_buf, purpose, sp, conndesc); + tor_free(conndesc); + } + + /* XXX need to specify its intended exit, etc? */ + + return 0; +} + +/** Called when the status of an OR connection <b>conn</b> changes: tell any + * interested control connections. <b>tp</b> is the new status for the + * connection. If <b>conn</b> has just closed or failed, then <b>reason</b> + * may be the reason why. + */ +int +control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, + int reason) +{ + int ncircs = 0; + const char *status; + char name[128]; + char ncircs_buf[32] = {0}; /* > 8 + log10(2^32)=10 + 2 */ + + if (!EVENT_IS_INTERESTING(EVENT_OR_CONN_STATUS)) + return 0; + + switch (tp) + { + case OR_CONN_EVENT_LAUNCHED: status = "LAUNCHED"; break; + case OR_CONN_EVENT_CONNECTED: status = "CONNECTED"; break; + case OR_CONN_EVENT_FAILED: status = "FAILED"; break; + case OR_CONN_EVENT_CLOSED: status = "CLOSED"; break; + case OR_CONN_EVENT_NEW: status = "NEW"; break; + default: + log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); + return 0; + } + if (conn->chan) { + ncircs = circuit_count_pending_on_channel(TLS_CHAN_TO_BASE(conn->chan)); + } else { + ncircs = 0; + } + ncircs += connection_or_get_num_circuits(conn); + if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { + tor_snprintf(ncircs_buf, sizeof(ncircs_buf), " NCIRCS=%d", ncircs); + } + + orconn_target_get_name(name, sizeof(name), conn); + send_control_event(EVENT_OR_CONN_STATUS, + "650 ORCONN %s %s%s%s%s ID=%"PRIu64"\r\n", + name, status, + reason ? " REASON=" : "", + orconn_end_reason_to_control_string(reason), + ncircs_buf, + (conn->base_.global_identifier)); + + return 0; +} + +/** + * Print out STREAM_BW event for a single conn + */ +int +control_event_stream_bandwidth(edge_connection_t *edge_conn) +{ + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; + if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { + if (!edge_conn->n_read && !edge_conn->n_written) + return 0; + + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); + send_control_event(EVENT_STREAM_BANDWIDTH_USED, + "650 STREAM_BW %"PRIu64" %lu %lu %s\r\n", + (edge_conn->base_.global_identifier), + (unsigned long)edge_conn->n_read, + (unsigned long)edge_conn->n_written, + tbuf); + + edge_conn->n_written = edge_conn->n_read = 0; + } + + return 0; +} + +/** A second or more has elapsed: tell any interested control + * connections how much bandwidth streams have used. */ +int +control_event_stream_bandwidth_used(void) +{ + if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { + smartlist_t *conns = get_connection_array(); + edge_connection_t *edge_conn; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) + { + if (conn->type != CONN_TYPE_AP) + continue; + edge_conn = TO_EDGE_CONN(conn); + if (!edge_conn->n_read && !edge_conn->n_written) + continue; + + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); + send_control_event(EVENT_STREAM_BANDWIDTH_USED, + "650 STREAM_BW %"PRIu64" %lu %lu %s\r\n", + (edge_conn->base_.global_identifier), + (unsigned long)edge_conn->n_read, + (unsigned long)edge_conn->n_written, + tbuf); + + edge_conn->n_written = edge_conn->n_read = 0; + } + SMARTLIST_FOREACH_END(conn); + } + + return 0; +} + +/** A second or more has elapsed: tell any interested control connections + * how much bandwidth origin circuits have used. */ +int +control_event_circ_bandwidth_used(void) +{ + if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) + return 0; + + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!CIRCUIT_IS_ORIGIN(circ)) + continue; + + control_event_circ_bandwidth_used_for_circ(TO_ORIGIN_CIRCUIT(circ)); + } + SMARTLIST_FOREACH_END(circ); + + return 0; +} + +/** + * Emit a CIRC_BW event line for a specific circuit. + * + * This function sets the values it emits to 0, and does not emit + * an event if there is no new data to report since the last call. + * + * Therefore, it may be called at any frequency. + */ +int +control_event_circ_bandwidth_used_for_circ(origin_circuit_t *ocirc) +{ + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; + + tor_assert(ocirc); + + if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) + return 0; + + /* n_read_circ_bw and n_written_circ_bw are always updated + * when there is any new cell on a circuit, and set to 0 after + * the event, below. + * + * Therefore, checking them is sufficient to determine if there + * is new data to report. */ + if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) + return 0; + + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); + send_control_event(EVENT_CIRC_BANDWIDTH_USED, + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu TIME=%s " + "DELIVERED_READ=%lu OVERHEAD_READ=%lu " + "DELIVERED_WRITTEN=%lu OVERHEAD_WRITTEN=%lu\r\n", + ocirc->global_identifier, + (unsigned long)ocirc->n_read_circ_bw, + (unsigned long)ocirc->n_written_circ_bw, + tbuf, + (unsigned long)ocirc->n_delivered_read_circ_bw, + (unsigned long)ocirc->n_overhead_read_circ_bw, + (unsigned long)ocirc->n_delivered_written_circ_bw, + (unsigned long)ocirc->n_overhead_written_circ_bw); + ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; + ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0; + ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0; + + return 0; +} + +/** Print out CONN_BW event for a single OR/DIR/EXIT <b>conn</b> and reset + * bandwidth counters. */ +int +control_event_conn_bandwidth(connection_t *conn) +{ + const char *conn_type_str; + if (!get_options()->TestingEnableConnBwEvent || + !EVENT_IS_INTERESTING(EVENT_CONN_BW)) + return 0; + if (!conn->n_read_conn_bw && !conn->n_written_conn_bw) + return 0; + switch (conn->type) { + case CONN_TYPE_OR: + conn_type_str = "OR"; + break; + case CONN_TYPE_DIR: + conn_type_str = "DIR"; + break; + case CONN_TYPE_EXIT: + conn_type_str = "EXIT"; + break; + default: + return 0; + } + send_control_event(EVENT_CONN_BW, + "650 CONN_BW ID=%"PRIu64" TYPE=%s " + "READ=%lu WRITTEN=%lu\r\n", + (conn->global_identifier), + conn_type_str, + (unsigned long)conn->n_read_conn_bw, + (unsigned long)conn->n_written_conn_bw); + conn->n_written_conn_bw = conn->n_read_conn_bw = 0; + return 0; +} + +/** A second or more has elapsed: tell any interested control + * connections how much bandwidth connections have used. */ +int +control_event_conn_bandwidth_used(void) +{ + if (get_options()->TestingEnableConnBwEvent && + EVENT_IS_INTERESTING(EVENT_CONN_BW)) { + SMARTLIST_FOREACH(get_connection_array(), connection_t *, conn, + control_event_conn_bandwidth(conn)); + } + return 0; +} + +/** Helper: iterate over cell statistics of <b>circ</b> and sum up added + * cells, removed cells, and waiting times by cell command and direction. + * Store results in <b>cell_stats</b>. Free cell statistics of the + * circuit afterwards. */ +void +sum_up_cell_stats_by_command(circuit_t *circ, cell_stats_t *cell_stats) +{ + memset(cell_stats, 0, sizeof(cell_stats_t)); + SMARTLIST_FOREACH_BEGIN(circ->testing_cell_stats, + const testing_cell_stats_entry_t *, ent) { + tor_assert(ent->command <= CELL_COMMAND_MAX_); + if (!ent->removed && !ent->exitward) { + cell_stats->added_cells_appward[ent->command] += 1; + } else if (!ent->removed && ent->exitward) { + cell_stats->added_cells_exitward[ent->command] += 1; + } else if (!ent->exitward) { + cell_stats->removed_cells_appward[ent->command] += 1; + cell_stats->total_time_appward[ent->command] += ent->waiting_time * 10; + } else { + cell_stats->removed_cells_exitward[ent->command] += 1; + cell_stats->total_time_exitward[ent->command] += ent->waiting_time * 10; + } + } SMARTLIST_FOREACH_END(ent); + circuit_clear_testing_cell_stats(circ); +} + +/** Helper: append a cell statistics string to <code>event_parts</code>, + * prefixed with <code>key</code>=. Statistics consist of comma-separated + * key:value pairs with lower-case command strings as keys and cell + * numbers or total waiting times as values. A key:value pair is included + * if the entry in <code>include_if_non_zero</code> is not zero, but with + * the (possibly zero) entry from <code>number_to_include</code>. Both + * arrays are expected to have a length of CELL_COMMAND_MAX_ + 1. If no + * entry in <code>include_if_non_zero</code> is positive, no string will + * be added to <code>event_parts</code>. */ +void +append_cell_stats_by_command(smartlist_t *event_parts, const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include) +{ + smartlist_t *key_value_strings = smartlist_new(); + int i; + for (i = 0; i <= CELL_COMMAND_MAX_; i++) { + if (include_if_non_zero[i] > 0) { + smartlist_add_asprintf(key_value_strings, "%s:%"PRIu64, + cell_command_to_string(i), + (number_to_include[i])); + } + } + if (smartlist_len(key_value_strings) > 0) { + char *joined = smartlist_join_strings(key_value_strings, ",", 0, NULL); + smartlist_add_asprintf(event_parts, "%s=%s", key, joined); + SMARTLIST_FOREACH(key_value_strings, char *, cp, tor_free(cp)); + tor_free(joined); + } + smartlist_free(key_value_strings); +} + +/** Helper: format <b>cell_stats</b> for <b>circ</b> for inclusion in a + * CELL_STATS event and write result string to <b>event_string</b>. */ +void +format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats) +{ + smartlist_t *event_parts = smartlist_new(); + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "ID=%lu", + (unsigned long)ocirc->global_identifier); + } else if (TO_OR_CIRCUIT(circ)->p_chan) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + smartlist_add_asprintf(event_parts, "InboundQueue=%lu", + (unsigned long)or_circ->p_circ_id); + smartlist_add_asprintf(event_parts, "InboundConn=%"PRIu64, + (or_circ->p_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "InboundAdded", + cell_stats->added_cells_appward, + cell_stats->added_cells_appward); + append_cell_stats_by_command(event_parts, "InboundRemoved", + cell_stats->removed_cells_appward, + cell_stats->removed_cells_appward); + append_cell_stats_by_command(event_parts, "InboundTime", + cell_stats->removed_cells_appward, + cell_stats->total_time_appward); + } + if (circ->n_chan) { + smartlist_add_asprintf(event_parts, "OutboundQueue=%lu", + (unsigned long)circ->n_circ_id); + smartlist_add_asprintf(event_parts, "OutboundConn=%"PRIu64, + (circ->n_chan->global_identifier)); + append_cell_stats_by_command(event_parts, "OutboundAdded", + cell_stats->added_cells_exitward, + cell_stats->added_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundRemoved", + cell_stats->removed_cells_exitward, + cell_stats->removed_cells_exitward); + append_cell_stats_by_command(event_parts, "OutboundTime", + cell_stats->removed_cells_exitward, + cell_stats->total_time_exitward); + } + *event_string = smartlist_join_strings(event_parts, " ", 0, NULL); + SMARTLIST_FOREACH(event_parts, char *, cp, tor_free(cp)); + smartlist_free(event_parts); +} + +/** A second or more has elapsed: tell any interested control connection + * how many cells have been processed for a given circuit. */ +int +control_event_circuit_cell_stats(void) +{ + cell_stats_t *cell_stats; + char *event_string; + if (!get_options()->TestingEnableCellStatsEvent || + !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) + return 0; + cell_stats = tor_malloc(sizeof(cell_stats_t)); + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!circ->testing_cell_stats) + continue; + sum_up_cell_stats_by_command(circ, cell_stats); + format_cell_stats(&event_string, circ, cell_stats); + send_control_event(EVENT_CELL_STATS, + "650 CELL_STATS %s\r\n", event_string); + tor_free(event_string); + } + SMARTLIST_FOREACH_END(circ); + tor_free(cell_stats); + return 0; +} + +/* about 5 minutes worth. */ +#define N_BW_EVENTS_TO_CACHE 300 +/* Index into cached_bw_events to next write. */ +static int next_measurement_idx = 0; +/* number of entries set in n_measurements */ +static int n_measurements = 0; +static struct cached_bw_event_t { + uint32_t n_read; + uint32_t n_written; +} cached_bw_events[N_BW_EVENTS_TO_CACHE]; + +/** A second or more has elapsed: tell any interested control + * connections how much bandwidth we used. */ +int +control_event_bandwidth_used(uint32_t n_read, uint32_t n_written) +{ + cached_bw_events[next_measurement_idx].n_read = n_read; + cached_bw_events[next_measurement_idx].n_written = n_written; + if (++next_measurement_idx == N_BW_EVENTS_TO_CACHE) + next_measurement_idx = 0; + if (n_measurements < N_BW_EVENTS_TO_CACHE) + ++n_measurements; + + if (EVENT_IS_INTERESTING(EVENT_BANDWIDTH_USED)) { + send_control_event(EVENT_BANDWIDTH_USED, + "650 BW %lu %lu\r\n", + (unsigned long)n_read, + (unsigned long)n_written); + } + + return 0; +} + +char * +get_bw_samples(void) +{ + int i; + int idx = (next_measurement_idx + N_BW_EVENTS_TO_CACHE - n_measurements) + % N_BW_EVENTS_TO_CACHE; + tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); + + smartlist_t *elements = smartlist_new(); + + for (i = 0; i < n_measurements; ++i) { + tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE); + const struct cached_bw_event_t *bwe = &cached_bw_events[idx]; + + smartlist_add_asprintf(elements, "%u,%u", + (unsigned)bwe->n_read, + (unsigned)bwe->n_written); + + idx = (idx + 1) % N_BW_EVENTS_TO_CACHE; + } + + char *result = smartlist_join_strings(elements, " ", 0, NULL); + + SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); + smartlist_free(elements); + + return result; +} + +/** Called when we are sending a log message to the controllers: suspend + * sending further log messages to the controllers until we're done. Used by + * CONN_LOG_PROTECT. */ +void +disable_control_logging(void) +{ + ++disable_log_messages; +} + +/** We're done sending a log message to the controllers: re-enable controller + * logging. Used by CONN_LOG_PROTECT. */ +void +enable_control_logging(void) +{ + if (--disable_log_messages < 0) + tor_assert(0); +} + +/** We got a log message: tell any interested control connections. */ +void +control_event_logmsg(int severity, log_domain_mask_t domain, const char *msg) +{ + int event; + + /* Don't even think of trying to add stuff to a buffer from a cpuworker + * thread. (See #25987 for plan to fix.) */ + if (! in_main_thread()) + return; + + if (disable_log_messages) + return; + + if (domain == LD_BUG && EVENT_IS_INTERESTING(EVENT_STATUS_GENERAL) && + severity <= LOG_NOTICE) { + char *esc = esc_for_log(msg); + ++disable_log_messages; + control_event_general_status(severity, "BUG REASON=%s", esc); + --disable_log_messages; + tor_free(esc); + } + + event = log_severity_to_event(severity); + if (event >= 0 && EVENT_IS_INTERESTING(event)) { + char *b = NULL; + const char *s; + if (strchr(msg, '\n')) { + char *cp; + b = tor_strdup(msg); + for (cp = b; *cp; ++cp) + if (*cp == '\r' || *cp == '\n') + *cp = ' '; + } + switch (severity) { + case LOG_DEBUG: s = "DEBUG"; break; + case LOG_INFO: s = "INFO"; break; + case LOG_NOTICE: s = "NOTICE"; break; + case LOG_WARN: s = "WARN"; break; + case LOG_ERR: s = "ERR"; break; + default: s = "UnknownLogSeverity"; break; + } + ++disable_log_messages; + send_control_event(event, "650 %s %s\r\n", s, b?b:msg); + if (severity == LOG_ERR) { + /* Force a flush, since we may be about to die horribly */ + queued_events_flush_all(1); + } + --disable_log_messages; + tor_free(b); + } +} + +/** + * Logging callback: called when there is a queued pending log callback. + */ +void +control_event_logmsg_pending(void) +{ + if (! in_main_thread()) { + /* We can't handle this case yet, since we're using a + * mainloop_event_t to invoke queued_events_flush_all. We ought to + * use a different mechanism instead: see #25987. + **/ + return; + } + tor_assert(flush_queued_events_event); + mainloop_event_activate(flush_queued_events_event); +} + +/** Called whenever we receive new router descriptors: tell any + * interested control connections. <b>routers</b> is a list of + * routerinfo_t's. + */ +int +control_event_descriptors_changed(smartlist_t *routers) +{ + char *msg; + + if (!EVENT_IS_INTERESTING(EVENT_NEW_DESC)) + return 0; + + { + smartlist_t *names = smartlist_new(); + char *ids; + SMARTLIST_FOREACH(routers, routerinfo_t *, ri, { + char *b = tor_malloc(MAX_VERBOSE_NICKNAME_LEN+1); + router_get_verbose_nickname(b, ri); + smartlist_add(names, b); + }); + ids = smartlist_join_strings(names, " ", 0, NULL); + tor_asprintf(&msg, "650 NEWDESC %s\r\n", ids); + send_control_event_string(EVENT_NEW_DESC, msg); + tor_free(ids); + tor_free(msg); + SMARTLIST_FOREACH(names, char *, cp, tor_free(cp)); + smartlist_free(names); + } + return 0; +} + +/** Called when an address mapping on <b>from</b> from changes to <b>to</b>. + * <b>expires</b> values less than 3 are special; see connection_edge.c. If + * <b>error</b> is non-NULL, it is an error code describing the failure + * mode of the mapping. + */ +int +control_event_address_mapped(const char *from, const char *to, time_t expires, + const char *error, const int cached) +{ + if (!EVENT_IS_INTERESTING(EVENT_ADDRMAP)) + return 0; + + if (expires < 3 || expires == TIME_MAX) + send_control_event(EVENT_ADDRMAP, + "650 ADDRMAP %s %s NEVER %s%s" + "CACHED=\"%s\"\r\n", + from, to, error?error:"", error?" ":"", + cached?"YES":"NO"); + else { + char buf[ISO_TIME_LEN+1]; + char buf2[ISO_TIME_LEN+1]; + format_local_iso_time(buf,expires); + format_iso_time(buf2,expires); + send_control_event(EVENT_ADDRMAP, + "650 ADDRMAP %s %s \"%s\"" + " %s%sEXPIRES=\"%s\" CACHED=\"%s\"\r\n", + from, to, buf, + error?error:"", error?" ":"", + buf2, cached?"YES":"NO"); + } + + return 0; +} +/** The network liveness has changed; this is called from circuitstats.c + * whenever we receive a cell, or when timeout expires and we assume the + * network is down. */ +int +control_event_network_liveness_update(int liveness) +{ + if (liveness > 0) { + if (get_cached_network_liveness() <= 0) { + /* Update cached liveness */ + set_cached_network_liveness(1); + log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS UP"); + send_control_event_string(EVENT_NETWORK_LIVENESS, + "650 NETWORK_LIVENESS UP\r\n"); + } + /* else was already live, no-op */ + } else { + if (get_cached_network_liveness() > 0) { + /* Update cached liveness */ + set_cached_network_liveness(0); + log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS DOWN"); + send_control_event_string(EVENT_NETWORK_LIVENESS, + "650 NETWORK_LIVENESS DOWN\r\n"); + } + /* else was already dead, no-op */ + } + + return 0; +} + +/** Helper function for NS-style events. Constructs and sends an event + * of type <b>event</b> with string <b>event_string</b> out of the set of + * networkstatuses <b>statuses</b>. Currently it is used for NS events + * and NEWCONSENSUS events. */ +static int +control_event_networkstatus_changed_helper(smartlist_t *statuses, + uint16_t event, + const char *event_string) +{ + smartlist_t *strs; + char *s, *esc = NULL; + if (!EVENT_IS_INTERESTING(event) || !smartlist_len(statuses)) + return 0; + + strs = smartlist_new(); + smartlist_add_strdup(strs, "650+"); + smartlist_add_strdup(strs, event_string); + smartlist_add_strdup(strs, "\r\n"); + SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs, + { + s = networkstatus_getinfo_helper_single(rs); + if (!s) continue; + smartlist_add(strs, s); + }); + + s = smartlist_join_strings(strs, "", 0, NULL); + write_escaped_data(s, strlen(s), &esc); + SMARTLIST_FOREACH(strs, char *, cp, tor_free(cp)); + smartlist_free(strs); + tor_free(s); + send_control_event_string(event, esc); + send_control_event_string(event, + "650 OK\r\n"); + + tor_free(esc); + return 0; +} + +/** Called when the routerstatus_ts <b>statuses</b> have changed: sends + * an NS event to any controller that cares. */ +int +control_event_networkstatus_changed(smartlist_t *statuses) +{ + return control_event_networkstatus_changed_helper(statuses, EVENT_NS, "NS"); +} + +/** Called when we get a new consensus networkstatus. Sends a NEWCONSENSUS + * event consisting of an NS-style line for each relay in the consensus. */ +int +control_event_newconsensus(const networkstatus_t *consensus) +{ + if (!control_event_is_interesting(EVENT_NEWCONSENSUS)) + return 0; + return control_event_networkstatus_changed_helper( + consensus->routerstatus_list, EVENT_NEWCONSENSUS, "NEWCONSENSUS"); +} + +/** Called when we compute a new circuitbuildtimeout */ +int +control_event_buildtimeout_set(buildtimeout_set_event_t type, + const char *args) +{ + const char *type_string = NULL; + + if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET)) + return 0; + + switch (type) { + case BUILDTIMEOUT_SET_EVENT_COMPUTED: + type_string = "COMPUTED"; + break; + case BUILDTIMEOUT_SET_EVENT_RESET: + type_string = "RESET"; + break; + case BUILDTIMEOUT_SET_EVENT_SUSPENDED: + type_string = "SUSPENDED"; + break; + case BUILDTIMEOUT_SET_EVENT_DISCARD: + type_string = "DISCARD"; + break; + case BUILDTIMEOUT_SET_EVENT_RESUME: + type_string = "RESUME"; + break; + default: + type_string = "UNKNOWN"; + break; + } + + send_control_event(EVENT_BUILDTIMEOUT_SET, + "650 BUILDTIMEOUT_SET %s %s\r\n", + type_string, args); + + return 0; +} + +/** Called when a signal has been processed from signal_callback */ +int +control_event_signal(uintptr_t signal_num) +{ + const char *signal_string = NULL; + + if (!control_event_is_interesting(EVENT_GOT_SIGNAL)) + return 0; + + for (unsigned i = 0; signal_table[i].signal_name != NULL; ++i) { + if ((int)signal_num == signal_table[i].sig) { + signal_string = signal_table[i].signal_name; + break; + } + } + + if (signal_string == NULL) { + log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal", + (unsigned long)signal_num); + return -1; + } + + send_control_event(EVENT_GOT_SIGNAL, "650 SIGNAL %s\r\n", + signal_string); + return 0; +} + +/** Called when a single local_routerstatus_t has changed: Sends an NS event + * to any controller that cares. */ +int +control_event_networkstatus_changed_single(const routerstatus_t *rs) +{ + smartlist_t *statuses; + int r; + + if (!EVENT_IS_INTERESTING(EVENT_NS)) + return 0; + + statuses = smartlist_new(); + smartlist_add(statuses, (void*)rs); + r = control_event_networkstatus_changed(statuses); + smartlist_free(statuses); + return r; +} + +/** Our own router descriptor has changed; tell any controllers that care. + */ +int +control_event_my_descriptor_changed(void) +{ + send_control_event(EVENT_DESCCHANGED, "650 DESCCHANGED\r\n"); + return 0; +} + +/** Helper: sends a status event where <b>type</b> is one of + * EVENT_STATUS_{GENERAL,CLIENT,SERVER}, where <b>severity</b> is one of + * LOG_{NOTICE,WARN,ERR}, and where <b>format</b> is a printf-style format + * string corresponding to <b>args</b>. */ +static int +control_event_status(int type, int severity, const char *format, va_list args) +{ + char *user_buf = NULL; + char format_buf[160]; + const char *status, *sev; + + switch (type) { + case EVENT_STATUS_GENERAL: + status = "STATUS_GENERAL"; + break; + case EVENT_STATUS_CLIENT: + status = "STATUS_CLIENT"; + break; + case EVENT_STATUS_SERVER: + status = "STATUS_SERVER"; + break; + default: + log_warn(LD_BUG, "Unrecognized status type %d", type); + return -1; + } + switch (severity) { + case LOG_NOTICE: + sev = "NOTICE"; + break; + case LOG_WARN: + sev = "WARN"; + break; + case LOG_ERR: + sev = "ERR"; + break; + default: + log_warn(LD_BUG, "Unrecognized status severity %d", severity); + return -1; + } + if (tor_snprintf(format_buf, sizeof(format_buf), "650 %s %s", + status, sev)<0) { + log_warn(LD_BUG, "Format string too long."); + return -1; + } + if (tor_vasprintf(&user_buf, format, args)<0) { + log_warn(LD_BUG, "Failed to create user buffer."); + return -1; + } + + send_control_event(type, "%s %s\r\n", format_buf, user_buf); + tor_free(user_buf); + return 0; +} + +#ifndef COCCI +#define CONTROL_EVENT_STATUS_BODY(event, sev) \ + int r; \ + do { \ + va_list ap; \ + if (!EVENT_IS_INTERESTING(event)) \ + return 0; \ + \ + va_start(ap, format); \ + r = control_event_status((event), (sev), format, ap); \ + va_end(ap); \ + } while (0) +#endif /* !defined(COCCI) */ + +/** Format and send an EVENT_STATUS_GENERAL event whose main text is obtained + * by formatting the arguments using the printf-style <b>format</b>. */ +int +control_event_general_status(int severity, const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, severity); + return r; +} + +/** Format and send an EVENT_STATUS_GENERAL LOG_ERR event, and flush it to the + * controller(s) immediately. */ +int +control_event_general_error(const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, LOG_ERR); + /* Force a flush, since we may be about to die horribly */ + queued_events_flush_all(1); + return r; +} + +/** Format and send an EVENT_STATUS_CLIENT event whose main text is obtained + * by formatting the arguments using the printf-style <b>format</b>. */ +int +control_event_client_status(int severity, const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, severity); + return r; +} + +/** Format and send an EVENT_STATUS_CLIENT LOG_ERR event, and flush it to the + * controller(s) immediately. */ +int +control_event_client_error(const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, LOG_ERR); + /* Force a flush, since we may be about to die horribly */ + queued_events_flush_all(1); + return r; +} + +/** Format and send an EVENT_STATUS_SERVER event whose main text is obtained + * by formatting the arguments using the printf-style <b>format</b>. */ +int +control_event_server_status(int severity, const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, severity); + return r; +} + +/** Format and send an EVENT_STATUS_SERVER LOG_ERR event, and flush it to the + * controller(s) immediately. */ +int +control_event_server_error(const char *format, ...) +{ + CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, LOG_ERR); + /* Force a flush, since we may be about to die horribly */ + queued_events_flush_all(1); + return r; +} + +/** Called when the status of an entry guard with the given <b>nickname</b> + * and identity <b>digest</b> has changed to <b>status</b>: tells any + * controllers that care. */ +int +control_event_guard(const char *nickname, const char *digest, + const char *status) +{ + char hbuf[HEX_DIGEST_LEN+1]; + base16_encode(hbuf, sizeof(hbuf), digest, DIGEST_LEN); + if (!EVENT_IS_INTERESTING(EVENT_GUARD)) + return 0; + + { + char buf[MAX_VERBOSE_NICKNAME_LEN+1]; + const node_t *node = node_get_by_id(digest); + if (node) { + node_get_verbose_nickname(node, buf); + } else { + tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname); + } + send_control_event(EVENT_GUARD, + "650 GUARD ENTRY %s %s\r\n", buf, status); + } + return 0; +} + +/** Called when a configuration option changes. This is generally triggered + * 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) +{ + char *result; + smartlist_t *lines; + if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) || !changes) { + return; + } + lines = smartlist_new(); + 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", line->key, line->value); + } + } + result = smartlist_join_strings(lines, "\r\n", 0, NULL); + send_control_event(EVENT_CONF_CHANGED, + "650-CONF_CHANGED\r\n%s\r\n650 OK\r\n", result); + tor_free(result); + SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); + smartlist_free(lines); +} + +/** We just generated a new summary of which countries we've seen clients + * from recently. Send a copy to the controller in case it wants to + * display it for the user. */ +void +control_event_clients_seen(const char *controller_str) +{ + send_control_event(EVENT_CLIENTS_SEEN, + "650 CLIENTS_SEEN %s\r\n", controller_str); +} + +/** A new pluggable transport called <b>transport_name</b> was + * launched on <b>addr</b>:<b>port</b>. <b>mode</b> is either + * "server" or "client" depending on the mode of the pluggable + * transport. + * "650" SP "TRANSPORT_LAUNCHED" SP Mode SP Name SP Address SP Port + */ +void +control_event_transport_launched(const char *mode, const char *transport_name, + tor_addr_t *addr, uint16_t port) +{ + send_control_event(EVENT_TRANSPORT_LAUNCHED, + "650 TRANSPORT_LAUNCHED %s %s %s %u\r\n", + mode, transport_name, fmt_addr(addr), port); +} + +/** A pluggable transport called <b>pt_name</b> has emitted a log message + * found in <b>message</b> at <b>severity</b> log level. */ +void +control_event_pt_log(const char *log) +{ + send_control_event(EVENT_PT_LOG, + "650 PT_LOG %s\r\n", + log); +} + +/** A pluggable transport has emitted a STATUS message found in + * <b>status</b>. */ +void +control_event_pt_status(const char *status) +{ + send_control_event(EVENT_PT_STATUS, + "650 PT_STATUS %s\r\n", + status); +} + +/** Convert rendezvous auth type to string for HS_DESC control events + */ +const char * +rend_auth_type_to_string(rend_auth_type_t auth_type) +{ + const char *str; + + switch (auth_type) { + case REND_NO_AUTH: + str = "NO_AUTH"; + break; + case REND_BASIC_AUTH: + str = "BASIC_AUTH"; + break; + case REND_STEALTH_AUTH: + str = "STEALTH_AUTH"; + break; + default: + str = "UNKNOWN"; + } + + return str; +} + +/** Return either the onion address if the given pointer is a non empty + * string else the unknown string. */ +static const char * +rend_hsaddress_str_or_unknown(const char *onion_address) +{ + static const char *str_unknown = "UNKNOWN"; + const char *str_ret = str_unknown; + + /* No valid pointer, unknown it is. */ + if (!onion_address) { + goto end; + } + /* Empty onion address thus we don't know, unknown it is. */ + if (onion_address[0] == '\0') { + goto end; + } + /* All checks are good so return the given onion address. */ + str_ret = onion_address; + + end: + return str_ret; +} + +/** send HS_DESC requested event. + * + * <b>rend_query</b> is used to fetch requested onion address and auth type. + * <b>hs_dir</b> is the description of contacting hs directory. + * <b>desc_id_base32</b> is the ID of requested hs descriptor. + * <b>hsdir_index</b> is the HSDir fetch index value for v3, an hex string. + */ +void +control_event_hs_descriptor_requested(const char *onion_address, + rend_auth_type_t auth_type, + const char *id_digest, + const char *desc_id, + const char *hsdir_index) +{ + char *hsdir_index_field = NULL; + + if (BUG(!id_digest || !desc_id)) { + return; + } + + if (hsdir_index) { + tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC REQUESTED %s %s %s %s%s\r\n", + rend_hsaddress_str_or_unknown(onion_address), + rend_auth_type_to_string(auth_type), + node_describe_longname_by_id(id_digest), + desc_id, + hsdir_index_field ? hsdir_index_field : ""); + tor_free(hsdir_index_field); +} + +/** send HS_DESC CREATED event when a local service generates a descriptor. + * + * <b>onion_address</b> is service address. + * <b>desc_id</b> is the descriptor ID. + * <b>replica</b> is the the descriptor replica number. If it is negative, it + * is ignored. + */ +void +control_event_hs_descriptor_created(const char *onion_address, + const char *desc_id, + int replica) +{ + char *replica_field = NULL; + + if (BUG(!onion_address || !desc_id)) { + return; + } + + if (replica >= 0) { + tor_asprintf(&replica_field, " REPLICA=%d", replica); + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s%s\r\n", + onion_address, desc_id, + replica_field ? replica_field : ""); + tor_free(replica_field); +} + +/** send HS_DESC upload event. + * + * <b>onion_address</b> is service address. + * <b>hs_dir</b> is the description of contacting hs directory. + * <b>desc_id</b> is the ID of requested hs descriptor. + */ +void +control_event_hs_descriptor_upload(const char *onion_address, + const char *id_digest, + const char *desc_id, + const char *hsdir_index) +{ + char *hsdir_index_field = NULL; + + if (BUG(!onion_address || !id_digest || !desc_id)) { + return; + } + + if (hsdir_index) { + tor_asprintf(&hsdir_index_field, " HSDIR_INDEX=%s", hsdir_index); + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC UPLOAD %s UNKNOWN %s %s%s\r\n", + onion_address, + node_describe_longname_by_id(id_digest), + desc_id, + hsdir_index_field ? hsdir_index_field : ""); + tor_free(hsdir_index_field); +} + +/** send HS_DESC event after got response from hs directory. + * + * NOTE: this is an internal function used by following functions: + * control_event_hsv2_descriptor_received + * control_event_hsv2_descriptor_failed + * control_event_hsv3_descriptor_failed + * + * So do not call this function directly. + */ +static void +event_hs_descriptor_receive_end(const char *action, + const char *onion_address, + const char *desc_id, + rend_auth_type_t auth_type, + const char *hsdir_id_digest, + const char *reason) +{ + char *reason_field = NULL; + + if (BUG(!action || !onion_address)) { + return; + } + + if (reason) { + tor_asprintf(&reason_field, " REASON=%s", reason); + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC %s %s %s %s%s%s\r\n", + action, + rend_hsaddress_str_or_unknown(onion_address), + rend_auth_type_to_string(auth_type), + hsdir_id_digest ? + node_describe_longname_by_id(hsdir_id_digest) : + "UNKNOWN", + desc_id ? desc_id : "", + reason_field ? reason_field : ""); + + tor_free(reason_field); +} + +/** send HS_DESC event after got response from hs directory. + * + * NOTE: this is an internal function used by following functions: + * control_event_hs_descriptor_uploaded + * control_event_hs_descriptor_upload_failed + * + * So do not call this function directly. + */ +void +control_event_hs_descriptor_upload_end(const char *action, + const char *onion_address, + const char *id_digest, + const char *reason) +{ + char *reason_field = NULL; + + if (BUG(!action || !id_digest)) { + return; + } + + if (reason) { + tor_asprintf(&reason_field, " REASON=%s", reason); + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC %s %s UNKNOWN %s%s\r\n", + action, + rend_hsaddress_str_or_unknown(onion_address), + node_describe_longname_by_id(id_digest), + reason_field ? reason_field : ""); + + tor_free(reason_field); +} + +/** For an HS descriptor query <b>rend_data</b>, using the + * <b>onion_address</b> and HSDir fingerprint <b>hsdir_fp</b>, find out + * which descriptor ID in the query is the right one. + * + * Return a pointer of the binary descriptor ID found in the query's object + * or NULL if not found. */ +static const char * +get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) +{ + int replica; + const char *desc_id = NULL; + const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data); + + /* Possible if the fetch was done using a descriptor ID. This means that + * the HSFETCH command was used. */ + if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) { + desc_id = rend_data_v2->desc_id_fetch; + goto end; + } + + /* Without a directory fingerprint at this stage, we can't do much. */ + if (hsdir_fp == NULL) { + goto end; + } + + /* OK, we have an onion address so now let's find which descriptor ID + * is the one associated with the HSDir fingerprint. */ + for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; + replica++) { + const char *digest = rend_data_get_desc_id(rend_data, replica, NULL); + + SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) { + if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) { + /* Found it! This descriptor ID is the right one. */ + desc_id = digest; + goto end; + } + } SMARTLIST_FOREACH_END(fingerprint); + } + + end: + return desc_id; +} + +/** send HS_DESC RECEIVED event + * + * called when we successfully received a hidden service descriptor. + */ +void +control_event_hsv2_descriptor_received(const char *onion_address, + const rend_data_t *rend_data, + const char *hsdir_id_digest) +{ + char *desc_id_field = NULL; + const char *desc_id; + + if (BUG(!rend_data || !hsdir_id_digest || !onion_address)) { + return; + } + + desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); + if (desc_id != NULL) { + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + /* Set the descriptor ID digest to base32 so we can send it. */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + /* Extra whitespace is needed before the value. */ + tor_asprintf(&desc_id_field, " %s", desc_id_base32); + } + + event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, + TO_REND_DATA_V2(rend_data)->auth_type, + hsdir_id_digest, NULL); + tor_free(desc_id_field); +} + +/* Send HS_DESC RECEIVED event + * + * Called when we successfully received a hidden service descriptor. */ +void +control_event_hsv3_descriptor_received(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest) +{ + char *desc_id_field = NULL; + + if (BUG(!onion_address || !desc_id || !hsdir_id_digest)) { + return; + } + + /* Because DescriptorID is an optional positional value, we need to add a + * whitespace before in order to not be next to the HsDir value. */ + tor_asprintf(&desc_id_field, " %s", desc_id); + + event_hs_descriptor_receive_end("RECEIVED", onion_address, desc_id_field, + REND_NO_AUTH, hsdir_id_digest, NULL); + tor_free(desc_id_field); +} + +/** send HS_DESC UPLOADED event + * + * called when we successfully uploaded a hidden service descriptor. + */ +void +control_event_hs_descriptor_uploaded(const char *id_digest, + const char *onion_address) +{ + if (BUG(!id_digest)) { + return; + } + + control_event_hs_descriptor_upload_end("UPLOADED", onion_address, + id_digest, NULL); +} + +/** Send HS_DESC event to inform controller that query <b>rend_data</b> + * failed to retrieve hidden service descriptor from directory identified by + * <b>id_digest</b>. If NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, + * add it to REASON= field. + */ +void +control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, + const char *hsdir_id_digest, + const char *reason) +{ + char *desc_id_field = NULL; + const char *desc_id; + + if (BUG(!rend_data)) { + return; + } + + desc_id = get_desc_id_from_query(rend_data, hsdir_id_digest); + if (desc_id != NULL) { + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + /* Set the descriptor ID digest to base32 so we can send it. */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + /* Extra whitespace is needed before the value. */ + tor_asprintf(&desc_id_field, " %s", desc_id_base32); + } + + event_hs_descriptor_receive_end("FAILED", rend_data_get_address(rend_data), + desc_id_field, + TO_REND_DATA_V2(rend_data)->auth_type, + hsdir_id_digest, reason); + tor_free(desc_id_field); +} + +/** Send HS_DESC event to inform controller that the query to + * <b>onion_address</b> failed to retrieve hidden service descriptor + * <b>desc_id</b> from directory identified by <b>hsdir_id_digest</b>. If + * NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL, add it to REASON= + * field. */ +void +control_event_hsv3_descriptor_failed(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest, + const char *reason) +{ + char *desc_id_field = NULL; + + if (BUG(!onion_address || !desc_id || !reason)) { + return; + } + + /* Because DescriptorID is an optional positional value, we need to add a + * whitespace before in order to not be next to the HsDir value. */ + tor_asprintf(&desc_id_field, " %s", desc_id); + + event_hs_descriptor_receive_end("FAILED", onion_address, desc_id_field, + REND_NO_AUTH, hsdir_id_digest, reason); + tor_free(desc_id_field); +} + +/** Send HS_DESC_CONTENT event after completion of a successful fetch + * from hs directory. If <b>hsdir_id_digest</b> is NULL, it is replaced + * by "UNKNOWN". If <b>content</b> is NULL, it is replaced by an empty + * string. The <b>onion_address</b> or <b>desc_id</b> set to NULL will + * not trigger the control event. */ +void +control_event_hs_descriptor_content(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest, + const char *content) +{ + static const char *event_name = "HS_DESC_CONTENT"; + char *esc_content = NULL; + + if (!onion_address || !desc_id) { + log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, ", + onion_address, desc_id); + return; + } + + if (content == NULL) { + /* Point it to empty content so it can still be escaped. */ + content = ""; + } + write_escaped_data(content, strlen(content), &esc_content); + + send_control_event(EVENT_HS_DESC_CONTENT, + "650+%s %s %s %s\r\n%s650 OK\r\n", + event_name, + rend_hsaddress_str_or_unknown(onion_address), + desc_id, + hsdir_id_digest ? + node_describe_longname_by_id(hsdir_id_digest) : + "UNKNOWN", + esc_content); + tor_free(esc_content); +} + +/** Send HS_DESC event to inform controller upload of hidden service + * descriptor identified by <b>id_digest</b> failed. If <b>reason</b> + * is not NULL, add it to REASON= field. + */ +void +control_event_hs_descriptor_upload_failed(const char *id_digest, + const char *onion_address, + const char *reason) +{ + if (BUG(!id_digest)) { + return; + } + control_event_hs_descriptor_upload_end("FAILED", onion_address, + id_digest, reason); +} + +void +control_events_free_all(void) +{ + smartlist_t *queued_events = NULL; + + stats_prev_n_read = stats_prev_n_written = 0; + + if (queued_control_events_lock) { + tor_mutex_acquire(queued_control_events_lock); + flush_queued_event_pending = 0; + queued_events = queued_control_events; + queued_control_events = NULL; + tor_mutex_release(queued_control_events_lock); + } + if (queued_events) { + SMARTLIST_FOREACH(queued_events, queued_event_t *, ev, + queued_event_free(ev)); + smartlist_free(queued_events); + } + if (flush_queued_events_event) { + mainloop_event_free(flush_queued_events_event); + flush_queued_events_event = NULL; + } + global_event_mask = 0; + disable_log_messages = 0; +} + +#ifdef TOR_UNIT_TESTS +/* For testing: change the value of global_event_mask */ +void +control_testing_set_global_event_mask(uint64_t mask) +{ + global_event_mask = mask; +} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/control/control_events.h b/src/feature/control/control_events.h new file mode 100644 index 0000000000..74bbc0047d --- /dev/null +++ b/src/feature/control/control_events.h @@ -0,0 +1,353 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_events.h + * \brief Header file for control_events.c. + **/ + +#ifndef TOR_CONTROL_EVENTS_H +#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 . */ +typedef enum circuit_status_minor_event_t { + CIRC_MINOR_EVENT_PURPOSE_CHANGED, + CIRC_MINOR_EVENT_CANNIBALIZED, +} circuit_status_minor_event_t; + +/** 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 { + STREAM_EVENT_SENT_CONNECT = 0, + STREAM_EVENT_SENT_RESOLVE = 1, + STREAM_EVENT_SUCCEEDED = 2, + STREAM_EVENT_FAILED = 3, + STREAM_EVENT_CLOSED = 4, + STREAM_EVENT_NEW = 5, + STREAM_EVENT_NEW_RESOLVE = 6, + STREAM_EVENT_FAILED_RETRIABLE = 7, + STREAM_EVENT_REMAP = 8 +} stream_status_event_t; + +/** Used to indicate the type of a buildtime event */ +typedef enum buildtimeout_set_event_t { + BUILDTIMEOUT_SET_EVENT_COMPUTED = 0, + BUILDTIMEOUT_SET_EVENT_RESET = 1, + BUILDTIMEOUT_SET_EVENT_SUSPENDED = 2, + BUILDTIMEOUT_SET_EVENT_DISCARD = 3, + BUILDTIMEOUT_SET_EVENT_RESUME = 4 +} buildtimeout_set_event_t; + +/** Enum describing various stages of bootstrapping, for use with controller + * bootstrap status events. The values range from 0 to 100. */ +typedef enum { + BOOTSTRAP_STATUS_UNDEF=-1, + BOOTSTRAP_STATUS_STARTING=0, + + /* Initial connection to any relay */ + + BOOTSTRAP_STATUS_CONN_PT=1, + BOOTSTRAP_STATUS_CONN_DONE_PT=2, + BOOTSTRAP_STATUS_CONN_PROXY=3, + BOOTSTRAP_STATUS_CONN_DONE_PROXY=4, + BOOTSTRAP_STATUS_CONN=5, + BOOTSTRAP_STATUS_CONN_DONE=10, + BOOTSTRAP_STATUS_HANDSHAKE=14, + BOOTSTRAP_STATUS_HANDSHAKE_DONE=15, + + /* Loading directory info */ + + BOOTSTRAP_STATUS_ONEHOP_CREATE=20, + BOOTSTRAP_STATUS_REQUESTING_STATUS=25, + BOOTSTRAP_STATUS_LOADING_STATUS=30, + BOOTSTRAP_STATUS_LOADING_KEYS=40, + BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS=45, + BOOTSTRAP_STATUS_LOADING_DESCRIPTORS=50, + BOOTSTRAP_STATUS_ENOUGH_DIRINFO=75, + + /* Connecting to a relay for AP circuits */ + + BOOTSTRAP_STATUS_AP_CONN_PT=76, + BOOTSTRAP_STATUS_AP_CONN_DONE_PT=77, + BOOTSTRAP_STATUS_AP_CONN_PROXY=78, + BOOTSTRAP_STATUS_AP_CONN_DONE_PROXY=79, + BOOTSTRAP_STATUS_AP_CONN=80, + BOOTSTRAP_STATUS_AP_CONN_DONE=85, + BOOTSTRAP_STATUS_AP_HANDSHAKE=89, + BOOTSTRAP_STATUS_AP_HANDSHAKE_DONE=90, + + /* Creating AP circuits */ + + BOOTSTRAP_STATUS_CIRCUIT_CREATE=95, + BOOTSTRAP_STATUS_DONE=100 +} bootstrap_status_t; + +/** Reason for remapping an AP connection's address: we have a cached + * answer. */ +#define REMAP_STREAM_SOURCE_CACHE 1 +/** Reason for remapping an AP connection's address: the exit node told us an + * answer. */ +#define REMAP_STREAM_SOURCE_EXIT 2 + +void control_initialize_event_queue(void); + +void control_update_global_event_mask(void); +void control_adjust_event_log_severity(void); + +#define EVENT_NS 0x000F +int control_event_is_interesting(int event); + +void control_per_second_events(void); +int control_any_per_second_event_enabled(void); + +int control_event_circuit_status(origin_circuit_t *circ, + circuit_status_event_t e, int reason); +int control_event_circuit_purpose_changed(origin_circuit_t *circ, + int old_purpose); +int control_event_circuit_cannibalized(origin_circuit_t *circ, + int old_purpose, + const struct timeval *old_tv_created); +int control_event_stream_status(entry_connection_t *conn, + stream_status_event_t e, + int reason); +int control_event_or_conn_status(or_connection_t *conn, + or_conn_status_event_t e, int reason); +int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); +int control_event_stream_bandwidth(edge_connection_t *edge_conn); +int control_event_stream_bandwidth_used(void); +int control_event_circ_bandwidth_used(void); +int control_event_circ_bandwidth_used_for_circ(origin_circuit_t *ocirc); +int control_event_conn_bandwidth(connection_t *conn); +int control_event_conn_bandwidth_used(void); +int control_event_circuit_cell_stats(void); +void control_event_logmsg(int severity, log_domain_mask_t domain, + const char *msg); +void control_event_logmsg_pending(void); +int control_event_descriptors_changed(smartlist_t *routers); +int control_event_address_mapped(const char *from, const char *to, + time_t expires, const char *error, + const int cached); +int control_event_my_descriptor_changed(void); +int control_event_network_liveness_update(int liveness); +int control_event_networkstatus_changed(smartlist_t *statuses); + +int control_event_newconsensus(const networkstatus_t *consensus); +int control_event_networkstatus_changed_single(const routerstatus_t *rs); +int control_event_general_status(int severity, const char *format, ...) + CHECK_PRINTF(2,3); +int control_event_client_status(int severity, const char *format, ...) + CHECK_PRINTF(2,3); +int control_event_server_status(int severity, const char *format, ...) + CHECK_PRINTF(2,3); + +int control_event_general_error(const char *format, ...) + CHECK_PRINTF(1,2); +int control_event_client_error(const char *format, ...) + CHECK_PRINTF(1,2); +int control_event_server_error(const char *format, ...) + CHECK_PRINTF(1,2); + +int control_event_guard(const char *nickname, const char *digest, + const char *status); +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); + +void control_event_bootstrap(bootstrap_status_t status, int progress); +MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, + int reason, + or_connection_t *or_conn)); +void control_event_boot_dir(bootstrap_status_t status, int progress); +void control_event_boot_first_orconn(void); +void control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn); +char *control_event_boot_last_msg(void); +void control_event_bootstrap_reset(void); + +void control_event_clients_seen(const char *controller_str); +void control_event_transport_launched(const char *mode, + const char *transport_name, + tor_addr_t *addr, uint16_t port); +void control_event_pt_log(const char *log); +void control_event_pt_status(const char *status); + +void control_event_hs_descriptor_requested(const char *onion_address, + rend_auth_type_t auth_type, + const char *id_digest, + const char *desc_id, + const char *hsdir_index); +void control_event_hs_descriptor_created(const char *onion_address, + const char *desc_id, + int replica); +void control_event_hs_descriptor_upload(const char *onion_address, + const char *desc_id, + const char *hs_dir, + const char *hsdir_index); +void control_event_hs_descriptor_upload_end(const char *action, + const char *onion_address, + const char *hs_dir, + const char *reason); +void control_event_hs_descriptor_uploaded(const char *hs_dir, + const char *onion_address); +/* Hidden service v2 HS_DESC specific. */ +void control_event_hsv2_descriptor_failed(const rend_data_t *rend_data, + const char *id_digest, + const char *reason); +void control_event_hsv2_descriptor_received(const char *onion_address, + const rend_data_t *rend_data, + const char *id_digest); +/* Hidden service v3 HS_DESC specific. */ +void control_event_hsv3_descriptor_failed(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest, + const char *reason); +void control_event_hsv3_descriptor_received(const char *onion_address, + const char *desc_id, + const char *hsdir_id_digest); +void control_event_hs_descriptor_upload_failed(const char *hs_dir, + const char *onion_address, + const char *reason); +void control_event_hs_descriptor_content(const char *onion_address, + const char *desc_id, + const char *hsdir_fp, + const char *content); + +void control_events_free_all(void); + +#ifdef CONTROL_MODULE_PRIVATE +char *get_bw_samples(void); +#endif /* defined(CONTROL_MODULE_PRIVATE) */ + +#ifdef CONTROL_EVENTS_PRIVATE +/** Bitfield: The bit 1<<e is set if <b>any</b> open control + * connection is interested in events of type <b>e</b>. We use this + * so that we can decide to skip generating event messages that nobody + * has interest in without having to walk over the global connection + * list to find out. + **/ +typedef uint64_t event_mask_t; + +/* Recognized asynchronous event types. It's okay to expand this list + * because it is used both as a list of v0 event types, and as indices + * into the bitfield to determine which controllers want which events. + */ +/* This bitfield has no event zero 0x0000 */ +#define EVENT_MIN_ 0x0001 +#define EVENT_CIRCUIT_STATUS 0x0001 +#define EVENT_STREAM_STATUS 0x0002 +#define EVENT_OR_CONN_STATUS 0x0003 +#define EVENT_BANDWIDTH_USED 0x0004 +#define EVENT_CIRCUIT_STATUS_MINOR 0x0005 +#define EVENT_NEW_DESC 0x0006 +#define EVENT_DEBUG_MSG 0x0007 +#define EVENT_INFO_MSG 0x0008 +#define EVENT_NOTICE_MSG 0x0009 +#define EVENT_WARN_MSG 0x000A +#define EVENT_ERR_MSG 0x000B +#define EVENT_ADDRMAP 0x000C +/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We + can reclaim 0x000D. */ +#define EVENT_DESCCHANGED 0x000E +/* Exposed above */ +// #define EVENT_NS 0x000F +#define EVENT_STATUS_CLIENT 0x0010 +#define EVENT_STATUS_SERVER 0x0011 +#define EVENT_STATUS_GENERAL 0x0012 +#define EVENT_GUARD 0x0013 +#define EVENT_STREAM_BANDWIDTH_USED 0x0014 +#define EVENT_CLIENTS_SEEN 0x0015 +#define EVENT_NEWCONSENSUS 0x0016 +#define EVENT_BUILDTIMEOUT_SET 0x0017 +#define EVENT_GOT_SIGNAL 0x0018 +#define EVENT_CONF_CHANGED 0x0019 +#define EVENT_CONN_BW 0x001A +#define EVENT_CELL_STATS 0x001B +/* UNUSED : 0x001C */ +#define EVENT_CIRC_BANDWIDTH_USED 0x001D +#define EVENT_TRANSPORT_LAUNCHED 0x0020 +#define EVENT_HS_DESC 0x0021 +#define EVENT_HS_DESC_CONTENT 0x0022 +#define EVENT_NETWORK_LIVENESS 0x0023 +#define EVENT_PT_LOG 0x0024 +#define EVENT_PT_STATUS 0x0025 +#define EVENT_MAX_ 0x0025 + +/* sizeof(control_connection_t.event_mask) in bits, currently a uint64_t */ +#define EVENT_CAPACITY_ 0x0040 + +/* If EVENT_MAX_ ever hits 0x0040, we need to make the mask into a + * different structure, as it can only handle a maximum left shift of 1<<63. */ + +#if EVENT_MAX_ >= EVENT_CAPACITY_ +#error control_connection_t.event_mask has an event greater than its capacity +#endif + +#define EVENT_MASK_(e) (((uint64_t)1)<<(e)) + +#define EVENT_MASK_NONE_ ((uint64_t)0x0) + +#define EVENT_MASK_ABOVE_MIN_ ((~((uint64_t)0x0)) << EVENT_MIN_) +#define EVENT_MASK_BELOW_MAX_ ((~((uint64_t)0x0)) \ + >> (EVENT_CAPACITY_ - EVENT_MAX_ \ + - EVENT_MIN_)) + +#define EVENT_MASK_ALL_ (EVENT_MASK_ABOVE_MIN_ \ + & EVENT_MASK_BELOW_MAX_) + +/** Helper structure: temporarily stores cell statistics for a circuit. */ +typedef struct cell_stats_t { + /** Number of cells added in app-ward direction by command. */ + uint64_t added_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells added in exit-ward direction by command. */ + uint64_t added_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in app-ward direction by command. */ + uint64_t removed_cells_appward[CELL_COMMAND_MAX_ + 1]; + /** Number of cells removed in exit-ward direction by command. */ + uint64_t removed_cells_exitward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in app-ward direction by command. */ + uint64_t total_time_appward[CELL_COMMAND_MAX_ + 1]; + /** Total waiting time of cells in exit-ward direction by command. */ + uint64_t total_time_exitward[CELL_COMMAND_MAX_ + 1]; +} cell_stats_t; + +void sum_up_cell_stats_by_command(circuit_t *circ, + cell_stats_t *cell_stats); +void append_cell_stats_by_command(smartlist_t *event_parts, + const char *key, + const uint64_t *include_if_non_zero, + const uint64_t *number_to_include); +void format_cell_stats(char **event_string, circuit_t *circ, + cell_stats_t *cell_stats); + +/** Helper structure: maps event values to their names. */ +struct control_event_t { + uint16_t event_code; + const char *event_name; +}; + +extern const struct control_event_t control_event_table[]; + +#ifdef TOR_UNIT_TESTS +MOCK_DECL(STATIC void, + send_control_event_string,(uint16_t event, const char *msg)); + +MOCK_DECL(STATIC void, + queue_control_event_string,(uint16_t event, char *msg)); + +void control_testing_set_global_event_mask(uint64_t mask); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(CONTROL_EVENTS_PRIVATE) */ + +#endif /* !defined(TOR_CONTROL_EVENTS_H) */ diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c new file mode 100644 index 0000000000..d76e6ad8dd --- /dev/null +++ b/src/feature/control/control_fmt.c @@ -0,0 +1,274 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_fmt.c + * \brief Formatting functions for controller data. + */ + +#include "core/or/or.h" + +#include "core/mainloop/connection.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "feature/control/control_fmt.h" +#include "feature/control/control_proto.h" +#include "feature/nodelist/nodelist.h" + +#include "core/or/cpath_build_state_st.h" +#include "core/or/entry_connection_st.h" +#include "core/or/or_connection_st.h" +#include "core/or/origin_circuit_st.h" +#include "core/or/socks_request_st.h" +#include "feature/control/control_connection_st.h" + +/** Given an AP connection <b>conn</b> and a <b>len</b>-character buffer + * <b>buf</b>, determine the address:port combination requested on + * <b>conn</b>, and write it to <b>buf</b>. Return 0 on success, -1 on + * failure. */ +int +write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len) +{ + char buf2[256]; + if (conn->chosen_exit_name) + if (tor_snprintf(buf2, sizeof(buf2), ".%s.exit", conn->chosen_exit_name)<0) + return -1; + if (!conn->socks_request) + return -1; + if (tor_snprintf(buf, len, "%s%s%s:%d", + conn->socks_request->address, + conn->chosen_exit_name ? buf2 : "", + !conn->chosen_exit_name && connection_edge_is_rendezvous_stream( + ENTRY_TO_EDGE_CONN(conn)) ? ".onion" : "", + conn->socks_request->port)<0) + return -1; + return 0; +} + +/** Figure out the best name for the target router of an OR connection + * <b>conn</b>, and write it into the <b>len</b>-character buffer + * <b>name</b>. */ +void +orconn_target_get_name(char *name, size_t len, or_connection_t *conn) +{ + const node_t *node = node_get_by_id(conn->identity_digest); + if (node) { + tor_assert(len > MAX_VERBOSE_NICKNAME_LEN); + node_get_verbose_nickname(node, name); + } else if (! tor_digest_is_zero(conn->identity_digest)) { + name[0] = '$'; + base16_encode(name+1, len-1, conn->identity_digest, + DIGEST_LEN); + } else { + tor_snprintf(name, len, "%s:%d", + conn->base_.address, conn->base_.port); + } +} + +/** Allocate and return a description of <b>circ</b>'s current status, + * including its path (if any). */ +char * +circuit_describe_status_for_controller(origin_circuit_t *circ) +{ + char *rv; + smartlist_t *descparts = smartlist_new(); + + { + char *vpath = circuit_list_path_for_controller(circ); + if (*vpath) { + smartlist_add(descparts, vpath); + } else { + tor_free(vpath); /* empty path; don't put an extra space in the result */ + } + } + + { + cpath_build_state_t *build_state = circ->build_state; + smartlist_t *flaglist = smartlist_new(); + char *flaglist_joined; + + if (build_state->onehop_tunnel) + smartlist_add(flaglist, (void *)"ONEHOP_TUNNEL"); + if (build_state->is_internal) + smartlist_add(flaglist, (void *)"IS_INTERNAL"); + if (build_state->need_capacity) + smartlist_add(flaglist, (void *)"NEED_CAPACITY"); + if (build_state->need_uptime) + smartlist_add(flaglist, (void *)"NEED_UPTIME"); + + /* Only emit a BUILD_FLAGS argument if it will have a non-empty value. */ + if (smartlist_len(flaglist)) { + flaglist_joined = smartlist_join_strings(flaglist, ",", 0, NULL); + + smartlist_add_asprintf(descparts, "BUILD_FLAGS=%s", flaglist_joined); + + tor_free(flaglist_joined); + } + + smartlist_free(flaglist); + } + + smartlist_add_asprintf(descparts, "PURPOSE=%s", + circuit_purpose_to_controller_string(circ->base_.purpose)); + + { + const char *hs_state = + circuit_purpose_to_controller_hs_state_string(circ->base_.purpose); + + if (hs_state != NULL) { + smartlist_add_asprintf(descparts, "HS_STATE=%s", hs_state); + } + } + + if (circ->rend_data != NULL || circ->hs_ident != NULL) { + char addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const char *onion_address; + if (circ->rend_data) { + onion_address = rend_data_get_address(circ->rend_data); + } else { + hs_build_address(&circ->hs_ident->identity_pk, HS_VERSION_THREE, addr); + onion_address = addr; + } + smartlist_add_asprintf(descparts, "REND_QUERY=%s", onion_address); + } + + { + char tbuf[ISO_TIME_USEC_LEN+1]; + format_iso_time_nospace_usec(tbuf, &circ->base_.timestamp_created); + + smartlist_add_asprintf(descparts, "TIME_CREATED=%s", tbuf); + } + + // Show username and/or password if available. + if (circ->socks_username_len > 0) { + char* socks_username_escaped = esc_for_log_len(circ->socks_username, + (size_t) circ->socks_username_len); + smartlist_add_asprintf(descparts, "SOCKS_USERNAME=%s", + socks_username_escaped); + tor_free(socks_username_escaped); + } + if (circ->socks_password_len > 0) { + char* socks_password_escaped = esc_for_log_len(circ->socks_password, + (size_t) circ->socks_password_len); + smartlist_add_asprintf(descparts, "SOCKS_PASSWORD=%s", + socks_password_escaped); + tor_free(socks_password_escaped); + } + + rv = smartlist_join_strings(descparts, " ", 0, NULL); + + SMARTLIST_FOREACH(descparts, char *, cp, tor_free(cp)); + smartlist_free(descparts); + + return rv; +} + +/** Allocate and return a description of <b>conn</b>'s current status. */ +char * +entry_connection_describe_status_for_controller(const entry_connection_t *conn) +{ + char *rv; + smartlist_t *descparts = smartlist_new(); + + if (conn->socks_request != NULL) { + // Show username and/or password if available; used by IsolateSOCKSAuth. + if (conn->socks_request->usernamelen > 0) { + char* username_escaped = esc_for_log_len(conn->socks_request->username, + (size_t) conn->socks_request->usernamelen); + smartlist_add_asprintf(descparts, "SOCKS_USERNAME=%s", + username_escaped); + tor_free(username_escaped); + } + if (conn->socks_request->passwordlen > 0) { + char* password_escaped = esc_for_log_len(conn->socks_request->password, + (size_t) conn->socks_request->passwordlen); + smartlist_add_asprintf(descparts, "SOCKS_PASSWORD=%s", + password_escaped); + tor_free(password_escaped); + } + + const char *client_protocol; + // Show the client protocol; used by IsolateClientProtocol. + switch (conn->socks_request->listener_type) + { + case CONN_TYPE_AP_LISTENER: + switch (conn->socks_request->socks_version) + { + case 4: client_protocol = "SOCKS4"; break; + case 5: client_protocol = "SOCKS5"; break; + default: client_protocol = "UNKNOWN"; + } + break; + case CONN_TYPE_AP_TRANS_LISTENER: client_protocol = "TRANS"; break; + case CONN_TYPE_AP_NATD_LISTENER: client_protocol = "NATD"; break; + case CONN_TYPE_AP_DNS_LISTENER: client_protocol = "DNS"; break; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: + client_protocol = "HTTPCONNECT"; break; + default: client_protocol = "UNKNOWN"; + } + smartlist_add_asprintf(descparts, "CLIENT_PROTOCOL=%s", + client_protocol); + } + + // Show newnym epoch; used for stream isolation when NEWNYM is used. + smartlist_add_asprintf(descparts, "NYM_EPOCH=%u", + conn->nym_epoch); + + // Show session group; used for stream isolation of multiple listener ports. + smartlist_add_asprintf(descparts, "SESSION_GROUP=%d", + conn->entry_cfg.session_group); + + // Show isolation flags. + smartlist_t *isoflaglist = smartlist_new(); + char *isoflaglist_joined; + if (conn->entry_cfg.isolation_flags & ISO_DESTPORT) { + smartlist_add(isoflaglist, (void *)"DESTPORT"); + } + if (conn->entry_cfg.isolation_flags & ISO_DESTADDR) { + smartlist_add(isoflaglist, (void *)"DESTADDR"); + } + if (conn->entry_cfg.isolation_flags & ISO_SOCKSAUTH) { + smartlist_add(isoflaglist, (void *)"SOCKS_USERNAME"); + smartlist_add(isoflaglist, (void *)"SOCKS_PASSWORD"); + } + if (conn->entry_cfg.isolation_flags & ISO_CLIENTPROTO) { + smartlist_add(isoflaglist, (void *)"CLIENT_PROTOCOL"); + } + if (conn->entry_cfg.isolation_flags & ISO_CLIENTADDR) { + smartlist_add(isoflaglist, (void *)"CLIENTADDR"); + } + if (conn->entry_cfg.isolation_flags & ISO_SESSIONGRP) { + smartlist_add(isoflaglist, (void *)"SESSION_GROUP"); + } + if (conn->entry_cfg.isolation_flags & ISO_NYM_EPOCH) { + smartlist_add(isoflaglist, (void *)"NYM_EPOCH"); + } + isoflaglist_joined = smartlist_join_strings(isoflaglist, ",", 0, NULL); + smartlist_add_asprintf(descparts, "ISO_FIELDS=%s", isoflaglist_joined); + tor_free(isoflaglist_joined); + smartlist_free(isoflaglist); + + rv = smartlist_join_strings(descparts, " ", 0, NULL); + + SMARTLIST_FOREACH(descparts, char *, cp, tor_free(cp)); + smartlist_free(descparts); + + return rv; +} + +/** Return a longname the node whose identity is <b>id_digest</b>. If + * node_get_by_id() returns NULL, base 16 encoding of <b>id_digest</b> is + * returned instead. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +MOCK_IMPL(const char *, +node_describe_longname_by_id,(const char *id_digest)) +{ + static char longname[MAX_VERBOSE_NICKNAME_LEN+1]; + node_get_verbose_nickname_by_id(id_digest, longname); + return longname; +} diff --git a/src/feature/control/control_fmt.h b/src/feature/control/control_fmt.h new file mode 100644 index 0000000000..f3357cfc4e --- /dev/null +++ b/src/feature/control/control_fmt.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_fmt.h + * \brief Header file for control_fmt.c. + **/ + +#ifndef TOR_CONTROL_FMT_H +#define TOR_CONTROL_FMT_H + +int write_stream_target_to_buf(entry_connection_t *conn, char *buf, + size_t len); +void orconn_target_get_name(char *buf, size_t len, + or_connection_t *conn); +char *circuit_describe_status_for_controller(origin_circuit_t *circ); +char *entry_connection_describe_status_for_controller(const + entry_connection_t *conn); + +MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); + +#endif /* !defined(TOR_CONTROL_FMT_H) */ diff --git a/src/feature/control/control_getinfo.c b/src/feature/control/control_getinfo.c new file mode 100644 index 0000000000..5dcc4b170d --- /dev/null +++ b/src/feature/control/control_getinfo.c @@ -0,0 +1,1757 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_getinfo.c + * \brief Implementation for miscellaneous controller getinfo commands. + */ + +#define CONTROL_EVENTS_PRIVATE +#define CONTROL_MODULE_PRIVATE +#define CONTROL_GETINFO_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "core/mainloop/mainloop.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/policies.h" +#include "core/or/versions.h" +#include "feature/client/addressmap.h" +#include "feature/client/bridges.h" +#include "feature/client/entrynodes.h" +#include "feature/control/control.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_events.h" +#include "feature/control/control_fmt.h" +#include "feature/control/control_getinfo.h" +#include "feature/control/control_proto.h" +#include "feature/control/fmt_serverstatus.h" +#include "feature/control/getinfo_geoip.h" +#include "feature/dircache/dirserv.h" +#include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dlstatus.h" +#include "feature/dircommon/directory.h" +#include "feature/hibernate/hibernate.h" +#include "feature/hs/hs_cache.h" +#include "feature/hs_common/shared_random_client.h" +#include "feature/nodelist/authcert.h" +#include "feature/nodelist/microdesc.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerinfo.h" +#include "feature/nodelist/routerlist.h" +#include "feature/relay/router.h" +#include "feature/relay/routermode.h" +#include "feature/relay/selftest.h" +#include "feature/rend/rendcache.h" +#include "feature/stats/geoip_stats.h" +#include "feature/stats/predict_ports.h" +#include "lib/version/torversion.h" +#include "lib/encoding/kvline.h" + +#include "core/or/entry_connection_st.h" +#include "core/or/or_connection_st.h" +#include "core/or/origin_circuit_st.h" +#include "core/or/socks_request_st.h" +#include "feature/control/control_connection_st.h" +#include "feature/control/control_cmd_args_st.h" +#include "feature/dircache/cached_dir_st.h" +#include "feature/nodelist/extrainfo_st.h" +#include "feature/nodelist/microdesc_st.h" +#include "feature/nodelist/networkstatus_st.h" +#include "feature/nodelist/node_st.h" +#include "feature/nodelist/routerinfo_st.h" +#include "feature/nodelist/routerlist_st.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifndef _WIN32 +#include <pwd.h> +#endif + +static char *list_getinfo_options(void); +static char *download_status_to_string(const download_status_t *dl); + +/** Implementation helper for GETINFO: knows the answers for various + * trivial-to-implement questions. */ +static int +getinfo_helper_misc(control_connection_t *conn, const char *question, + char **answer, const char **errmsg) +{ + (void) conn; + if (!strcmp(question, "version")) { + *answer = tor_strdup(get_version()); + } else if (!strcmp(question, "bw-event-cache")) { + *answer = get_bw_samples(); + } else if (!strcmp(question, "config-file")) { + const char *a = get_torrc_fname(0); + if (a) + *answer = tor_strdup(a); + } else if (!strcmp(question, "config-defaults-file")) { + const char *a = get_torrc_fname(1); + if (a) + *answer = tor_strdup(a); + } else if (!strcmp(question, "config-text")) { + *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); + } else if (!strcmp(question, "config-can-saveconf")) { + *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1"); + } else if (!strcmp(question, "info/names")) { + *answer = list_getinfo_options(); + } else if (!strcmp(question, "dormant")) { + int dormant = rep_hist_circbuilding_dormant(time(NULL)); + *answer = tor_strdup(dormant ? "1" : "0"); + } else if (!strcmp(question, "events/names")) { + int i; + smartlist_t *event_names = smartlist_new(); + + for (i = 0; control_event_table[i].event_name != NULL; ++i) { + smartlist_add(event_names, (char *)control_event_table[i].event_name); + } + + *answer = smartlist_join_strings(event_names, " ", 0, NULL); + + smartlist_free(event_names); + } else if (!strcmp(question, "signal/names")) { + smartlist_t *signal_names = smartlist_new(); + int j; + for (j = 0; signal_table[j].signal_name != NULL; ++j) { + smartlist_add(signal_names, (char*)signal_table[j].signal_name); + } + + *answer = smartlist_join_strings(signal_names, " ", 0, NULL); + + smartlist_free(signal_names); + } else if (!strcmp(question, "features/names")) { + *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS"); + } else if (!strcmp(question, "address")) { + uint32_t addr; + if (router_pick_published_address(get_options(), &addr, 0) < 0) { + *errmsg = "Address unknown"; + return -1; + } + *answer = tor_dup_ip(addr); + } else if (!strcmp(question, "traffic/read")) { + tor_asprintf(answer, "%"PRIu64, (get_bytes_read())); + } else if (!strcmp(question, "traffic/written")) { + tor_asprintf(answer, "%"PRIu64, (get_bytes_written())); + } else if (!strcmp(question, "uptime")) { + long uptime_secs = get_uptime(); + tor_asprintf(answer, "%ld", uptime_secs); + } else if (!strcmp(question, "process/pid")) { + int myPid = -1; + +#ifdef _WIN32 + myPid = _getpid(); +#else + myPid = getpid(); +#endif + + tor_asprintf(answer, "%d", myPid); + } else if (!strcmp(question, "process/uid")) { +#ifdef _WIN32 + *answer = tor_strdup("-1"); +#else + int myUid = geteuid(); + tor_asprintf(answer, "%d", myUid); +#endif /* defined(_WIN32) */ + } else if (!strcmp(question, "process/user")) { +#ifdef _WIN32 + *answer = tor_strdup(""); +#else + int myUid = geteuid(); + const struct passwd *myPwEntry = tor_getpwuid(myUid); + + if (myPwEntry) { + *answer = tor_strdup(myPwEntry->pw_name); + } else { + *answer = tor_strdup(""); + } +#endif /* defined(_WIN32) */ + } else if (!strcmp(question, "process/descriptor-limit")) { + int max_fds = get_max_sockets(); + tor_asprintf(answer, "%d", max_fds); + } else if (!strcmp(question, "limits/max-mem-in-queues")) { + tor_asprintf(answer, "%"PRIu64, + (get_options()->MaxMemInQueues)); + } else if (!strcmp(question, "fingerprint")) { + crypto_pk_t *server_key; + if (!server_mode(get_options())) { + *errmsg = "Not running in server mode"; + return -1; + } + server_key = get_server_identity_key(); + *answer = tor_malloc(HEX_DIGEST_LEN+1); + crypto_pk_get_fingerprint(server_key, *answer, 0); + } + return 0; +} + +/** Awful hack: return a newly allocated string based on a routerinfo and + * (possibly) an extrainfo, sticking the read-history and write-history from + * <b>ei</b> into the resulting string. The thing you get back won't + * necessarily have a valid signature. + * + * New code should never use this; it's for backward compatibility. + * + * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might + * not be NUL-terminated. */ +static char * +munge_extrainfo_into_routerinfo(const char *ri_body, + const signed_descriptor_t *ri, + const signed_descriptor_t *ei) +{ + char *out = NULL, *outp; + int i; + const char *router_sig; + const char *ei_body = signed_descriptor_get_body(ei); + size_t ri_len = ri->signed_descriptor_len; + size_t ei_len = ei->signed_descriptor_len; + if (!ei_body) + goto bail; + + outp = out = tor_malloc(ri_len+ei_len+1); + if (!(router_sig = tor_memstr(ri_body, ri_len, "\nrouter-signature"))) + goto bail; + ++router_sig; + memcpy(out, ri_body, router_sig-ri_body); + outp += router_sig-ri_body; + + for (i=0; i < 2; ++i) { + const char *kwd = i ? "\nwrite-history " : "\nread-history "; + const char *cp, *eol; + if (!(cp = tor_memstr(ei_body, ei_len, kwd))) + continue; + ++cp; + if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body)))) + continue; + memcpy(outp, cp, eol-cp+1); + outp += eol-cp+1; + } + memcpy(outp, router_sig, ri_len - (router_sig-ri_body)); + *outp++ = '\0'; + tor_assert(outp-out < (int)(ri_len+ei_len+1)); + + return out; + bail: + tor_free(out); + return tor_strndup(ri_body, ri->signed_descriptor_len); +} + +/** Implementation helper for GETINFO: answers requests for information about + * which ports are bound. */ +static int +getinfo_helper_listeners(control_connection_t *control_conn, + const char *question, + char **answer, const char **errmsg) +{ + int type; + smartlist_t *res; + + (void)control_conn; + (void)errmsg; + + if (!strcmp(question, "net/listeners/or")) + type = CONN_TYPE_OR_LISTENER; + else if (!strcmp(question, "net/listeners/extor")) + type = CONN_TYPE_EXT_OR_LISTENER; + else if (!strcmp(question, "net/listeners/dir")) + type = CONN_TYPE_DIR_LISTENER; + else if (!strcmp(question, "net/listeners/socks")) + type = CONN_TYPE_AP_LISTENER; + else if (!strcmp(question, "net/listeners/trans")) + type = CONN_TYPE_AP_TRANS_LISTENER; + else if (!strcmp(question, "net/listeners/natd")) + type = CONN_TYPE_AP_NATD_LISTENER; + else if (!strcmp(question, "net/listeners/httptunnel")) + type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + else if (!strcmp(question, "net/listeners/dns")) + type = CONN_TYPE_AP_DNS_LISTENER; + else if (!strcmp(question, "net/listeners/control")) + type = CONN_TYPE_CONTROL_LISTENER; + else + return 0; /* unknown key */ + + res = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + struct sockaddr_storage ss; + socklen_t ss_len = sizeof(ss); + + if (conn->type != type || conn->marked_for_close || !SOCKET_OK(conn->s)) + continue; + + if (getsockname(conn->s, (struct sockaddr *)&ss, &ss_len) < 0) { + smartlist_add_asprintf(res, "%s:%d", conn->address, (int)conn->port); + } else { + char *tmp = tor_sockaddr_to_str((struct sockaddr *)&ss); + smartlist_add(res, esc_for_log(tmp)); + tor_free(tmp); + } + + } SMARTLIST_FOREACH_END(conn); + + *answer = smartlist_join_strings(res, " ", 0, NULL); + + SMARTLIST_FOREACH(res, char *, cp, tor_free(cp)); + smartlist_free(res); + return 0; +} + +/** Implementation helper for GETINFO: answers requests for information about + * the current time in both local and UTC forms. */ +STATIC int +getinfo_helper_current_time(control_connection_t *control_conn, + const char *question, + char **answer, const char **errmsg) +{ + (void)control_conn; + (void)errmsg; + + struct timeval now; + tor_gettimeofday(&now); + char timebuf[ISO_TIME_LEN+1]; + + if (!strcmp(question, "current-time/local")) + format_local_iso_time_nospace(timebuf, (time_t)now.tv_sec); + else if (!strcmp(question, "current-time/utc")) + format_iso_time_nospace(timebuf, (time_t)now.tv_sec); + else + return 0; + + *answer = tor_strdup(timebuf); + return 0; +} + +/** GETINFO helper for dumping different consensus flavors + * returns: 0 on success -1 on error. */ +STATIC int +getinfo_helper_current_consensus(consensus_flavor_t flavor, + char** answer, + const char** errmsg) +{ + const char *flavor_name = networkstatus_get_flavor_name(flavor); + if (BUG(!strcmp(flavor_name, "??"))) { + *errmsg = "Internal error: unrecognized flavor name."; + return -1; + } + if (we_want_to_fetch_flavor(get_options(), flavor)) { + /** Check from the cache */ + const cached_dir_t *consensus = dirserv_get_consensus(flavor_name); + if (consensus) { + *answer = tor_strdup(consensus->dir); + } + } + if (!*answer) { /* try loading it from disk */ + + tor_mmap_t *mapped = networkstatus_map_cached_consensus(flavor_name); + if (mapped) { + *answer = tor_memdup_nulterm(mapped->data, mapped->size); + tor_munmap_file(mapped); + } + if (!*answer) { /* generate an error */ + *errmsg = "Could not open cached consensus. " + "Make sure FetchUselessDescriptors is set to 1."; + return -1; + } + } + return 0; +} + +/** Helper for getinfo_helper_dir. + * + * Add a signed_descriptor_t to <b>descs_out</b> for each router matching + * <b>key</b>. The key should be either + * - "/tor/server/authority" for our own routerinfo; + * - "/tor/server/all" for all the routerinfos we have, concatenated; + * - "/tor/server/fp/FP" where FP is a plus-separated sequence of + * hex identity digests; or + * - "/tor/server/d/D" where D is a plus-separated sequence + * of server descriptor digests, in hex. + * + * Return 0 if we found some matching descriptors, or -1 if we do not + * have any descriptors, no matching descriptors, or if we did not + * recognize the key (URL). + * If -1 is returned *<b>msg</b> will be set to an appropriate error + * message. + */ +static int +controller_get_routerdescs(smartlist_t *descs_out, const char *key, + const char **msg) +{ + *msg = NULL; + + if (!strcmp(key, "/tor/server/all")) { + routerlist_t *rl = router_get_routerlist(); + SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, + smartlist_add(descs_out, &(r->cache_info))); + } else if (!strcmp(key, "/tor/server/authority")) { + const routerinfo_t *ri = router_get_my_routerinfo(); + if (ri) + smartlist_add(descs_out, (void*) &(ri->cache_info)); + } else if (!strcmpstart(key, "/tor/server/d/")) { + smartlist_t *digests = smartlist_new(); + key += strlen("/tor/server/d/"); + dir_split_resource_into_fingerprints(key, digests, NULL, + DSR_HEX|DSR_SORT_UNIQ); + SMARTLIST_FOREACH(digests, const char *, d, + { + signed_descriptor_t *sd = router_get_by_descriptor_digest(d); + if (sd) + smartlist_add(descs_out,sd); + }); + SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); + smartlist_free(digests); + } else if (!strcmpstart(key, "/tor/server/fp/")) { + smartlist_t *digests = smartlist_new(); + time_t cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH; + key += strlen("/tor/server/fp/"); + dir_split_resource_into_fingerprints(key, digests, NULL, + DSR_HEX|DSR_SORT_UNIQ); + SMARTLIST_FOREACH_BEGIN(digests, const char *, d) { + if (router_digest_is_me(d)) { + /* calling router_get_my_routerinfo() to make sure it exists */ + const routerinfo_t *ri = router_get_my_routerinfo(); + if (ri) + smartlist_add(descs_out, (void*) &(ri->cache_info)); + } else { + const routerinfo_t *ri = router_get_by_id_digest(d); + /* Don't actually serve a descriptor that everyone will think is + * expired. This is an (ugly) workaround to keep buggy 0.1.1.10 + * Tors from downloading descriptors that they will throw away. + */ + if (ri && ri->cache_info.published_on > cutoff) + smartlist_add(descs_out, (void*) &(ri->cache_info)); + } + } SMARTLIST_FOREACH_END(d); + SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); + smartlist_free(digests); + } else { + *msg = "Key not recognized"; + return -1; + } + + if (!smartlist_len(descs_out)) { + *msg = "Servers unavailable"; + return -1; + } + return 0; +} + +/** Implementation helper for GETINFO: knows the answers for questions about + * directory information. */ +STATIC int +getinfo_helper_dir(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + (void) control_conn; + if (!strcmpstart(question, "desc/id/")) { + const routerinfo_t *ri = NULL; + const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); + if (node) + ri = node->ri; + if (ri) { + const char *body = signed_descriptor_get_body(&ri->cache_info); + if (body) + *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/id/* " + "instead of desc/id/*."; + return 0; + } + } else if (!strcmpstart(question, "desc/name/")) { + const routerinfo_t *ri = NULL; + /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the + * warning goes to the user, not to the controller. */ + const node_t *node = + node_get_by_nickname(question+strlen("desc/name/"), 0); + if (node) + ri = node->ri; + if (ri) { + const char *body = signed_descriptor_get_body(&ri->cache_info); + if (body) + *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/name/* " + "instead of desc/name/*."; + return 0; + } + } else if (!strcmp(question, "desc/download-enabled")) { + int r = we_fetch_router_descriptors(get_options()); + tor_asprintf(answer, "%d", !!r); + } else if (!strcmp(question, "desc/all-recent")) { + routerlist_t *routerlist = router_get_routerlist(); + smartlist_t *sl = smartlist_new(); + if (routerlist && routerlist->routers) { + SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, + { + const char *body = signed_descriptor_get_body(&ri->cache_info); + if (body) + smartlist_add(sl, + tor_strndup(body, ri->cache_info.signed_descriptor_len)); + }); + } + *answer = smartlist_join_strings(sl, "", 0, NULL); + SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); + smartlist_free(sl); + } else if (!strcmp(question, "desc/all-recent-extrainfo-hack")) { + /* XXXX Remove this once Torstat asks for extrainfos. */ + routerlist_t *routerlist = router_get_routerlist(); + smartlist_t *sl = smartlist_new(); + if (routerlist && routerlist->routers) { + SMARTLIST_FOREACH_BEGIN(routerlist->routers, const routerinfo_t *, ri) { + const char *body = signed_descriptor_get_body(&ri->cache_info); + signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest( + ri->cache_info.extra_info_digest); + if (ei && body) { + smartlist_add(sl, munge_extrainfo_into_routerinfo(body, + &ri->cache_info, ei)); + } else if (body) { + smartlist_add(sl, + tor_strndup(body, ri->cache_info.signed_descriptor_len)); + } + } SMARTLIST_FOREACH_END(ri); + } + *answer = smartlist_join_strings(sl, "", 0, NULL); + SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); + smartlist_free(sl); + } else if (!strcmpstart(question, "hs/client/desc/id/")) { + hostname_type_t addr_type; + + question += strlen("hs/client/desc/id/"); + if (rend_valid_v2_service_id(question)) { + addr_type = ONION_V2_HOSTNAME; + } else if (hs_address_is_valid(question)) { + addr_type = ONION_V3_HOSTNAME; + } else { + *errmsg = "Invalid address"; + return -1; + } + + if (addr_type == ONION_V2_HOSTNAME) { + rend_cache_entry_t *e = NULL; + if (!rend_cache_lookup_entry(question, -1, &e)) { + /* Descriptor found in cache */ + *answer = tor_strdup(e->desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } + } else { + ed25519_public_key_t service_pk; + const char *desc; + + /* The check before this if/else makes sure of this. */ + tor_assert(addr_type == ONION_V3_HOSTNAME); + + if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { + *errmsg = "Invalid v3 address"; + return -1; + } + + desc = hs_cache_lookup_encoded_as_client(&service_pk); + if (desc) { + *answer = tor_strdup(desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } + } + } else if (!strcmpstart(question, "hs/service/desc/id/")) { + hostname_type_t addr_type; + + question += strlen("hs/service/desc/id/"); + if (rend_valid_v2_service_id(question)) { + addr_type = ONION_V2_HOSTNAME; + } else if (hs_address_is_valid(question)) { + addr_type = ONION_V3_HOSTNAME; + } else { + *errmsg = "Invalid address"; + return -1; + } + rend_cache_entry_t *e = NULL; + + if (addr_type == ONION_V2_HOSTNAME) { + if (!rend_cache_lookup_v2_desc_as_service(question, &e)) { + /* Descriptor found in cache */ + *answer = tor_strdup(e->desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } + } else { + ed25519_public_key_t service_pk; + char *desc; + + /* The check before this if/else makes sure of this. */ + tor_assert(addr_type == ONION_V3_HOSTNAME); + + if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) { + *errmsg = "Invalid v3 address"; + return -1; + } + + desc = hs_service_lookup_current_desc(&service_pk); + if (desc) { + /* Newly allocated string, we have ownership. */ + *answer = desc; + } else { + *errmsg = "Not found in cache"; + return -1; + } + } + } else if (!strcmp(question, "md/all")) { + const smartlist_t *nodes = nodelist_get_list(); + tor_assert(nodes); + + if (smartlist_len(nodes) == 0) { + *answer = tor_strdup(""); + return 0; + } + + smartlist_t *microdescs = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(nodes, node_t *, n) { + if (n->md && n->md->body) { + char *copy = tor_strndup(n->md->body, n->md->bodylen); + smartlist_add(microdescs, copy); + } + } SMARTLIST_FOREACH_END(n); + + *answer = smartlist_join_strings(microdescs, "", 0, NULL); + SMARTLIST_FOREACH(microdescs, char *, md, tor_free(md)); + smartlist_free(microdescs); + } else if (!strcmpstart(question, "md/id/")) { + const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); + const microdesc_t *md = NULL; + if (node) md = node->md; + if (md && md->body) { + *answer = tor_strndup(md->body, md->bodylen); + } + } else if (!strcmpstart(question, "md/name/")) { + /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the + * warning goes to the user, not to the controller. */ + const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); + /* XXXX duplicated code */ + const microdesc_t *md = NULL; + if (node) md = node->md; + if (md && md->body) { + *answer = tor_strndup(md->body, md->bodylen); + } + } else if (!strcmp(question, "md/download-enabled")) { + int r = we_fetch_microdescriptors(get_options()); + tor_asprintf(answer, "%d", !!r); + } else if (!strcmpstart(question, "desc-annotations/id/")) { + const routerinfo_t *ri = NULL; + const node_t *node = + node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); + if (node) + ri = node->ri; + if (ri) { + const char *annotations = + signed_descriptor_get_annotations(&ri->cache_info); + if (annotations) + *answer = tor_strndup(annotations, + ri->cache_info.annotations_len); + } + } else if (!strcmpstart(question, "dir/server/")) { + size_t answer_len = 0; + char *url = NULL; + smartlist_t *descs = smartlist_new(); + const char *msg; + int res; + char *cp; + tor_asprintf(&url, "/tor/%s", question+4); + res = controller_get_routerdescs(descs, url, &msg); + if (res) { + log_warn(LD_CONTROL, "getinfo '%s': %s", question, msg); + smartlist_free(descs); + tor_free(url); + *errmsg = msg; + return -1; + } + SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd, + answer_len += sd->signed_descriptor_len); + cp = *answer = tor_malloc(answer_len+1); + SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd, + { + memcpy(cp, signed_descriptor_get_body(sd), + sd->signed_descriptor_len); + cp += sd->signed_descriptor_len; + }); + *cp = '\0'; + tor_free(url); + smartlist_free(descs); + } else if (!strcmpstart(question, "dir/status/")) { + *answer = tor_strdup(""); + } else if (!strcmp(question, "dir/status-vote/current/consensus")) { + int consensus_result = getinfo_helper_current_consensus(FLAV_NS, + answer, errmsg); + if (consensus_result < 0) { + return -1; + } + } else if (!strcmp(question, + "dir/status-vote/current/consensus-microdesc")) { + int consensus_result = getinfo_helper_current_consensus(FLAV_MICRODESC, + answer, errmsg); + if (consensus_result < 0) { + return -1; + } + } else if (!strcmp(question, "network-status")) { /* v1 */ + static int network_status_warned = 0; + if (!network_status_warned) { + log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will " + "go away in a future version of Tor."); + network_status_warned = 1; + } + routerlist_t *routerlist = router_get_routerlist(); + if (!routerlist || !routerlist->routers || + list_server_status_v1(routerlist->routers, answer, 1) < 0) { + return -1; + } + } else if (!strcmpstart(question, "extra-info/digest/")) { + question += strlen("extra-info/digest/"); + if (strlen(question) == HEX_DIGEST_LEN) { + char d[DIGEST_LEN]; + signed_descriptor_t *sd = NULL; + if (base16_decode(d, sizeof(d), question, strlen(question)) + == sizeof(d)) { + /* XXXX this test should move into extrainfo_get_by_descriptor_digest, + * but I don't want to risk affecting other parts of the code, + * especially since the rules for using our own extrainfo (including + * when it might be freed) are different from those for using one + * we have downloaded. */ + if (router_extrainfo_digest_is_me(d)) + sd = &(router_get_my_extrainfo()->cache_info); + else + sd = extrainfo_get_by_descriptor_digest(d); + } + if (sd) { + const char *body = signed_descriptor_get_body(sd); + if (body) + *answer = tor_strndup(body, sd->signed_descriptor_len); + } + } + } + + return 0; +} + +/** Given a smartlist of 20-byte digests, return a newly allocated string + * containing each of those digests in order, formatted in HEX, and terminated + * with a newline. */ +static char * +digest_list_to_string(const smartlist_t *sl) +{ + int len; + char *result, *s; + + /* Allow for newlines, and a \0 at the end */ + len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1; + result = tor_malloc_zero(len); + + s = result; + SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) { + base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN); + s[HEX_DIGEST_LEN] = '\n'; + s += HEX_DIGEST_LEN + 1; + } SMARTLIST_FOREACH_END(digest); + *s = '\0'; + + return result; +} + +/** Turn a download_status_t into a human-readable description in a newly + * allocated string. The format is specified in control-spec.txt, under + * the documentation for "GETINFO download/..." . */ +static char * +download_status_to_string(const download_status_t *dl) +{ + char *rv = NULL; + char tbuf[ISO_TIME_LEN+1]; + const char *schedule_str, *want_authority_str; + const char *increment_on_str, *backoff_str; + + if (dl) { + /* Get some substrings of the eventual output ready */ + format_iso_time(tbuf, download_status_get_next_attempt_at(dl)); + + switch (dl->schedule) { + case DL_SCHED_GENERIC: + schedule_str = "DL_SCHED_GENERIC"; + break; + case DL_SCHED_CONSENSUS: + schedule_str = "DL_SCHED_CONSENSUS"; + break; + case DL_SCHED_BRIDGE: + schedule_str = "DL_SCHED_BRIDGE"; + break; + default: + schedule_str = "unknown"; + break; + } + + switch (dl->want_authority) { + case DL_WANT_ANY_DIRSERVER: + want_authority_str = "DL_WANT_ANY_DIRSERVER"; + break; + case DL_WANT_AUTHORITY: + want_authority_str = "DL_WANT_AUTHORITY"; + break; + default: + want_authority_str = "unknown"; + break; + } + + switch (dl->increment_on) { + case DL_SCHED_INCREMENT_FAILURE: + increment_on_str = "DL_SCHED_INCREMENT_FAILURE"; + break; + case DL_SCHED_INCREMENT_ATTEMPT: + increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT"; + break; + default: + increment_on_str = "unknown"; + break; + } + + backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL"; + + /* Now assemble them */ + tor_asprintf(&rv, + "next-attempt-at %s\n" + "n-download-failures %u\n" + "n-download-attempts %u\n" + "schedule %s\n" + "want-authority %s\n" + "increment-on %s\n" + "backoff %s\n" + "last-backoff-position %u\n" + "last-delay-used %d\n", + tbuf, + dl->n_download_failures, + dl->n_download_attempts, + schedule_str, + want_authority_str, + increment_on_str, + backoff_str, + dl->last_backoff_position, + dl->last_delay_used); + } + + return rv; +} + +/** Handle the consensus download cases for getinfo_helper_downloads() */ +STATIC void +getinfo_helper_downloads_networkstatus(const char *flavor, + download_status_t **dl_to_emit, + const char **errmsg) +{ + /* + * We get the one for the current bootstrapped status by default, or + * take an extra /bootstrap or /running suffix + */ + if (strcmp(flavor, "ns") == 0) { + *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS); + } else if (strcmp(flavor, "ns/bootstrap") == 0) { + *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS); + } else if (strcmp(flavor, "ns/running") == 0 ) { + *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS); + } else if (strcmp(flavor, "microdesc") == 0) { + *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC); + } else if (strcmp(flavor, "microdesc/bootstrap") == 0) { + *dl_to_emit = + networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC); + } else if (strcmp(flavor, "microdesc/running") == 0) { + *dl_to_emit = + networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC); + } else { + *errmsg = "Unknown flavor"; + } +} + +/** Handle the cert download cases for getinfo_helper_downloads() */ +STATIC void +getinfo_helper_downloads_cert(const char *fp_sk_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg) +{ + const char *sk_req; + char id_digest[DIGEST_LEN]; + char sk_digest[DIGEST_LEN]; + + /* + * We have to handle four cases; fp_sk_req is the request with + * a prefix of "downloads/cert/" snipped off. + * + * Case 1: fp_sk_req = "fps" + * - We should emit a digest_list with a list of all the identity + * fingerprints that can be queried for certificate download status; + * get it by calling list_authority_ids_with_downloads(). + * + * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp + * - We want the default certificate for this identity fingerprint's + * download status; this is the download we get from URLs starting + * in /fp/ on the directory server. We can get it with + * id_only_download_status_for_authority_id(). + * + * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp + * - We want a list of all signing key digests for this identity + * fingerprint which can be queried for certificate download status. + * Get it with list_sk_digests_for_authority_id(). + * + * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and + * signing key digest sk + * - We want the download status for the certificate for this specific + * signing key and fingerprint. These correspond to the ones we get + * from URLs starting in /fp-sk/ on the directory server. Get it with + * list_sk_digests_for_authority_id(). + */ + + if (strcmp(fp_sk_req, "fps") == 0) { + *digest_list = list_authority_ids_with_downloads(); + if (!(*digest_list)) { + *errmsg = "Failed to get list of authority identity digests (!)"; + } + } else if (!strcmpstart(fp_sk_req, "fp/")) { + fp_sk_req += strlen("fp/"); + /* Okay, look for another / to tell the fp from fp-sk cases */ + sk_req = strchr(fp_sk_req, '/'); + if (sk_req) { + /* okay, split it here and try to parse <fp> */ + if (base16_decode(id_digest, DIGEST_LEN, + fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) { + /* Skip past the '/' */ + ++sk_req; + if (strcmp(sk_req, "sks") == 0) { + /* We're asking for the list of signing key fingerprints */ + *digest_list = list_sk_digests_for_authority_id(id_digest); + if (!(*digest_list)) { + *errmsg = "Failed to get list of signing key digests for this " + "authority identity digest"; + } + } else { + /* We've got a signing key digest */ + if (base16_decode(sk_digest, DIGEST_LEN, + sk_req, strlen(sk_req)) == DIGEST_LEN) { + *dl_to_emit = + download_status_for_authority_id_and_sk(id_digest, sk_digest); + if (!(*dl_to_emit)) { + *errmsg = "Failed to get download status for this identity/" + "signing key digest pair"; + } + } else { + *errmsg = "That didn't look like a signing key digest"; + } + } + } else { + *errmsg = "That didn't look like an identity digest"; + } + } else { + /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */ + if (strlen(fp_sk_req) == HEX_DIGEST_LEN) { + if (base16_decode(id_digest, DIGEST_LEN, + fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) { + *dl_to_emit = id_only_download_status_for_authority_id(id_digest); + if (!(*dl_to_emit)) { + *errmsg = "Failed to get download status for this authority " + "identity digest"; + } + } else { + *errmsg = "That didn't look like a digest"; + } + } else { + *errmsg = "That didn't look like a digest"; + } + } + } else { + *errmsg = "Unknown certificate download status query"; + } +} + +/** Handle the routerdesc download cases for getinfo_helper_downloads() */ +STATIC void +getinfo_helper_downloads_desc(const char *desc_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg) +{ + char desc_digest[DIGEST_LEN]; + /* + * Two cases to handle here: + * + * Case 1: desc_req = "descs" + * - Emit a list of all router descriptor digests, which we get by + * calling router_get_descriptor_digests(); this can return NULL + * if we have no current ns-flavor consensus. + * + * Case 2: desc_req = <fp> + * - Check on the specified fingerprint and emit its download_status_t + * using router_get_dl_status_by_descriptor_digest(). + */ + + if (strcmp(desc_req, "descs") == 0) { + *digest_list = router_get_descriptor_digests(); + if (!(*digest_list)) { + *errmsg = "We don't seem to have a networkstatus-flavored consensus"; + } + /* + * Microdescs don't use the download_status_t mechanism, so we don't + * answer queries about their downloads here; see microdesc.c. + */ + } else if (strlen(desc_req) == HEX_DIGEST_LEN) { + if (base16_decode(desc_digest, DIGEST_LEN, + desc_req, strlen(desc_req)) == DIGEST_LEN) { + /* Okay we got a digest-shaped thing; try asking for it */ + *dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest); + if (!(*dl_to_emit)) { + *errmsg = "No such descriptor digest found"; + } + } else { + *errmsg = "That didn't look like a digest"; + } + } else { + *errmsg = "Unknown router descriptor download status query"; + } +} + +/** Handle the bridge download cases for getinfo_helper_downloads() */ +STATIC void +getinfo_helper_downloads_bridge(const char *bridge_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg) +{ + char bridge_digest[DIGEST_LEN]; + /* + * Two cases to handle here: + * + * Case 1: bridge_req = "bridges" + * - Emit a list of all bridge identity digests, which we get by + * calling list_bridge_identities(); this can return NULL if we are + * not using bridges. + * + * Case 2: bridge_req = <fp> + * - Check on the specified fingerprint and emit its download_status_t + * using get_bridge_dl_status_by_id(). + */ + + if (strcmp(bridge_req, "bridges") == 0) { + *digest_list = list_bridge_identities(); + if (!(*digest_list)) { + *errmsg = "We don't seem to be using bridges"; + } + } else if (strlen(bridge_req) == HEX_DIGEST_LEN) { + if (base16_decode(bridge_digest, DIGEST_LEN, + bridge_req, strlen(bridge_req)) == DIGEST_LEN) { + /* Okay we got a digest-shaped thing; try asking for it */ + *dl_to_emit = get_bridge_dl_status_by_id(bridge_digest); + if (!(*dl_to_emit)) { + *errmsg = "No such bridge identity digest found"; + } + } else { + *errmsg = "That didn't look like a digest"; + } + } else { + *errmsg = "Unknown bridge descriptor download status query"; + } +} + +/** Implementation helper for GETINFO: knows the answers for questions about + * download status information. */ +STATIC int +getinfo_helper_downloads(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + download_status_t *dl_to_emit = NULL; + smartlist_t *digest_list = NULL; + + /* Assert args are sane */ + tor_assert(control_conn != NULL); + tor_assert(question != NULL); + tor_assert(answer != NULL); + tor_assert(errmsg != NULL); + + /* We check for this later to see if we should supply a default */ + *errmsg = NULL; + + /* Are we after networkstatus downloads? */ + if (!strcmpstart(question, "downloads/networkstatus/")) { + getinfo_helper_downloads_networkstatus( + question + strlen("downloads/networkstatus/"), + &dl_to_emit, errmsg); + /* Certificates? */ + } else if (!strcmpstart(question, "downloads/cert/")) { + getinfo_helper_downloads_cert( + question + strlen("downloads/cert/"), + &dl_to_emit, &digest_list, errmsg); + /* Router descriptors? */ + } else if (!strcmpstart(question, "downloads/desc/")) { + getinfo_helper_downloads_desc( + question + strlen("downloads/desc/"), + &dl_to_emit, &digest_list, errmsg); + /* Bridge descriptors? */ + } else if (!strcmpstart(question, "downloads/bridge/")) { + getinfo_helper_downloads_bridge( + question + strlen("downloads/bridge/"), + &dl_to_emit, &digest_list, errmsg); + } else { + *errmsg = "Unknown download status query"; + } + + if (dl_to_emit) { + *answer = download_status_to_string(dl_to_emit); + + return 0; + } else if (digest_list) { + *answer = digest_list_to_string(digest_list); + SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s)); + smartlist_free(digest_list); + + return 0; + } else { + if (!(*errmsg)) { + *errmsg = "Unknown error"; + } + + return -1; + } +} + +/** Implementation helper for GETINFO: knows how to generate summaries of the + * current states of things we send events about. */ +static int +getinfo_helper_events(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + const or_options_t *options = get_options(); + (void) control_conn; + if (!strcmp(question, "circuit-status")) { + smartlist_t *status = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) { + origin_circuit_t *circ; + char *circdesc; + const char *state; + if (! CIRCUIT_IS_ORIGIN(circ_) || circ_->marked_for_close) + continue; + circ = TO_ORIGIN_CIRCUIT(circ_); + + if (circ->base_.state == CIRCUIT_STATE_OPEN) + state = "BUILT"; + else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) + state = "GUARD_WAIT"; + else if (circ->cpath) + state = "EXTENDED"; + else + state = "LAUNCHED"; + + circdesc = circuit_describe_status_for_controller(circ); + + smartlist_add_asprintf(status, "%lu %s%s%s", + (unsigned long)circ->global_identifier, + state, *circdesc ? " " : "", circdesc); + tor_free(circdesc); + } + SMARTLIST_FOREACH_END(circ_); + *answer = smartlist_join_strings(status, "\r\n", 0, NULL); + SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); + smartlist_free(status); + } else if (!strcmp(question, "stream-status")) { + smartlist_t *conns = get_connection_array(); + smartlist_t *status = smartlist_new(); + char buf[256]; + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const char *state; + entry_connection_t *conn; + circuit_t *circ; + origin_circuit_t *origin_circ = NULL; + if (base_conn->type != CONN_TYPE_AP || + base_conn->marked_for_close || + base_conn->state == AP_CONN_STATE_SOCKS_WAIT || + base_conn->state == AP_CONN_STATE_NATD_WAIT) + continue; + conn = TO_ENTRY_CONN(base_conn); + switch (base_conn->state) + { + case AP_CONN_STATE_CONTROLLER_WAIT: + case AP_CONN_STATE_CIRCUIT_WAIT: + if (conn->socks_request && + SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)) + state = "NEWRESOLVE"; + else + state = "NEW"; + break; + case AP_CONN_STATE_RENDDESC_WAIT: + case AP_CONN_STATE_CONNECT_WAIT: + state = "SENTCONNECT"; break; + case AP_CONN_STATE_RESOLVE_WAIT: + state = "SENTRESOLVE"; break; + case AP_CONN_STATE_OPEN: + state = "SUCCEEDED"; break; + default: + log_warn(LD_BUG, "Asked for stream in unknown state %d", + base_conn->state); + continue; + } + circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); + if (circ && CIRCUIT_IS_ORIGIN(circ)) + origin_circ = TO_ORIGIN_CIRCUIT(circ); + write_stream_target_to_buf(conn, buf, sizeof(buf)); + smartlist_add_asprintf(status, "%lu %s %lu %s", + (unsigned long) base_conn->global_identifier,state, + origin_circ? + (unsigned long)origin_circ->global_identifier : 0ul, + buf); + } SMARTLIST_FOREACH_END(base_conn); + *answer = smartlist_join_strings(status, "\r\n", 0, NULL); + SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); + smartlist_free(status); + } else if (!strcmp(question, "orconn-status")) { + smartlist_t *conns = get_connection_array(); + smartlist_t *status = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const char *state; + char name[128]; + or_connection_t *conn; + if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close) + continue; + conn = TO_OR_CONN(base_conn); + if (conn->base_.state == OR_CONN_STATE_OPEN) + state = "CONNECTED"; + else if (conn->nickname) + state = "LAUNCHED"; + else + state = "NEW"; + orconn_target_get_name(name, sizeof(name), conn); + smartlist_add_asprintf(status, "%s %s", name, state); + } SMARTLIST_FOREACH_END(base_conn); + *answer = smartlist_join_strings(status, "\r\n", 0, NULL); + SMARTLIST_FOREACH(status, char *, cp, tor_free(cp)); + smartlist_free(status); + } else if (!strcmpstart(question, "address-mappings/")) { + time_t min_e, max_e; + smartlist_t *mappings; + question += strlen("address-mappings/"); + if (!strcmp(question, "all")) { + min_e = 0; max_e = TIME_MAX; + } else if (!strcmp(question, "cache")) { + min_e = 2; max_e = TIME_MAX; + } else if (!strcmp(question, "config")) { + min_e = 0; max_e = 0; + } else if (!strcmp(question, "control")) { + min_e = 1; max_e = 1; + } else { + return 0; + } + mappings = smartlist_new(); + addressmap_get_mappings(mappings, min_e, max_e, 1); + *answer = smartlist_join_strings(mappings, "\r\n", 0, NULL); + SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp)); + smartlist_free(mappings); + } else if (!strcmpstart(question, "status/")) { + /* Note that status/ is not a catch-all for events; there's only supposed + * to be a status GETINFO if there's a corresponding STATUS event. */ + if (!strcmp(question, "status/circuit-established")) { + *answer = tor_strdup(have_completed_a_circuit() ? "1" : "0"); + } else if (!strcmp(question, "status/enough-dir-info")) { + *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0"); + } else if (!strcmp(question, "status/good-server-descriptor") || + !strcmp(question, "status/accepted-server-descriptor")) { + /* They're equivalent for now, until we can figure out how to make + * good-server-descriptor be what we want. See comment in + * control-spec.txt. */ + *answer = tor_strdup(directories_have_accepted_server_descriptor() + ? "1" : "0"); + } else if (!strcmp(question, "status/reachability-succeeded/or")) { + *answer = tor_strdup(check_whether_orport_reachable(options) ? + "1" : "0"); + } else if (!strcmp(question, "status/reachability-succeeded/dir")) { + *answer = tor_strdup(check_whether_dirport_reachable(options) ? + "1" : "0"); + } else if (!strcmp(question, "status/reachability-succeeded")) { + tor_asprintf(answer, "OR=%d DIR=%d", + check_whether_orport_reachable(options) ? 1 : 0, + check_whether_dirport_reachable(options) ? 1 : 0); + } else if (!strcmp(question, "status/bootstrap-phase")) { + *answer = control_event_boot_last_msg(); + } else if (!strcmpstart(question, "status/version/")) { + int is_server = server_mode(options); + networkstatus_t *c = networkstatus_get_latest_consensus(); + version_status_t status; + const char *recommended; + if (c) { + recommended = is_server ? c->server_versions : c->client_versions; + status = tor_version_is_obsolete(VERSION, recommended); + } else { + recommended = "?"; + status = VS_UNKNOWN; + } + + if (!strcmp(question, "status/version/recommended")) { + *answer = tor_strdup(recommended); + return 0; + } + if (!strcmp(question, "status/version/current")) { + switch (status) + { + case VS_RECOMMENDED: *answer = tor_strdup("recommended"); break; + case VS_OLD: *answer = tor_strdup("obsolete"); break; + case VS_NEW: *answer = tor_strdup("new"); break; + case VS_NEW_IN_SERIES: *answer = tor_strdup("new in series"); break; + case VS_UNRECOMMENDED: *answer = tor_strdup("unrecommended"); break; + case VS_EMPTY: *answer = tor_strdup("none recommended"); break; + case VS_UNKNOWN: *answer = tor_strdup("unknown"); break; + default: tor_fragile_assert(); + } + } + } else if (!strcmp(question, "status/clients-seen")) { + char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL)); + if (!bridge_stats) { + *errmsg = "No bridge-client stats available"; + return -1; + } + *answer = bridge_stats; + } else if (!strcmp(question, "status/fresh-relay-descs")) { + if (!server_mode(options)) { + *errmsg = "Only relays have descriptors"; + return -1; + } + routerinfo_t *r; + extrainfo_t *e; + if (router_build_fresh_descriptor(&r, &e) < 0) { + *errmsg = "Error generating descriptor"; + return -1; + } + size_t size = r->cache_info.signed_descriptor_len + 1; + if (e) { + size += e->cache_info.signed_descriptor_len + 1; + } + tor_assert(r->cache_info.signed_descriptor_len); + char *descs = tor_malloc(size); + char *cp = descs; + memcpy(cp, signed_descriptor_get_body(&r->cache_info), + r->cache_info.signed_descriptor_len); + cp += r->cache_info.signed_descriptor_len - 1; + if (e) { + if (cp[0] == '\0') { + cp[0] = '\n'; + } else if (cp[0] != '\n') { + cp[1] = '\n'; + cp++; + } + memcpy(cp, signed_descriptor_get_body(&e->cache_info), + e->cache_info.signed_descriptor_len); + cp += e->cache_info.signed_descriptor_len - 1; + } + if (cp[0] == '\n') { + cp[0] = '\0'; + } else if (cp[0] != '\0') { + cp[1] = '\0'; + } + *answer = descs; + routerinfo_free(r); + extrainfo_free(e); + } else { + return 0; + } + } + return 0; +} + +/** Implementation helper for GETINFO: knows how to enumerate hidden services + * created via the control port. */ +STATIC int +getinfo_helper_onions(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + smartlist_t *onion_list = NULL; + (void) errmsg; /* no errors from this method */ + + if (control_conn && !strcmp(question, "onions/current")) { + onion_list = control_conn->ephemeral_onion_services; + } else if (!strcmp(question, "onions/detached")) { + onion_list = get_detached_onion_services(); + } else { + return 0; + } + if (!onion_list || smartlist_len(onion_list) == 0) { + if (answer) { + *answer = tor_strdup(""); + } + } else { + if (answer) { + *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL); + } + } + + return 0; +} + +/** Implementation helper for GETINFO: answers queries about network + * liveness. */ +static int +getinfo_helper_liveness(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + (void)control_conn; + (void)errmsg; + if (strcmp(question, "network-liveness") == 0) { + if (get_cached_network_liveness()) { + *answer = tor_strdup("up"); + } else { + *answer = tor_strdup("down"); + } + } + + return 0; +} + +/** Implementation helper for GETINFO: answers queries about shared random + * value. */ +static int +getinfo_helper_sr(control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg) +{ + (void) control_conn; + (void) errmsg; + + if (!strcmp(question, "sr/current")) { + *answer = sr_get_current_for_control(); + } else if (!strcmp(question, "sr/previous")) { + *answer = sr_get_previous_for_control(); + } + /* Else statement here is unrecognized key so do nothing. */ + + return 0; +} + +/** Callback function for GETINFO: on a given control connection, try to + * answer the question <b>q</b> and store the newly-allocated answer in + * *<b>a</b>. If an internal error occurs, return -1 and optionally set + * *<b>error_out</b> to point to an error message to be delivered to the + * controller. On success, _or if the key is not recognized_, return 0. Do not + * set <b>a</b> if the key is not recognized but you may set <b>error_out</b> + * to improve the error message. + */ +typedef int (*getinfo_helper_t)(control_connection_t *, + const char *q, char **a, + const char **error_out); + +/** A single item for the GETINFO question-to-answer-function table. */ +typedef struct getinfo_item_t { + const char *varname; /**< The value (or prefix) of the question. */ + getinfo_helper_t fn; /**< The function that knows the answer: NULL if + * this entry is documentation-only. */ + const char *desc; /**< Description of the variable. */ + int is_prefix; /** Must varname match exactly, or must it be a prefix? */ +} getinfo_item_t; + +#define ITEM(name, fn, desc) { name, getinfo_helper_##fn, desc, 0 } +#define PREFIX(name, fn, desc) { name, getinfo_helper_##fn, desc, 1 } +#define DOC(name, desc) { name, NULL, desc, 0 } + +/** Table mapping questions accepted by GETINFO to the functions that know how + * to answer them. */ +static const getinfo_item_t getinfo_items[] = { + ITEM("version", misc, "The current version of Tor."), + ITEM("bw-event-cache", misc, "Cached BW events for a short interval."), + ITEM("config-file", misc, "Current location of the \"torrc\" file."), + ITEM("config-defaults-file", misc, "Current location of the defaults file."), + ITEM("config-text", misc, + "Return the string that would be written by a saveconf command."), + ITEM("config-can-saveconf", misc, + "Is it possible to save the configuration to the \"torrc\" file?"), + ITEM("accounting/bytes", accounting, + "Number of bytes read/written so far in the accounting interval."), + ITEM("accounting/bytes-left", accounting, + "Number of bytes left to write/read so far in the accounting interval."), + ITEM("accounting/enabled", accounting, "Is accounting currently enabled?"), + ITEM("accounting/hibernating", accounting, "Are we hibernating or awake?"), + ITEM("accounting/interval-start", accounting, + "Time when the accounting period starts."), + ITEM("accounting/interval-end", accounting, + "Time when the accounting period ends."), + ITEM("accounting/interval-wake", accounting, + "Time to wake up in this accounting period."), + ITEM("helper-nodes", entry_guards, NULL), /* deprecated */ + ITEM("entry-guards", entry_guards, + "Which nodes are we using as entry guards?"), + ITEM("fingerprint", misc, NULL), + PREFIX("config/", config, "Current configuration values."), + DOC("config/names", + "List of configuration options, types, and documentation."), + DOC("config/defaults", + "List of default values for configuration options. " + "See also config/names"), + PREFIX("current-time/", current_time, "Current time."), + DOC("current-time/local", "Current time on the local system."), + DOC("current-time/utc", "Current UTC time."), + PREFIX("downloads/networkstatus/", downloads, + "Download statuses for networkstatus objects"), + DOC("downloads/networkstatus/ns", + "Download status for current-mode networkstatus download"), + DOC("downloads/networkstatus/ns/bootstrap", + "Download status for bootstrap-time networkstatus download"), + DOC("downloads/networkstatus/ns/running", + "Download status for run-time networkstatus download"), + DOC("downloads/networkstatus/microdesc", + "Download status for current-mode microdesc download"), + DOC("downloads/networkstatus/microdesc/bootstrap", + "Download status for bootstrap-time microdesc download"), + DOC("downloads/networkstatus/microdesc/running", + "Download status for run-time microdesc download"), + PREFIX("downloads/cert/", downloads, + "Download statuses for certificates, by id fingerprint and " + "signing key"), + DOC("downloads/cert/fps", + "List of authority fingerprints for which any download statuses " + "exist"), + DOC("downloads/cert/fp/<fp>", + "Download status for <fp> with the default signing key; corresponds " + "to /fp/ URLs on directory server."), + DOC("downloads/cert/fp/<fp>/sks", + "List of signing keys for which specific download statuses are " + "available for this id fingerprint"), + DOC("downloads/cert/fp/<fp>/<sk>", + "Download status for <fp> with signing key <sk>; corresponds " + "to /fp-sk/ URLs on directory server."), + PREFIX("downloads/desc/", downloads, + "Download statuses for router descriptors, by descriptor digest"), + DOC("downloads/desc/descs", + "Return a list of known router descriptor digests"), + DOC("downloads/desc/<desc>", + "Return a download status for a given descriptor digest"), + PREFIX("downloads/bridge/", downloads, + "Download statuses for bridge descriptors, by bridge identity " + "digest"), + DOC("downloads/bridge/bridges", + "Return a list of configured bridge identity digests with download " + "statuses"), + DOC("downloads/bridge/<desc>", + "Return a download status for a given bridge identity digest"), + ITEM("info/names", misc, + "List of GETINFO options, types, and documentation."), + ITEM("events/names", misc, + "Events that the controller can ask for with SETEVENTS."), + ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"), + ITEM("features/names", misc, "What arguments can USEFEATURE take?"), + PREFIX("desc/id/", dir, "Router descriptors by ID."), + PREFIX("desc/name/", dir, "Router descriptors by nickname."), + ITEM("desc/all-recent", dir, + "All non-expired, non-superseded router descriptors."), + ITEM("desc/download-enabled", dir, + "Do we try to download router descriptors?"), + ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */ + ITEM("md/all", dir, "All known microdescriptors."), + PREFIX("md/id/", dir, "Microdescriptors by ID"), + PREFIX("md/name/", dir, "Microdescriptors by name"), + ITEM("md/download-enabled", dir, + "Do we try to download microdescriptors?"), + PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), + PREFIX("hs/client/desc/id", dir, + "Hidden Service descriptor in client's cache by onion."), + PREFIX("hs/service/desc/id/", dir, + "Hidden Service descriptor in services's cache by onion."), + PREFIX("net/listeners/", listeners, "Bound addresses by type"), + ITEM("ns/all", networkstatus, + "Brief summary of router status (v2 directory format)"), + PREFIX("ns/id/", networkstatus, + "Brief summary of router status by ID (v2 directory format)."), + PREFIX("ns/name/", networkstatus, + "Brief summary of router status by nickname (v2 directory format)."), + PREFIX("ns/purpose/", networkstatus, + "Brief summary of router status by purpose (v2 directory format)."), + PREFIX("consensus/", networkstatus, + "Information about and from the ns consensus."), + ITEM("network-status", dir, + "Brief summary of router status (v1 directory format)"), + ITEM("network-liveness", liveness, + "Current opinion on whether the network is live"), + ITEM("circuit-status", events, "List of current circuits originating here."), + ITEM("stream-status", events,"List of current streams."), + ITEM("orconn-status", events, "A list of current OR connections."), + ITEM("dormant", misc, + "Is Tor dormant (not building circuits because it's idle)?"), + PREFIX("address-mappings/", events, NULL), + DOC("address-mappings/all", "Current address mappings."), + DOC("address-mappings/cache", "Current cached DNS replies."), + DOC("address-mappings/config", + "Current address mappings from configuration."), + DOC("address-mappings/control", "Current address mappings from controller."), + PREFIX("status/", events, NULL), + DOC("status/circuit-established", + "Whether we think client functionality is working."), + DOC("status/enough-dir-info", + "Whether we have enough up-to-date directory information to build " + "circuits."), + DOC("status/bootstrap-phase", + "The last bootstrap phase status event that Tor sent."), + DOC("status/clients-seen", + "Breakdown of client countries seen by a bridge."), + DOC("status/fresh-relay-descs", + "A fresh relay/ei descriptor pair for Tor's current state. Not stored."), + DOC("status/version/recommended", "List of currently recommended versions."), + DOC("status/version/current", "Status of the current version."), + ITEM("address", misc, "IP address of this Tor host, if we can guess it."), + ITEM("traffic/read", misc,"Bytes read since the process was started."), + ITEM("traffic/written", misc, + "Bytes written since the process was started."), + ITEM("uptime", misc, "Uptime of the Tor daemon in seconds."), + ITEM("process/pid", misc, "Process id belonging to the main tor process."), + ITEM("process/uid", misc, "User id running the tor process."), + ITEM("process/user", misc, + "Username under which the tor process is running."), + ITEM("process/descriptor-limit", misc, "File descriptor limit."), + ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"), + PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."), + PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."), + PREFIX("dir/status/", dir, + "v2 networkstatus docs as retrieved from a DirPort."), + ITEM("dir/status-vote/current/consensus", dir, + "v3 Networkstatus consensus as retrieved from a DirPort."), + ITEM("dir/status-vote/current/consensus-microdesc", dir, + "v3 Microdescriptor consensus as retrieved from a DirPort."), + ITEM("exit-policy/default", policies, + "The default value appended to the configured exit policy."), + ITEM("exit-policy/reject-private/default", policies, + "The default rules appended to the configured exit policy by" + " ExitPolicyRejectPrivate."), + ITEM("exit-policy/reject-private/relay", policies, + "The relay-specific rules appended to the configured exit policy by" + " ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."), + ITEM("exit-policy/full", policies, "The entire exit policy of onion router"), + ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), + ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), + PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"), + ITEM("onions/current", onions, + "Onion services owned by the current control connection."), + ITEM("onions/detached", onions, + "Onion services detached from the control connection."), + ITEM("sr/current", sr, "Get current shared random value."), + ITEM("sr/previous", sr, "Get previous shared random value."), + { NULL, NULL, NULL, 0 } +}; + +/** Allocate and return a list of recognized GETINFO options. */ +static char * +list_getinfo_options(void) +{ + int i; + smartlist_t *lines = smartlist_new(); + char *ans; + for (i = 0; getinfo_items[i].varname; ++i) { + if (!getinfo_items[i].desc) + continue; + + smartlist_add_asprintf(lines, "%s%s -- %s\n", + getinfo_items[i].varname, + getinfo_items[i].is_prefix ? "*" : "", + getinfo_items[i].desc); + } + smartlist_sort_strings(lines); + + ans = smartlist_join_strings(lines, "", 0, NULL); + SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); + smartlist_free(lines); + + return ans; +} + +/** Lookup the 'getinfo' entry <b>question</b>, and return + * the answer in <b>*answer</b> (or NULL if key not recognized). + * Return 0 if success or unrecognized, or -1 if recognized but + * internal error. */ +static int +handle_getinfo_helper(control_connection_t *control_conn, + const char *question, char **answer, + const char **err_out) +{ + int i; + *answer = NULL; /* unrecognized key by default */ + + for (i = 0; getinfo_items[i].varname; ++i) { + int match; + if (getinfo_items[i].is_prefix) + match = !strcmpstart(question, getinfo_items[i].varname); + else + match = !strcmp(question, getinfo_items[i].varname); + if (match) { + tor_assert(getinfo_items[i].fn); + return getinfo_items[i].fn(control_conn, question, answer, err_out); + } + } + + return 0; /* unrecognized */ +} + +const control_cmd_syntax_t getinfo_syntax = { + .max_args = UINT_MAX, +}; + +/** Called when we receive a GETINFO command. Try to fetch all requested + * information, and reply with information or error message. */ +int +handle_control_getinfo(control_connection_t *conn, + const control_cmd_args_t *args) +{ + const smartlist_t *questions = args->args; + smartlist_t *answers = smartlist_new(); + smartlist_t *unrecognized = smartlist_new(); + char *ans = NULL; + + SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { + const char *errmsg = NULL; + + if (handle_getinfo_helper(conn, q, &ans, &errmsg) < 0) { + if (!errmsg) + errmsg = "Internal error"; + control_write_endreply(conn, 551, errmsg); + goto done; + } + if (!ans) { + if (errmsg) { + /* use provided error message */ + control_reply_add_str(unrecognized, 552, errmsg); + } else { + /* use default error message */ + control_reply_add_printf(unrecognized, 552, + "Unrecognized key \"%s\"", q); + } + } else { + control_reply_add_one_kv(answers, 250, KV_RAW, q, ans); + tor_free(ans); + } + } SMARTLIST_FOREACH_END(q); + + control_reply_add_done(answers); + + if (smartlist_len(unrecognized)) { + control_write_reply_lines(conn, unrecognized); + /* If there were any unrecognized queries, don't write real answers */ + goto done; + } + + control_write_reply_lines(conn, answers); + + done: + control_reply_free(answers); + control_reply_free(unrecognized); + + return 0; +} diff --git a/src/feature/control/control_getinfo.h b/src/feature/control/control_getinfo.h new file mode 100644 index 0000000000..0ada49258e --- /dev/null +++ b/src/feature/control/control_getinfo.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control.h + * \brief Header file for control.c. + **/ + +#ifndef TOR_CONTROL_GETINFO_H +#define TOR_CONTROL_GETINFO_H + +struct control_cmd_syntax_t; +struct control_cmd_args_t; +extern const struct control_cmd_syntax_t getinfo_syntax; + +int handle_control_getinfo(control_connection_t *conn, + const struct control_cmd_args_t *args); + +#ifdef CONTROL_GETINFO_PRIVATE +STATIC int getinfo_helper_onions( + control_connection_t *control_conn, + const char *question, + char **answer, + const char **errmsg); +STATIC void getinfo_helper_downloads_networkstatus( + const char *flavor, + download_status_t **dl_to_emit, + const char **errmsg); +STATIC void getinfo_helper_downloads_cert( + const char *fp_sk_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg); +STATIC void getinfo_helper_downloads_desc( + const char *desc_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg); +STATIC void getinfo_helper_downloads_bridge( + const char *bridge_req, + download_status_t **dl_to_emit, + smartlist_t **digest_list, + const char **errmsg); +STATIC int getinfo_helper_downloads( + control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg); +STATIC int getinfo_helper_current_consensus( + consensus_flavor_t flavor, + char **answer, + const char **errmsg); +STATIC int getinfo_helper_dir( + control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg); +STATIC int getinfo_helper_current_time( + control_connection_t *control_conn, + const char *question, char **answer, + const char **errmsg); +#endif /* defined(CONTROL_GETINFO_PRIVATE) */ + +#endif /* !defined(TOR_CONTROL_GETINFO_H) */ diff --git a/src/feature/control/control_hs.c b/src/feature/control/control_hs.c new file mode 100644 index 0000000000..f5b331de9a --- /dev/null +++ b/src/feature/control/control_hs.c @@ -0,0 +1,342 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2019-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_hs.c + * + * \brief Implement commands for Tor's control-socket interface that are + * related to onion services. + **/ + +#include "core/or/or.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_hs.h" +#include "feature/control/control_proto.h" +#include "feature/hs/hs_client.h" +#include "lib/encoding/confline.h" + +#include "feature/control/control_cmd_args_st.h" + +/** Parse the 'KeyType ":" PrivateKey' from <b>client_privkey_str</b> and store + * it into <b>privkey</b>. Use <b>conn</b> to output any errors if needed. + * + * Return 0 if all went well, -1 otherwise. */ +static int +parse_private_key_from_control_port(const char *client_privkey_str, + curve25519_secret_key_t *privkey, + control_connection_t *conn) +{ + int retval = -1; + smartlist_t *key_args = smartlist_new(); + + tor_assert(privkey); + + smartlist_split_string(key_args, client_privkey_str, ":", + SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(key_args) != 2) { + control_printf_endreply(conn, 512, "Invalid key type/blob"); + goto err; + } + + const char *key_type = smartlist_get(key_args, 0); + const char *key_blob = smartlist_get(key_args, 1); + + if (strcasecmp(key_type, "x25519")) { + control_printf_endreply(conn, 552, + "Unrecognized key type \"%s\"", key_type); + goto err; + } + + if (base64_decode((char*)privkey->secret_key, sizeof(privkey->secret_key), + key_blob, + strlen(key_blob)) != sizeof(privkey->secret_key)) { + control_printf_endreply(conn, 512, "Failed to decode x25519 private key"); + goto err; + } + + if (fast_mem_is_zero((const char*)privkey->secret_key, + sizeof(privkey->secret_key))) { + control_printf_endreply(conn, 553, + "Invalid private key \"%s\"", key_blob); + goto err; + } + + retval = 0; + + err: + SMARTLIST_FOREACH(key_args, char *, c, tor_free(c)); + smartlist_free(key_args); + return retval; +} + +/** Syntax details for ONION_CLIENT_AUTH_ADD */ +const control_cmd_syntax_t onion_client_auth_add_syntax = { + .max_args = 2, + .accept_keywords = true, +}; + +/** Called when we get an ONION_CLIENT_AUTH_ADD command; parse the body, and + * register the new client-side client auth credentials: + * "ONION_CLIENT_AUTH_ADD" SP HSAddress + * SP KeyType ":" PrivateKeyBlob + * [SP "Type=" TYPE] CRLF + */ +int +handle_control_onion_client_auth_add(control_connection_t *conn, + const control_cmd_args_t *args) +{ + int retval = -1; + smartlist_t *flags = smartlist_new(); + hs_client_service_authorization_t *creds = NULL; + + tor_assert(args); + + int argc = smartlist_len(args->args); + /* We need at least 'HSAddress' and 'PrivateKeyBlob' */ + if (argc < 2) { + control_printf_endreply(conn, 512, + "Incomplete ONION_CLIENT_AUTH_ADD command"); + goto err; + } + + creds = tor_malloc_zero(sizeof(hs_client_service_authorization_t)); + + const char *hsaddress = smartlist_get(args->args, 0); + if (!hs_address_is_valid(hsaddress)) { + control_printf_endreply(conn, 512, "Invalid v3 address \"%s\"",hsaddress); + goto err; + } + strlcpy(creds->onion_address, hsaddress, sizeof(creds->onion_address)); + + /* Parse the client private key */ + const char *client_privkey = smartlist_get(args->args, 1); + if (parse_private_key_from_control_port(client_privkey, + &creds->enc_seckey, conn) < 0) { + goto err; + } + + /* Now let's parse the remaining arguments (variable size) */ + for (const config_line_t *line = args->kwargs; line; line = line->next) { + 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"); + goto err; + } + SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) { + if (!strcasecmp(flag, "Permanent")) { + creds->flags |= CLIENT_AUTH_FLAG_IS_PERMANENT; + } else { + control_printf_endreply(conn, 512, "Invalid 'Flags' argument: %s", + escaped(flag)); + goto err; + } + } SMARTLIST_FOREACH_END(flag); + } + } + + hs_client_register_auth_status_t register_status; + /* Register the credential (register func takes ownership of cred.) */ + register_status = hs_client_register_auth_credentials(creds); + switch (register_status) { + case REGISTER_FAIL_BAD_ADDRESS: + /* 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; + case REGISTER_SUCCESS_AND_DECRYPTED: + control_printf_endreply(conn, 252,"Registered client and decrypted desc"); + break; + case REGISTER_SUCCESS: + control_printf_endreply(conn, 250, "OK"); + break; + default: + tor_assert_nonfatal_unreached(); + } + + retval = 0; + goto done; + + err: + client_service_authorization_free(creds); + + done: + SMARTLIST_FOREACH(flags, char *, s, tor_free(s)); + smartlist_free(flags); + return retval; +} + +/** Syntax details for ONION_CLIENT_AUTH_REMOVE */ +const control_cmd_syntax_t onion_client_auth_remove_syntax = { + .max_args = 1, + .accept_keywords = true, +}; + +/** Called when we get an ONION_CLIENT_AUTH_REMOVE command; parse the body, and + * register the new client-side client auth credentials. + * "ONION_CLIENT_AUTH_REMOVE" SP HSAddress + */ +int +handle_control_onion_client_auth_remove(control_connection_t *conn, + const control_cmd_args_t *args) +{ + int retval = -1; + + tor_assert(args); + + int argc = smartlist_len(args->args); + if (argc < 1) { + control_printf_endreply(conn, 512, + "Incomplete ONION_CLIENT_AUTH_REMOVE command"); + goto err; + } + + const char *hsaddress = smartlist_get(args->args, 0); + if (!hs_address_is_valid(hsaddress)) { + control_printf_endreply(conn, 512, "Invalid v3 address \"%s\"",hsaddress); + goto err; + } + + hs_client_removal_auth_status_t removal_status; + removal_status = hs_client_remove_auth_credentials(hsaddress); + switch (removal_status) { + case REMOVAL_BAD_ADDRESS: + /* 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 REMOVAL_SUCCESS_NOT_FOUND: + control_printf_endreply(conn, 251, "No credentials for \"%s\"",hsaddress); + break; + case REMOVAL_SUCCESS: + control_printf_endreply(conn, 250, "OK"); + break; + default: + tor_assert_nonfatal_unreached(); + } + + retval = 0; + + err: + return retval; +} + +/** Helper: Return a newly allocated string with the encoding of client + * authorization credentials */ +static char * +encode_client_auth_cred_for_control_port( + hs_client_service_authorization_t *cred) +{ + smartlist_t *control_line = smartlist_new(); + char x25519_b64[128]; + char *msg_str = NULL; + + tor_assert(cred); + + if (base64_encode(x25519_b64, sizeof(x25519_b64), + (char *)cred->enc_seckey.secret_key, + sizeof(cred->enc_seckey.secret_key), 0) < 0) { + tor_assert_nonfatal_unreached(); + goto err; + } + + 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) { + smartlist_add_asprintf(control_line, " Flags=Permanent"); + } + } + + /* Join all the components into a single string */ + msg_str = smartlist_join_strings(control_line, "", 0, NULL); + + err: + SMARTLIST_FOREACH(control_line, char *, cp, tor_free(cp)); + smartlist_free(control_line); + + return msg_str; +} + +/** Syntax details for ONION_CLIENT_AUTH_VIEW */ +const control_cmd_syntax_t onion_client_auth_view_syntax = { + .max_args = 1, + .accept_keywords = true, +}; + +/** Called when we get an ONION_CLIENT_AUTH_VIEW command; parse the body, and + * register the new client-side client auth credentials. + * "ONION_CLIENT_AUTH_VIEW" [SP HSAddress] CRLF + */ +int +handle_control_onion_client_auth_view(control_connection_t *conn, + const control_cmd_args_t *args) +{ + int retval = -1; + const char *hsaddress = NULL; + /* We are gonna put all the credential strings into a smartlist, and sort it + before printing, so that we can get a guaranteed order of printing. */ + smartlist_t *creds_str_list = smartlist_new(); + + tor_assert(args); + + int argc = smartlist_len(args->args); + if (argc >= 1) { + hsaddress = smartlist_get(args->args, 0); + if (!hs_address_is_valid(hsaddress)) { + control_printf_endreply(conn, 512, "Invalid v3 addr \"%s\"", hsaddress); + goto err; + } + } + + if (hsaddress) { + control_printf_midreply(conn, 250, "ONION_CLIENT_AUTH_VIEW %s", hsaddress); + } else { + control_printf_midreply(conn, 250, "ONION_CLIENT_AUTH_VIEW"); + } + + /* Create an iterator out of the digest256map */ + digest256map_t *client_auths = get_hs_client_auths_map(); + digest256map_iter_t *itr = digest256map_iter_init(client_auths); + while (!digest256map_iter_done(itr)) { + const uint8_t *service_pubkey; + void *valp; + digest256map_iter_get(itr, &service_pubkey, &valp); + tor_assert(valp); + hs_client_service_authorization_t *cred = valp; + + /* If a specific HS address was requested, only print creds for that one */ + if (hsaddress && strcmp(cred->onion_address, hsaddress)) { + itr = digest256map_iter_next(client_auths, itr); + continue; + } + + char *encoding_str = encode_client_auth_cred_for_control_port(cred); + tor_assert_nonfatal(encoding_str); + smartlist_add(creds_str_list, encoding_str); + + itr = digest256map_iter_next(client_auths, itr); + } + + /* We got everything: Now sort the strings and print them */ + smartlist_sort_strings(creds_str_list); + SMARTLIST_FOREACH_BEGIN(creds_str_list, char *, c) { + control_printf_midreply(conn, 250, "%s", c); + } SMARTLIST_FOREACH_END(c); + + send_control_done(conn); + + retval = 0; + + err: + SMARTLIST_FOREACH(creds_str_list, char *, cp, tor_free(cp)); + smartlist_free(creds_str_list); + return retval; +} diff --git a/src/feature/control/control_hs.h b/src/feature/control/control_hs.h new file mode 100644 index 0000000000..8a0cd6818d --- /dev/null +++ b/src/feature/control/control_hs.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2019-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_hs.c + * + * \brief Header file for control_hs.c. + **/ + +#ifndef TOR_CONTROL_HS_H +#define TOR_CONTROL_HS_H + +struct control_connection_t; +struct control_cmd_syntax_t; +struct control_cmd_args_t; + +extern const struct control_cmd_syntax_t onion_client_auth_add_syntax; +extern const struct control_cmd_syntax_t onion_client_auth_remove_syntax; +extern const struct control_cmd_syntax_t onion_client_auth_view_syntax; + +int +handle_control_onion_client_auth_add(struct control_connection_t *conn, + const struct control_cmd_args_t *args); + +int +handle_control_onion_client_auth_remove(struct control_connection_t *conn, + const struct control_cmd_args_t *args); + +int +handle_control_onion_client_auth_view(struct control_connection_t *conn, + const struct control_cmd_args_t *args); + +#endif /* !defined(TOR_CONTROL_HS_H) */ diff --git a/src/feature/control/control_proto.c b/src/feature/control/control_proto.c new file mode 100644 index 0000000000..98715ad9d5 --- /dev/null +++ b/src/feature/control/control_proto.c @@ -0,0 +1,434 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_proto.c + * \brief Formatting functions for controller data. + */ + +#include "core/or/or.h" + +#include "core/mainloop/connection.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "feature/control/control_proto.h" +#include "feature/nodelist/nodelist.h" + +#include "core/or/cpath_build_state_st.h" +#include "core/or/entry_connection_st.h" +#include "core/or/or_connection_st.h" +#include "core/or/origin_circuit_st.h" +#include "core/or/socks_request_st.h" +#include "feature/control/control_connection_st.h" +#include "lib/container/smartlist.h" +#include "lib/encoding/kvline.h" + +/** Append a NUL-terminated string <b>s</b> to the end of + * <b>conn</b>-\>outbuf. + */ +void +connection_write_str_to_buf(const char *s, control_connection_t *conn) +{ + size_t len = strlen(s); + connection_buf_add(s, len, TO_CONN(conn)); +} + +/** Acts like sprintf, but writes its formatted string to the end of + * <b>conn</b>-\>outbuf. */ +void +connection_printf_to_buf(control_connection_t *conn, const char *format, ...) +{ + va_list ap; + char *buf = NULL; + int len; + + va_start(ap,format); + len = tor_vasprintf(&buf, format, ap); + va_end(ap); + + if (len < 0) { + log_err(LD_BUG, "Unable to format string for controller."); + tor_assert(0); + } + + connection_buf_add(buf, (size_t)len, TO_CONN(conn)); + + tor_free(buf); +} + +/** Given a <b>len</b>-character string in <b>data</b>, made of lines + * terminated by CRLF, allocate a new string in *<b>out</b>, and copy the + * contents of <b>data</b> into *<b>out</b>, adding a period before any period + * that appears at the start of a line, and adding a period-CRLF line at + * the end. Replace all LF characters sequences with CRLF. Return the number + * of bytes in *<b>out</b>. + * + * This corresponds to CmdData in control-spec.txt. + */ +size_t +write_escaped_data(const char *data, size_t len, char **out) +{ + tor_assert(len < SIZE_MAX - 9); + size_t sz_out = len+8+1; + char *outp; + const char *start = data, *end; + size_t i; + int start_of_line; + for (i=0; i < len; ++i) { + if (data[i] == '\n') { + sz_out += 2; /* Maybe add a CR; maybe add a dot. */ + if (sz_out >= SIZE_T_CEILING) { + log_warn(LD_BUG, "Input to write_escaped_data was too long"); + *out = tor_strdup(".\r\n"); + return 3; + } + } + } + *out = outp = tor_malloc(sz_out); + end = data+len; + start_of_line = 1; + while (data < end) { + if (*data == '\n') { + if (data > start && data[-1] != '\r') + *outp++ = '\r'; + start_of_line = 1; + } else if (*data == '.') { + if (start_of_line) { + start_of_line = 0; + *outp++ = '.'; + } + } else { + start_of_line = 0; + } + *outp++ = *data++; + } + if (outp < *out+2 || fast_memcmp(outp-2, "\r\n", 2)) { + *outp++ = '\r'; + *outp++ = '\n'; + } + *outp++ = '.'; + *outp++ = '\r'; + *outp++ = '\n'; + *outp = '\0'; /* NUL-terminate just in case. */ + tor_assert(outp >= *out); + tor_assert((size_t)(outp - *out) <= sz_out); + return outp - *out; +} + +/** Given a <b>len</b>-character string in <b>data</b>, made of lines + * terminated by CRLF, allocate a new string in *<b>out</b>, and copy + * the contents of <b>data</b> into *<b>out</b>, removing any period + * that appears at the start of a line, and replacing all CRLF sequences + * with LF. Return the number of + * bytes in *<b>out</b>. + * + * This corresponds to CmdData in control-spec.txt. + */ +size_t +read_escaped_data(const char *data, size_t len, char **out) +{ + char *outp; + const char *next; + const char *end; + + *out = outp = tor_malloc(len+1); + + end = data+len; + + while (data < end) { + /* we're at the start of a line. */ + if (*data == '.') + ++data; + next = memchr(data, '\n', end-data); + if (next) { + size_t n_to_copy = next-data; + /* Don't copy a CR that precedes this LF. */ + if (n_to_copy && *(next-1) == '\r') + --n_to_copy; + memcpy(outp, data, n_to_copy); + outp += n_to_copy; + data = next+1; /* This will point at the start of the next line, + * or the end of the string, or a period. */ + } else { + memcpy(outp, data, end-data); + outp += (end-data); + *outp = '\0'; + return outp - *out; + } + *outp++ = '\n'; + } + + *outp = '\0'; + return outp - *out; +} + +/** Send a "DONE" message down the control connection <b>conn</b>. */ +void +send_control_done(control_connection_t *conn) +{ + control_write_endreply(conn, 250, "OK"); +} + +/** Write a reply to the control channel. + * + * @param conn control connection + * @param code numeric result code + * @param c separator character, usually ' ', '-', or '+' + * @param s string reply content + */ +MOCK_IMPL(void, +control_write_reply, (control_connection_t *conn, int code, int c, + const char *s)) +{ + connection_printf_to_buf(conn, "%03d%c%s\r\n", code, c, s); +} + +/** Write a formatted reply to the control channel. + * + * @param conn control connection + * @param code numeric result code + * @param c separator character, usually ' ', '-', or '+' + * @param fmt format string + * @param ap va_list from caller + */ +void +control_vprintf_reply(control_connection_t *conn, int code, int c, + const char *fmt, va_list ap) +{ + char *buf = NULL; + int len; + + len = tor_vasprintf(&buf, fmt, ap); + if (len < 0) { + log_err(LD_BUG, "Unable to format string for controller."); + tor_assert(0); + } + control_write_reply(conn, code, c, buf); + tor_free(buf); +} + +/** Write an EndReplyLine */ +void +control_write_endreply(control_connection_t *conn, int code, const char *s) +{ + control_write_reply(conn, code, ' ', s); +} + +/** Write a formatted EndReplyLine */ +void +control_printf_endreply(control_connection_t *conn, int code, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + control_vprintf_reply(conn, code, ' ', fmt, ap); + va_end(ap); +} + +/** Write a MidReplyLine */ +void +control_write_midreply(control_connection_t *conn, int code, const char *s) +{ + control_write_reply(conn, code, '-', s); +} + +/** Write a formatted MidReplyLine */ +void +control_printf_midreply(control_connection_t *conn, int code, const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + control_vprintf_reply(conn, code, '-', fmt, ap); + va_end(ap); +} + +/** Write a DataReplyLine */ +void +control_write_datareply(control_connection_t *conn, int code, const char *s) +{ + control_write_reply(conn, code, '+', s); +} + +/** Write a formatted DataReplyLine */ +void +control_printf_datareply(control_connection_t *conn, int code, const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + control_vprintf_reply(conn, code, '+', fmt, ap); + va_end(ap); +} + +/** Write a CmdData */ +void +control_write_data(control_connection_t *conn, const char *data) +{ + char *esc = NULL; + size_t esc_len; + + esc_len = write_escaped_data(data, strlen(data), &esc); + connection_buf_add(esc, esc_len, TO_CONN(conn)); + tor_free(esc); +} + +/** Write a single reply line to @a conn. + * + * @param conn control connection + * @param line control reply line to write + * @param lastone true if this is the last reply line of a multi-line reply + */ +void +control_write_reply_line(control_connection_t *conn, + const control_reply_line_t *line, bool lastone) +{ + const config_line_t *kvline = line->kvline; + char *s = NULL; + + if (strpbrk(kvline->value, "\r\n") != NULL) { + /* If a key-value pair needs to be encoded as CmdData, it can be + the only key-value pair in that reply line */ + tor_assert(kvline->next == NULL); + control_printf_datareply(conn, line->code, "%s=", kvline->key); + control_write_data(conn, kvline->value); + return; + } + s = kvline_encode(kvline, line->flags); + if (lastone) { + control_write_endreply(conn, line->code, s); + } else { + control_write_midreply(conn, line->code, s); + } + tor_free(s); +} + +/** Write a set of reply lines to @a conn. + * + * @param conn control connection + * @param lines smartlist of pointers to control_reply_line_t to write + */ +void +control_write_reply_lines(control_connection_t *conn, smartlist_t *lines) +{ + bool lastone = false; + + SMARTLIST_FOREACH_BEGIN(lines, control_reply_line_t *, line) { + if (line_sl_idx >= line_sl_len - 1) + lastone = true; + control_write_reply_line(conn, line, lastone); + } SMARTLIST_FOREACH_END(line); +} + +/** Add a single key-value pair as a new reply line to a control reply + * line list. + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param flags kvline encoding flags + * @param key key + * @param val value + */ +void +control_reply_add_one_kv(smartlist_t *reply, int code, int flags, + const char *key, const char *val) +{ + control_reply_line_t *line = tor_malloc_zero(sizeof(*line)); + + line->code = code; + line->flags = flags; + config_line_append(&line->kvline, key, val); + smartlist_add(reply, line); +} + +/** Append a single key-value pair to last reply line in a control + * reply line list. + * + * @param reply smartlist of pointers to control_reply_line_t + * @param key key + * @param val value + */ +void +control_reply_append_kv(smartlist_t *reply, const char *key, const char *val) +{ + int len = smartlist_len(reply); + control_reply_line_t *line; + + tor_assert(len > 0); + + line = smartlist_get(reply, len - 1); + config_line_append(&line->kvline, key, val); +} + +/** Add new reply line consisting of the string @a s + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param s string containing the rest of the reply line + */ +void +control_reply_add_str(smartlist_t *reply, int code, const char *s) +{ + control_reply_add_one_kv(reply, code, KV_OMIT_KEYS|KV_RAW, "", s); +} + +/** Format a new reply line + * + * @param reply smartlist of pointers to control_reply_line_t + * @param code numeric control reply code + * @param fmt format string + */ +void +control_reply_add_printf(smartlist_t *reply, int code, const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + va_start(ap, fmt); + (void)tor_vasprintf(&buf, fmt, ap); + va_end(ap); + control_reply_add_str(reply, code, buf); + tor_free(buf); +} + +/** Add a "250 OK" line to a set of control reply lines */ +void +control_reply_add_done(smartlist_t *reply) +{ + control_reply_add_str(reply, 250, "OK"); +} + +/** Free a control_reply_line_t. Don't call this directly; use the + * control_reply_line_free() macro instead. */ +void +control_reply_line_free_(control_reply_line_t *line) +{ + if (!line) + return; + config_free_lines(line->kvline); + tor_free_(line); +} + +/** Clear a smartlist of control_reply_line_t. Doesn't free the + * smartlist, but does free each individual line. */ +void +control_reply_clear(smartlist_t *reply) +{ + SMARTLIST_FOREACH(reply, control_reply_line_t *, line, + control_reply_line_free(line)); + smartlist_clear(reply); +} + +/** Free a smartlist of control_reply_line_t. Don't call this + * directly; use the control_reply_free() macro instead. */ +void +control_reply_free_(smartlist_t *reply) +{ + control_reply_clear(reply); + smartlist_free_(reply); +} diff --git a/src/feature/control/control_proto.h b/src/feature/control/control_proto.h new file mode 100644 index 0000000000..4c32b820d1 --- /dev/null +++ b/src/feature/control/control_proto.h @@ -0,0 +1,120 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_proto.h + * \brief Header file for control_proto.c. + * + * See @ref replylines for details about the key-value abstraction for + * generating reply lines. + **/ + +#ifndef TOR_CONTROL_PROTO_H +#define TOR_CONTROL_PROTO_H + +#include "lib/encoding/confline.h" + +/** + * @defgroup replylines Control reply lines + * @brief Key-value structures for control reply lines + * + * Control reply lines are config_line_t key-value structures with + * some additional information to help formatting, such as the numeric + * result code specified in the control protocol and flags affecting + * the way kvline_encode() formats the @a kvline. + * + * Generally, modules implementing control commands will work with + * smartlists of these structures, using functions like + * control_reply_add_str() for adding a reply line consisting of a + * single string, or control_reply_add_one_kv() and + * control_reply_append_kv() for composing a line containing one or + * more key-value pairs. + * + * @{ + */ +/** @brief A reply line for the control protocol. + * + * This wraps config_line_t with some additional information that's + * useful when generating control reply lines. + */ +typedef struct control_reply_line_t { + int code; /**< numeric code */ + int flags; /**< kvline encoding flags */ + config_line_t *kvline; /**< kvline */ +} control_reply_line_t; + +void control_reply_line_free_(control_reply_line_t *line); +/** + * @brief Free and null a control_reply_line_t + * + * @param line pointer to control_reply_line_t to free + */ +#define control_reply_line_free(line) \ + FREE_AND_NULL(control_reply_line_t, \ + control_reply_line_free_, (line)) +/** @} */ + +void connection_write_str_to_buf(const char *s, control_connection_t *conn); +void connection_printf_to_buf(control_connection_t *conn, + const char *format, ...) + CHECK_PRINTF(2,3); + +size_t write_escaped_data(const char *data, size_t len, char **out); +size_t read_escaped_data(const char *data, size_t len, char **out); +void send_control_done(control_connection_t *conn); + +MOCK_DECL(void, control_write_reply, (control_connection_t *conn, int code, + int c, const char *s)); +void control_vprintf_reply(control_connection_t *conn, int code, int c, + const char *fmt, va_list ap) + CHECK_PRINTF(4, 0); +void control_write_endreply(control_connection_t *conn, int code, + const char *s); +void control_printf_endreply(control_connection_t *conn, int code, + const char *fmt, ...) + CHECK_PRINTF(3, 4); +void control_write_midreply(control_connection_t *conn, int code, + const char *s); +void control_printf_midreply(control_connection_t *conn, int code, + const char *fmt, + ...) + CHECK_PRINTF(3, 4); +void control_write_datareply(control_connection_t *conn, int code, + const char *s); +void control_printf_datareply(control_connection_t *conn, int code, + const char *fmt, + ...) + CHECK_PRINTF(3, 4); +void control_write_data(control_connection_t *conn, const char *data); + +/** @addtogroup replylines + * @{ + */ +void control_write_reply_line(control_connection_t *conn, + const control_reply_line_t *line, bool lastone); +void control_write_reply_lines(control_connection_t *conn, smartlist_t *lines); + +void control_reply_add_one_kv(smartlist_t *reply, int code, int flags, + const char *key, const char *val); +void control_reply_append_kv(smartlist_t *reply, const char *key, + const char *val); +void control_reply_add_str(smartlist_t *reply, int code, const char *s); +void control_reply_add_printf(smartlist_t *reply, int code, + const char *fmt, ...) + CHECK_PRINTF(3, 4); +void control_reply_add_done(smartlist_t *reply); + +void control_reply_clear(smartlist_t *reply); +void control_reply_free_(smartlist_t *reply); + +/** @brief Free and null a smartlist of control_reply_line_t. + * + * @param r pointer to smartlist_t of control_reply_line_t to free */ +#define control_reply_free(r) \ + FREE_AND_NULL(smartlist_t, control_reply_free_, (r)) +/** @} */ + +#endif /* !defined(TOR_CONTROL_PROTO_H) */ diff --git a/src/feature/control/feature_control.md b/src/feature/control/feature_control.md new file mode 100644 index 0000000000..9f1681ea91 --- /dev/null +++ b/src/feature/control/feature_control.md @@ -0,0 +1,8 @@ +@dir /feature/control +@brief feature/control: Controller API. + +The Controller API is a text-based protocol that another program (or another +thread, if you're running Tor in-process) can use to configure and control +Tor while it is running. The current protocol is documented in +[control-spec.txt](https://gitweb.torproject.org/torspec.git/tree/control-spec.txt). + diff --git a/src/feature/control/fmt_serverstatus.c b/src/feature/control/fmt_serverstatus.c index a1ddd2119a..ed9ad95ce2 100644 --- a/src/feature/control/fmt_serverstatus.c +++ b/src/feature/control/fmt_serverstatus.c @@ -1,16 +1,21 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file fmt_serverstatus.c + * @brief Format relay info for a controller. + **/ + #include "core/or/or.h" #include "feature/control/fmt_serverstatus.h" #include "app/config/config.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/voteflags.h"// XXXX remove +#include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" -#include "feature/nodelist/routerinfo.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" @@ -66,11 +71,9 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, smartlist_t *rs_entries; time_t now = time(NULL); time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; - const or_options_t *options = get_options(); /* We include v2 dir auths here too, because they need to answer * controllers. Eventually we'll deprecate this whole function; * see also networkstatus_getinfo_by_purpose(). */ - int authdir = authdir_mode_publishes_statuses(options); tor_assert(router_status_out); rs_entries = smartlist_new(); @@ -78,10 +81,6 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { const node_t *node = node_get_by_id(ri->cache_info.identity_digest); tor_assert(node); - if (authdir) { - /* Update router status in routerinfo_t. */ - dirserv_set_router_is_running(ri, now); - } if (for_controller) { char name_buf[MAX_VERBOSE_NICKNAME_LEN+2]; char *cp = name_buf; diff --git a/src/feature/control/fmt_serverstatus.h b/src/feature/control/fmt_serverstatus.h index 4b95e5b59f..9dd9fe125c 100644 --- a/src/feature/control/fmt_serverstatus.h +++ b/src/feature/control/fmt_serverstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,4 +15,4 @@ int list_server_status_v1(smartlist_t *routers, char **router_status_out, int for_controller); -#endif +#endif /* !defined(TOR_FMT_SERVERSTATUS_H) */ diff --git a/src/feature/control/getinfo_geoip.c b/src/feature/control/getinfo_geoip.c index d188725fa3..33019207e6 100644 --- a/src/feature/control/getinfo_geoip.c +++ b/src/feature/control/getinfo_geoip.c @@ -1,3 +1,12 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file getinfo_geoip.c + * @brief GEOIP-related contoller GETINFO commands. + **/ #include "core/or/or.h" #include "core/mainloop/connection.h" diff --git a/src/feature/control/getinfo_geoip.h b/src/feature/control/getinfo_geoip.h index fe22137859..5bc4b08414 100644 --- a/src/feature/control/getinfo_geoip.h +++ b/src/feature/control/getinfo_geoip.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file getinfo_geoip.h + * @brief Header for getinfo_geoip.c + **/ + #ifndef TOR_GETINFO_GEOIP_H #define TOR_GETINFO_GEOIP_H @@ -11,4 +16,4 @@ int getinfo_helper_geoip(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg); -#endif +#endif /* !defined(TOR_GETINFO_GEOIP_H) */ diff --git a/src/feature/control/include.am b/src/feature/control/include.am new file mode 100644 index 0000000000..07094f23bb --- /dev/null +++ b/src/feature/control/include.am @@ -0,0 +1,39 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/control/btrack.c \ + src/feature/control/btrack_circuit.c \ + src/feature/control/btrack_orconn.c \ + src/feature/control/btrack_orconn_cevent.c \ + src/feature/control/btrack_orconn_maps.c \ + src/feature/control/control.c \ + src/feature/control/control_auth.c \ + src/feature/control/control_bootstrap.c \ + src/feature/control/control_cmd.c \ + src/feature/control/control_hs.c \ + src/feature/control/control_events.c \ + src/feature/control/control_fmt.c \ + src/feature/control/control_getinfo.c \ + src/feature/control/control_proto.c \ + src/feature/control/fmt_serverstatus.c \ + src/feature/control/getinfo_geoip.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/control/btrack_circuit.h \ + src/feature/control/btrack_orconn.h \ + src/feature/control/btrack_orconn_cevent.h \ + src/feature/control/btrack_orconn_maps.h \ + src/feature/control/btrack_sys.h \ + src/feature/control/control.h \ + src/feature/control/control_auth.h \ + src/feature/control/control_cmd.h \ + src/feature/control/control_hs.h \ + src/feature/control/control_cmd_args_st.h \ + src/feature/control/control_connection_st.h \ + src/feature/control/control_events.h \ + src/feature/control/control_fmt.h \ + src/feature/control/control_getinfo.h \ + src/feature/control/control_proto.h \ + src/feature/control/fmt_serverstatus.h \ + src/feature/control/getinfo_geoip.h diff --git a/src/feature/dirauth/.may_include b/src/feature/dirauth/.may_include new file mode 100644 index 0000000000..a9bb274699 --- /dev/null +++ b/src/feature/dirauth/.may_include @@ -0,0 +1,2 @@ +*.h +feature/dirauth/*.inc diff --git a/src/feature/dirauth/authmode.c b/src/feature/dirauth/authmode.c index 29fcc6d1a9..0fde7bc679 100644 --- a/src/feature/dirauth/authmode.c +++ b/src/feature/dirauth/authmode.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -26,6 +26,15 @@ authdir_mode(const or_options_t *options) { return options->AuthoritativeDir != 0; } + +/* Return true iff we believe ourselves to be a v3 authoritative directory + * server. */ +int +authdir_mode_v3(const or_options_t *options) +{ + return authdir_mode(options) && options->V3AuthoritativeDir != 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. diff --git a/src/feature/dirauth/authmode.h b/src/feature/dirauth/authmode.h index 876a1f947b..6e6ba7f8ae 100644 --- a/src/feature/dirauth/authmode.h +++ b/src/feature/dirauth/authmode.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,22 +14,16 @@ #ifdef HAVE_MODULE_DIRAUTH int authdir_mode(const or_options_t *options); +int authdir_mode_v3(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); -/* Return true iff we believe ourselves to be a v3 authoritative directory - * server. */ -static inline int -authdir_mode_v3(const or_options_t *options) -{ - return authdir_mode(options) && options->V3AuthoritativeDir != 0; -} - +/* Is the dirauth module enabled? */ #define have_module_dirauth() (1) -#else /* HAVE_MODULE_DIRAUTH */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ #define authdir_mode(options) (((void)(options)),0) #define authdir_mode_handles_descs(options,purpose) \ @@ -41,6 +35,6 @@ authdir_mode_v3(const or_options_t *options) #define have_module_dirauth() (0) -#endif /* HAVE_MODULE_DIRAUTH */ +#endif /* defined(HAVE_MODULE_DIRAUTH) */ -#endif /* TOR_MODE_H */ +#endif /* !defined(TOR_DIRAUTH_MODE_H) */ diff --git a/src/feature/dirauth/bridgeauth.c b/src/feature/dirauth/bridgeauth.c new file mode 100644 index 0000000000..b7bf3e4e04 --- /dev/null +++ b/src/feature/dirauth/bridgeauth.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file bridgeauth.c + * @brief Bridge authority code + **/ + +#include "core/or/or.h" +#include "feature/dirauth/bridgeauth.h" +#include "feature/dirauth/voteflags.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/relay/router.h" +#include "app/config/config.h" + +#include "feature/nodelist/routerinfo_st.h" + +/** Write out router status entries for all our bridge descriptors. Here, we + * also mark routers as running. */ +void +bridgeauth_dump_bridge_status_to_file(time_t now) +{ + char *status; + char *fname = NULL; + char *thresholds = NULL; + char *published_thresholds_and_status = NULL; + char published[ISO_TIME_LEN+1]; + const routerinfo_t *me = router_get_my_routerinfo(); + char fingerprint[FINGERPRINT_LEN+1]; + char *fingerprint_line = NULL; + + dirserv_set_bridges_running(now); + status = networkstatus_getinfo_by_purpose("bridge", now); + + if (me && crypto_pk_get_fingerprint(me->identity_pkey, + fingerprint, 0) >= 0) { + tor_asprintf(&fingerprint_line, "fingerprint %s\n", fingerprint); + } else { + log_warn(LD_BUG, "Error computing fingerprint for bridge status."); + } + format_iso_time(published, now); + dirserv_compute_bridge_flag_thresholds(); + thresholds = dirserv_get_flag_thresholds_line(); + tor_asprintf(&published_thresholds_and_status, + "published %s\nflag-thresholds %s\n%s%s", + published, thresholds, fingerprint_line ? fingerprint_line : "", + status); + fname = get_datadir_fname("networkstatus-bridges"); + if (write_str_to_file(fname,published_thresholds_and_status,0)<0) { + log_warn(LD_DIRSERV, "Unable to write networkstatus-bridges file."); + } + tor_free(thresholds); + tor_free(published_thresholds_and_status); + tor_free(fname); + tor_free(status); + tor_free(fingerprint_line); +} diff --git a/src/feature/dirauth/bridgeauth.h b/src/feature/dirauth/bridgeauth.h new file mode 100644 index 0000000000..382d1cfcb8 --- /dev/null +++ b/src/feature/dirauth/bridgeauth.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file bridgeauth.h + * @brief Header for bridgeauth.c + **/ + +#ifndef TOR_DIRAUTH_BRIDGEAUTH_H +#define TOR_DIRAUTH_BRIDGEAUTH_H + +void bridgeauth_dump_bridge_status_to_file(time_t now); + +#endif /* !defined(TOR_DIRAUTH_BRIDGEAUTH_H) */ diff --git a/src/feature/dirauth/bwauth.c b/src/feature/dirauth/bwauth.c index 12f9399e9f..ff0c78f018 100644 --- a/src/feature/dirauth/bwauth.c +++ b/src/feature/dirauth/bwauth.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,13 +13,16 @@ #include "feature/dirauth/bwauth.h" #include "app/config/config.h" +#include "feature/dirauth/dirauth_sys.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/routerlist.h" #include "feature/dirparse/ns_parse.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/vote_routerstatus_st.h" +#include "lib/crypt_ops/crypto_format.h" #include "lib/encoding/keyval.h" /** Total number of routers with measured bandwidth; this is set by @@ -55,7 +58,7 @@ dirserv_get_last_n_measured_bws(void) } /** Measured bandwidth cache entry */ -typedef struct mbw_cache_entry_s { +typedef struct mbw_cache_entry_t { long mbw_kb; time_t as_of; } mbw_cache_entry_t; @@ -181,7 +184,7 @@ dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri) /* Check if we have a measured bandwidth, and check the threshold if not */ if (!(dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest, &mbw_kb, NULL))) { - threshold = get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; + threshold = dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; if (routers_with_measured_bw > threshold) { /* Return zero for unmeasured bandwidth if we are above threshold */ bw_kb = 0; @@ -198,14 +201,38 @@ dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri) } /** - * Read the measured bandwidth list file, apply it to the list of - * vote_routerstatus_t and store all the headers in <b>bw_file_headers</b>. + * Read the measured bandwidth list <b>from_file</b>: + * - store all the headers in <b>bw_file_headers</b>, + * - apply bandwidth lines to the list of vote_routerstatus_t in + * <b>routerstatuses</b>, + * - cache bandwidth lines for dirserv_get_bandwidth_for_router(), + * - expire old entries in the measured bandwidth cache, and + * - store the DIGEST_SHA256 of the contents of the file in <b>digest_out</b>. + * * Returns -1 on error, 0 otherwise. + * + * If the file can't be read, or is empty: + * - <b>bw_file_headers</b> is empty, + * - <b>routerstatuses</b> is not modified, + * - the measured bandwidth cache is not modified, and + * - <b>digest_out</b> is the zero-byte digest. + * + * Otherwise, if there is an error later in the file: + * - <b>bw_file_headers</b> contains all the headers up to the error, + * - <b>routerstatuses</b> is updated with all the relay lines up to the error, + * - the measured bandwidth cache is updated with all the relay lines up to + * the error, + * - if the timestamp is valid and recent, old entries in the measured + * bandwidth cache are expired, and + * - <b>digest_out</b> is the digest up to the first read error (if any). + * The digest is taken over all the readable file contents, even if the + * file is outdated or unparseable. */ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses, - smartlist_t *bw_file_headers) + smartlist_t *bw_file_headers, + uint8_t *digest_out) { FILE *fp = tor_fopen_cloexec(from_file, "r"); int applied_lines = 0; @@ -219,8 +246,7 @@ dirserv_read_measured_bandwidths(const char *from_file, int rv = -1; char *line = NULL; size_t n = 0; - - /* Initialise line, so that we can't possibly run off the end. */ + crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA256); if (fp == NULL) { log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s", @@ -228,16 +254,18 @@ dirserv_read_measured_bandwidths(const char *from_file, goto err; } - /* If fgets fails, line is either unmodified, or indeterminate. */ if (tor_getline(&line,&n,fp) <= 0) { log_warn(LD_DIRSERV, "Empty bandwidth file"); goto err; } + /* If the line could be gotten, add it to the digest */ + crypto_digest_add_bytes(digest, (const char *) line, strlen(line)); if (!strlen(line) || line[strlen(line)-1] != '\n') { log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s", escaped(line)); - goto err; + /* Continue adding lines to the digest. */ + goto continue_digest; } line[strlen(line)-1] = '\0'; @@ -245,14 +273,14 @@ dirserv_read_measured_bandwidths(const char *from_file, if (!ok) { log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s", escaped(line)); - goto err; + goto continue_digest; } - now = time(NULL); + now = approx_time(); if ((now - file_time) > MAX_MEASUREMENT_AGE) { log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u", (unsigned)(time(NULL) - file_time)); - goto err; + goto continue_digest; } /* If timestamp was correct and bw_file_headers is not NULL, @@ -267,6 +295,7 @@ dirserv_read_measured_bandwidths(const char *from_file, while (!feof(fp)) { measured_bw_line_t parsed_line; if (tor_getline(&line, &n, fp) >= 0) { + crypto_digest_add_bytes(digest, (const char *) line, strlen(line)); if (measured_bw_line_parse(&parsed_line, line, line_is_after_headers) != -1) { /* This condition will be true when the first complete valid bw line @@ -305,6 +334,14 @@ dirserv_read_measured_bandwidths(const char *from_file, "Applied %d measurements.", applied_lines); rv = 0; + continue_digest: + /* Continue parsing lines to return the digest of the Bandwidth File. */ + while (!feof(fp)) { + if (tor_getline(&line, &n, fp) >= 0) { + crypto_digest_add_bytes(digest, (const char *) line, strlen(line)); + } + } + err: if (line) { // we need to raw_free this buffer because we got it from tor_getdelim() @@ -312,6 +349,9 @@ dirserv_read_measured_bandwidths(const char *from_file, } if (fp) fclose(fp); + if (digest_out) + crypto_digest_get_digest(digest, (char *) digest_out, DIGEST256_LEN); + crypto_digest_free(digest); return rv; } @@ -327,6 +367,9 @@ dirserv_read_measured_bandwidths(const char *from_file, * the header block yet. If we encounter an incomplete bw line, return -1 but * don't warn since there could be additional header lines coming. If we * encounter a proper bw line, return 0 (and we got past the headers). + * + * If the line contains "vote=0", stop parsing it, and return -1, so that the + * line is ignored during voting. */ STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line, diff --git a/src/feature/dirauth/bwauth.h b/src/feature/dirauth/bwauth.h index 4507728458..849c58e2fc 100644 --- a/src/feature/dirauth/bwauth.h +++ b/src/feature/dirauth/bwauth.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -21,8 +21,8 @@ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses, - smartlist_t *bw_file_headers); - + smartlist_t *bw_file_headers, + uint8_t *digest_out); int dirserv_query_measured_bw_cache_kb(const char *node_id, long *bw_out, time_t *as_of_out); @@ -55,4 +55,4 @@ STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line, STATIC void dirserv_expire_measured_bw_cache(time_t now); #endif /* defined(BWAUTH_PRIVATE) */ -#endif +#endif /* !defined(TOR_BWAUTH_H) */ diff --git a/src/feature/dirauth/dirauth_config.c b/src/feature/dirauth/dirauth_config.c new file mode 100644 index 0000000000..ca16dc8424 --- /dev/null +++ b/src/feature/dirauth/dirauth_config.c @@ -0,0 +1,470 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_config.c + * @brief Code to interpret the user's configuration of Tor's directory + * authority module. + **/ + +#include "orconfig.h" +#include "feature/dirauth/dirauth_config.h" + +#include "lib/encoding/confline.h" +#include "lib/confmgt/confmgt.h" +#include "lib/conf/confdecl.h" + +/* Required for dirinfo_type_t in or_options_t */ +#include "core/or/or.h" +#include "app/config/config.h" + +#include "feature/dircommon/voting_schedule.h" +#include "feature/stats/rephist.h" + +#include "feature/dirauth/authmode.h" +#include "feature/dirauth/bwauth.h" +#include "feature/dirauth/dirauth_periodic.h" +#include "feature/dirauth/dirauth_sys.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/guardfraction.h" +#include "feature/dirauth/dirauth_options_st.h" + +/* Copied from config.c, we will refactor later in 29211. */ +#define REJECT(arg) \ + STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END +#if defined(__GNUC__) && __GNUC__ <= 3 +#define COMPLAIN(args...) \ + STMT_BEGIN log_warn(LD_CONFIG, args); STMT_END +#else +#define COMPLAIN(args, ...) \ + STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ + +#define YES_IF_CHANGED_INT(opt) \ + if (!CFG_EQ_INT(old_options, new_options, opt)) return 1; + +/** Return true iff we are configured to reject request under load for non + * relay connections. */ +bool +dirauth_should_reject_requests_under_load(void) +{ + return !!dirauth_get_options()->AuthDirRejectRequestsUnderLoad; +} + +/** + * Legacy validation/normalization function for the dirauth mode options in + * options. Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_dirauth_mode(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (!authdir_mode(options)) + return 0; + + /* confirm that our address isn't broken, so we can complain now */ + uint32_t tmp; + if (resolve_my_address(LOG_WARN, options, &tmp, NULL, NULL) < 0) + REJECT("Failed to resolve/guess local address. See logs for details."); + + if (!options->ContactInfo && !options->TestingTorNetwork) + REJECT("Authoritative directory servers must set ContactInfo"); + + if (options->UseEntryGuards) { + log_info(LD_CONFIG, "Authoritative directory servers can't set " + "UseEntryGuards. Disabling."); + options->UseEntryGuards = 0; + } + if (!options->DownloadExtraInfo && authdir_mode_v3(options)) { + log_info(LD_CONFIG, "Authoritative directories always try to download " + "extra-info documents. Setting DownloadExtraInfo."); + options->DownloadExtraInfo = 1; + } + if (!(options->BridgeAuthoritativeDir || + options->V3AuthoritativeDir)) + REJECT("AuthoritativeDir is set, but none of " + "(Bridge/V3)AuthoritativeDir is set."); + + /* If we have a v3bandwidthsfile and it's broken, complain on startup */ + if (options->V3BandwidthsFile && !old_options) { + dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL, NULL, + NULL); + } + /* same for guardfraction file */ + if (options->GuardfractionFile && !old_options) { + dirserv_read_guardfraction_file(options->GuardfractionFile, NULL); + } + + if (!options->DirPort_set) + REJECT("Running as authoritative directory, but no DirPort set."); + + if (!options->ORPort_set) + REJECT("Running as authoritative directory, but no ORPort set."); + + if (options->ClientOnly) + REJECT("Running as authoritative directory, but ClientOnly also set."); + + return 0; +} + +/** + * Legacy validation/normalization function for the dirauth schedule options + * in options. Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_dirauth_schedule(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (!authdir_mode_v3(options)) + return 0; + + if (options->V3AuthVoteDelay + options->V3AuthDistDelay >= + options->V3AuthVotingInterval/2) { + REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than half " + "V3AuthVotingInterval"); + } + + if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS) { + if (options->TestingTorNetwork) { + if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS_TESTING) { + REJECT("V3AuthVoteDelay is way too low."); + } else { + COMPLAIN("V3AuthVoteDelay is very low. " + "This may lead to failure to vote for a consensus."); + } + } else { + REJECT("V3AuthVoteDelay is way too low."); + } + } + + if (options->V3AuthDistDelay < MIN_DIST_SECONDS) { + if (options->TestingTorNetwork) { + if (options->V3AuthDistDelay < MIN_DIST_SECONDS_TESTING) { + REJECT("V3AuthDistDelay is way too low."); + } else { + COMPLAIN("V3AuthDistDelay is very low. " + "This may lead to missing votes in a consensus."); + } + } else { + REJECT("V3AuthDistDelay is way too low."); + } + } + + if (options->V3AuthNIntervalsValid < 2) + REJECT("V3AuthNIntervalsValid must be at least 2."); + + if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL) { + if (options->TestingTorNetwork) { + if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL_TESTING) { + /* Unreachable, covered by earlier checks */ + REJECT("V3AuthVotingInterval is insanely low."); /* LCOV_EXCL_LINE */ + } else { + COMPLAIN("V3AuthVotingInterval is very low. " + "This may lead to failure to synchronise for a consensus."); + } + } else { + REJECT("V3AuthVotingInterval is insanely low."); + } + } else if (options->V3AuthVotingInterval > 24*60*60) { + REJECT("V3AuthVotingInterval is insanely high."); + } else if (((24*60*60) % options->V3AuthVotingInterval) != 0) { + COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours."); + } + + return 0; +} + +/** + * Legacy validation/normalization function for the dirauth testing options + * in options. Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_dirauth_testing(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (!authdir_mode(options)) + return 0; + + if (!authdir_mode_v3(options)) + return 0; + + if (options->TestingV3AuthInitialVotingInterval + < MIN_VOTE_INTERVAL_TESTING_INITIAL) { + REJECT("TestingV3AuthInitialVotingInterval is insanely low."); + } else if (((30*60) % options->TestingV3AuthInitialVotingInterval) != 0) { + REJECT("TestingV3AuthInitialVotingInterval does not divide evenly into " + "30 minutes."); + } + + if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS_TESTING) { + REJECT("TestingV3AuthInitialVoteDelay is way too low."); + } + + if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS_TESTING) { + REJECT("TestingV3AuthInitialDistDelay is way too low."); + } + + if (options->TestingV3AuthInitialVoteDelay + + options->TestingV3AuthInitialDistDelay >= + options->TestingV3AuthInitialVotingInterval) { + REJECT("TestingV3AuthInitialVoteDelay plus TestingV3AuthInitialDistDelay " + "must be less than TestingV3AuthInitialVotingInterval"); + } + + if (options->TestingV3AuthVotingStartOffset > + MIN(options->TestingV3AuthInitialVotingInterval, + options->V3AuthVotingInterval)) { + REJECT("TestingV3AuthVotingStartOffset is higher than the voting " + "interval."); + } else if (options->TestingV3AuthVotingStartOffset < 0) { + REJECT("TestingV3AuthVotingStartOffset must be non-negative."); + } + + return 0; +} + +/** + * Return true if changing the configuration from <b>old</b> to <b>new</b> + * affects the timing of the voting subsystem + */ +static int +options_transition_affects_dirauth_timing(const or_options_t *old_options, + const or_options_t *new_options) +{ + tor_assert(old_options); + tor_assert(new_options); + + if (authdir_mode_v3(old_options) != authdir_mode_v3(new_options)) + return 1; + if (! authdir_mode_v3(new_options)) + return 0; + + YES_IF_CHANGED_INT(V3AuthVotingInterval); + YES_IF_CHANGED_INT(V3AuthVoteDelay); + YES_IF_CHANGED_INT(V3AuthDistDelay); + YES_IF_CHANGED_INT(TestingV3AuthInitialVotingInterval); + YES_IF_CHANGED_INT(TestingV3AuthInitialVoteDelay); + YES_IF_CHANGED_INT(TestingV3AuthInitialDistDelay); + YES_IF_CHANGED_INT(TestingV3AuthVotingStartOffset); + + return 0; +} + +/** Fetch the active option list, and take dirauth actions based on it. All of + * the things we do should survive being done repeatedly. If present, + * <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_dirauth(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + + /* We may need to reschedule some dirauth stuff if our status changed. */ + if (old_options) { + if (options_transition_affects_dirauth_timing(old_options, options)) { + voting_schedule_recalculate_timing(options, time(NULL)); + reschedule_dirvote(options); + } + } + + return 0; +} + +/** Fetch the active option list, and take dirauth mtbf actions based on it. + * All of the things we do should survive being done repeatedly. If present, + * <b>old_options</b> contains the previous value of the options. + * + * Must be called immediately after a successful or_state_load(). + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_dirauth_mtbf(const or_options_t *old_options) +{ + (void)old_options; + + const or_options_t *options = get_options(); + int running_tor = options->command == CMD_RUN_TOR; + + if (!authdir_mode(options)) + return 0; + + /* Load dirauth state */ + if (running_tor) { + rep_hist_load_mtbf_data(time(NULL)); + } + + return 0; +} + +/** Fetch the active option list, and take dirauth statistics actions based + * on it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Sets <b>*print_notice_out</b> if we enabled stats, and need to print + * a stats log using options_act_relay_stats_msg(). + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_dirauth_stats(const or_options_t *old_options, + bool *print_notice_out) +{ + if (BUG(!print_notice_out)) + return -1; + + const or_options_t *options = get_options(); + + if (authdir_mode_bridge(options)) { + time_t now = time(NULL); + int print_notice = 0; + + if (!old_options || !authdir_mode_bridge(old_options)) { + rep_hist_desc_stats_init(now); + print_notice = 1; + } + if (print_notice) + *print_notice_out = 1; + } + + /* If we used to have statistics enabled but we just disabled them, + stop gathering them. */ + if (old_options && authdir_mode_bridge(old_options) && + !authdir_mode_bridge(options)) + rep_hist_desc_stats_term(); + + return 0; +} + +/** + * Make any necessary modifications to a dirauth_options_t that occur + * before validation. On success return 0; on failure return -1 and + * set *<b>msg_out</b> to a newly allocated error string. + **/ +static int +dirauth_options_pre_normalize(void *arg, char **msg_out) +{ + dirauth_options_t *options = arg; + (void)msg_out; + + if (!options->RecommendedClientVersions) + options->RecommendedClientVersions = + config_lines_dup(options->RecommendedVersions); + if (!options->RecommendedServerVersions) + options->RecommendedServerVersions = + config_lines_dup(options->RecommendedVersions); + + if (config_ensure_bandwidth_cap(&options->AuthDirFastGuarantee, + "AuthDirFastGuarantee", msg_out) < 0) + return -1; + if (config_ensure_bandwidth_cap(&options->AuthDirGuardBWGuarantee, + "AuthDirGuardBWGuarantee", msg_out) < 0) + return -1; + + return 0; +} + +/** + * Check whether a dirauth_options_t is correct. + * + * On success return 0; on failure return -1 and set *<b>msg_out</b> to a + * newly allocated error string. + **/ +static int +dirauth_options_validate(const void *arg, char **msg) +{ + const dirauth_options_t *options = arg; + + if (options->VersioningAuthoritativeDirectory && + (!options->RecommendedClientVersions || + !options->RecommendedServerVersions)) { + REJECT("Versioning authoritative dir servers must set " + "Recommended*Versions."); + } + + char *t; + /* Call these functions to produce warnings only. */ + t = format_recommended_version_list(options->RecommendedClientVersions, 1); + tor_free(t); + t = format_recommended_version_list(options->RecommendedServerVersions, 1); + tor_free(t); + + if (options->TestingAuthDirTimeToLearnReachability > 2*60*60) { + COMPLAIN("TestingAuthDirTimeToLearnReachability is insanely high."); + } + + return 0; +} + +/* Declare the options field table for dirauth_options */ +#define CONF_CONTEXT TABLE +#include "feature/dirauth/dirauth_options.inc" +#undef CONF_CONTEXT + +/** Magic number for dirauth_options_t. */ +#define DIRAUTH_OPTIONS_MAGIC 0x41757448 + +/** + * Declare the configuration options for the dirauth module. + **/ +const config_format_t dirauth_options_fmt = { + .size = sizeof(dirauth_options_t), + .magic = { "dirauth_options_t", + DIRAUTH_OPTIONS_MAGIC, + offsetof(dirauth_options_t, magic) }, + .vars = dirauth_options_t_vars, + + .pre_normalize_fn = dirauth_options_pre_normalize, + .validate_fn = dirauth_options_validate +}; diff --git a/src/feature/dirauth/dirauth_config.h b/src/feature/dirauth/dirauth_config.h new file mode 100644 index 0000000000..9042ff8779 --- /dev/null +++ b/src/feature/dirauth/dirauth_config.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_config.h + * @brief Header for feature/dirauth/dirauth_config.c + **/ + +#ifndef TOR_FEATURE_DIRAUTH_DIRAUTH_CONFIG_H +#define TOR_FEATURE_DIRAUTH_DIRAUTH_CONFIG_H + +struct or_options_t; + +#ifdef HAVE_MODULE_DIRAUTH + +#include "lib/cc/torint.h" + +int options_validate_dirauth_mode(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_dirauth_schedule(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_dirauth_testing(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_act_dirauth(const struct or_options_t *old_options); +int options_act_dirauth_mtbf(const struct or_options_t *old_options); +int options_act_dirauth_stats(const struct or_options_t *old_options, + bool *print_notice_out); + +bool dirauth_should_reject_requests_under_load(void); + +extern const struct config_format_t dirauth_options_fmt; + +#else /* !defined(HAVE_MODULE_DIRAUTH) */ + +/** When tor is compiled with the dirauth module disabled, it can't be + * configured as a directory authority. + * + * Returns -1 and sets msg to a newly allocated string, if AuthoritativeDir + * is set in options. Otherwise returns 0. */ +static inline int +options_validate_dirauth_mode(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg) +{ + (void)old_options; + + /* Only check the primary option for now, #29211 will disable more + * options. */ + if (options->AuthoritativeDir) { + /* REJECT() this configuration */ + *msg = tor_strdup("This tor was built with dirauth mode disabled. " + "It can not be configured with AuthoritativeDir 1."); + return -1; + } + + return 0; +} + +#define options_validate_dirauth_schedule(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_dirauth_testing(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) + +#define options_act_dirauth(old_options) \ + (((void)(old_options)),0) +#define options_act_dirauth_mtbf(old_options) \ + (((void)(old_options)),0) + +static inline int +options_act_dirauth_stats(const struct or_options_t *old_options, + bool *print_notice_out) +{ + (void)old_options; + *print_notice_out = 0; + return 0; +} + +#define dirauth_should_reject_requests_under_load() (false) + +#endif /* defined(HAVE_MODULE_DIRAUTH) */ + +#endif /* !defined(TOR_FEATURE_DIRAUTH_DIRAUTH_CONFIG_H) */ diff --git a/src/feature/dirauth/dirauth_options.inc b/src/feature/dirauth/dirauth_options.inc new file mode 100644 index 0000000000..21f4996c39 --- /dev/null +++ b/src/feature/dirauth/dirauth_options.inc @@ -0,0 +1,105 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_options.inc + * @brief Declare configuration options for the crypto_ops module. + **/ + +/** Holds configuration about our directory authority options. */ +BEGIN_CONF_STRUCT(dirauth_options_t) + +/** If non-zero, always vote the Fast flag for any relay advertising + * this amount of capacity or more. */ +CONF_VAR(AuthDirFastGuarantee, MEMUNIT, 0, "100 KB") + +/** If non-zero, this advertised capacity or more is always sufficient + * to satisfy the bandwidth requirement for the Guard flag. */ +CONF_VAR(AuthDirGuardBWGuarantee, MEMUNIT, 0, "2 MB") + +/** Boolean: are we on IPv6? */ +CONF_VAR(AuthDirHasIPv6Connectivity, BOOL, 0, "0") + +/** True iff we should list bad exits, and vote for all other exits as + * good. */ +CONF_VAR(AuthDirListBadExits, BOOL, 0, "0") + +/** Do not permit more than this number of servers per IP address. */ +CONF_VAR(AuthDirMaxServersPerAddr, POSINT, 0, "2") + +/** Boolean: Do we enforce key-pinning? */ +CONF_VAR(AuthDirPinKeys, BOOL, 0, "1") + +/** Bool (default: 1): Switch for the shared random protocol. Only + * relevant to a directory authority. If off, the authority won't + * participate in the protocol. If on (default), a flag is added to the + * vote indicating participation. */ +CONF_VAR(AuthDirSharedRandomness, BOOL, 0, "1") + +/** Bool (default: 1): When testing routerinfos as a directory authority, + * do we enforce Ed25519 identity match? */ +/* NOTE: remove this option someday. */ +CONF_VAR(AuthDirTestEd25519LinkKeys, BOOL, 0, "1") + +/** Authority only: key=value pairs that we add to our networkstatus + * consensus vote on the 'params' line. */ +CONF_VAR(ConsensusParams, STRING, 0, NULL) + +/** Authority only: minimum number of measured bandwidths we must see + * before we only believe measured bandwidths to assign flags. */ +CONF_VAR(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, 0, "500") + +/** As directory authority, accept hidden service directories after what + * time? */ +CONF_VAR(MinUptimeHidServDirectoryV2, INTERVAL, 0, "96 hours") + +/** Which versions of tor should we tell users to run? */ +CONF_VAR(RecommendedVersions, LINELIST, 0, NULL) + +/** Which versions of tor should we tell users to run on clients? */ +CONF_VAR(RecommendedClientVersions, LINELIST, 0, NULL) + +/** Which versions of tor should we tell users to run on relays? */ +CONF_VAR(RecommendedServerVersions, LINELIST, 0, NULL) + +/** If an authority has been around for less than this amount of time, it + * does not believe its reachability information is accurate. Only + * altered on testing networks. */ +CONF_VAR(TestingAuthDirTimeToLearnReachability, INTERVAL, 0, "30 minutes") + + /** Relays in a testing network which should be voted Exit + * regardless of exit policy. */ +CONF_VAR(TestingDirAuthVoteExit, ROUTERSET, 0, NULL) +CONF_VAR(TestingDirAuthVoteExitIsStrict, BOOL, 0, "0") + +/** Relays in a testing network which should be voted Guard + * regardless of uptime and bandwidth. */ +CONF_VAR(TestingDirAuthVoteGuard, ROUTERSET, 0, NULL) +CONF_VAR(TestingDirAuthVoteGuardIsStrict, BOOL, 0, "0") + +/** Relays in a testing network which should be voted HSDir + * regardless of uptime and DirPort. */ +CONF_VAR(TestingDirAuthVoteHSDir, ROUTERSET, 0, NULL) +CONF_VAR(TestingDirAuthVoteHSDirIsStrict, BOOL, 0, "0") + +/** Minimum value for the Exit flag threshold on testing networks. */ +CONF_VAR(TestingMinExitFlagThreshold, MEMUNIT, 0, "0") + +/** Minimum value for the Fast flag threshold on testing networks. */ +CONF_VAR(TestingMinFastFlagThreshold, MEMUNIT, 0, "0") + +/** Boolean: is this an authoritative directory that's willing to recommend + * versions? */ +CONF_VAR(VersioningAuthoritativeDirectory, BOOL, 0, "0") + +/** Boolean: Under bandwidth pressure, if set to 1, the authority will always + * answer directory requests from relays but will start sending 503 error code + * for the other connections. If set to 0, all connections are considered the + * same and the authority will try to answer them all regardless of bandwidth + * pressure or not. */ +CONF_VAR(AuthDirRejectRequestsUnderLoad, BOOL, 0, "1") + +END_CONF_STRUCT(dirauth_options_t) diff --git a/src/feature/dirauth/dirauth_options_st.h b/src/feature/dirauth/dirauth_options_st.h new file mode 100644 index 0000000000..02a498c054 --- /dev/null +++ b/src/feature/dirauth/dirauth_options_st.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_options_st.h + * @brief Structure dirauth_options_t to hold directory authority options. + **/ + +#ifndef TOR_FEATURE_DIRAUTH_DIRAUTH_OPTIONS_ST_H +#define TOR_FEATURE_DIRAUTH_DIRAUTH_OPTIONS_ST_H + +#include "lib/conf/confdecl.h" +#include "feature/nodelist/routerset.h" + +#define CONF_CONTEXT STRUCT +#include "feature/dirauth/dirauth_options.inc" +#undef CONF_CONTEXT + +typedef struct dirauth_options_t dirauth_options_t; + +#endif /* !defined(TOR_FEATURE_DIRAUTH_DIRAUTH_OPTIONS_ST_H) */ diff --git a/src/feature/dirauth/dirauth_periodic.c b/src/feature/dirauth/dirauth_periodic.c new file mode 100644 index 0000000000..19e51c5a05 --- /dev/null +++ b/src/feature/dirauth/dirauth_periodic.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_periodic.c + * @brief Peridoic events for directory authorities. + **/ + +#include "core/or/or.h" + +#include "app/config/or_options_st.h" +#include "core/mainloop/netstatus.h" +#include "feature/dirauth/reachability.h" +#include "feature/stats/rephist.h" + +#include "feature/dirauth/bridgeauth.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/dirauth_periodic.h" +#include "feature/dirauth/authmode.h" + +#include "core/mainloop/periodic.h" + +#ifndef COCCI +#define DECLARE_EVENT(name, roles, flags) \ + static periodic_event_item_t name ## _event = \ + PERIODIC_EVENT(name, \ + PERIODIC_EVENT_ROLE_##roles, \ + flags) +#endif /* !defined(COCCI) */ + +#define FL(name) (PERIODIC_EVENT_FLAG_##name) + +/** + * Periodic callback: if we're an authority, check on our authority + * certificate (the one that authenticates our authority signing key). + */ +static int +check_authority_cert_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; + /* 1e. Periodically, if we're a v3 authority, we check whether our cert is + * close to expiring and warn the admin if it is. */ + v3_authority_check_key_expiry(); +#define CHECK_V3_CERTIFICATE_INTERVAL (5*60) + return CHECK_V3_CERTIFICATE_INTERVAL; +} + +DECLARE_EVENT(check_authority_cert, DIRAUTH, 0); + +/** + * Scheduled callback: Run directory-authority voting functionality. + * + * The schedule is a bit complicated here, so dirvote_act() manages the + * schedule itself. + **/ +static int +dirvote_callback(time_t now, const or_options_t *options) +{ + if (!authdir_mode_v3(options)) { + tor_assert_nonfatal_unreached(); + return 3600; + } + + time_t next = dirvote_act(options, now); + if (BUG(next == TIME_MAX)) { + /* This shouldn't be returned unless we called dirvote_act() without + * being an authority. If it happens, maybe our configuration will + * fix itself in an hour or so? */ + return 3600; + } + return safe_timer_diff(now, next); +} + +DECLARE_EVENT(dirvote, DIRAUTH, FL(NEED_NET)); + +/** Reschedule the directory-authority voting event. Run this whenever the + * schedule has changed. */ +void +reschedule_dirvote(const or_options_t *options) +{ + if (authdir_mode_v3(options)) { + periodic_event_reschedule(&dirvote_event); + } +} + +/** + * Periodic callback: if we're an authority, record our measured stability + * information from rephist in an mtbf file. + */ +static int +save_stability_callback(time_t now, const or_options_t *options) +{ + if (authdir_mode_tests_reachability(options)) { + if (rep_hist_record_mtbf_data(now, 1)<0) { + log_warn(LD_GENERAL, "Couldn't store mtbf data."); + } + } +#define SAVE_STABILITY_INTERVAL (30*60) + return SAVE_STABILITY_INTERVAL; +} + +DECLARE_EVENT(save_stability, AUTHORITIES, 0); + +/** + * Periodic callback: if we're an authority, make sure we test + * the routers on the network for reachability. + */ +static int +launch_reachability_tests_callback(time_t now, const or_options_t *options) +{ + if (authdir_mode_tests_reachability(options) && + !net_is_disabled()) { + /* try to determine reachability of the other Tor relays */ + dirserv_test_reachability(now); + } + return REACHABILITY_TEST_INTERVAL; +} + +DECLARE_EVENT(launch_reachability_tests, AUTHORITIES, FL(NEED_NET)); + +/** + * Periodic callback: if we're an authority, discount the stability + * information (and other rephist information) that's older. + */ +static int +downrate_stability_callback(time_t now, const or_options_t *options) +{ + (void)options; + /* 1d. Periodically, we discount older stability information so that new + * stability info counts more, and save the stability information to disk as + * appropriate. */ + time_t next = rep_hist_downrate_old_runs(now); + return safe_timer_diff(now, next); +} + +DECLARE_EVENT(downrate_stability, AUTHORITIES, 0); + +/** + * Periodic callback: if we're the bridge authority, write a networkstatus + * file to disk. + */ +static int +write_bridge_ns_callback(time_t now, const or_options_t *options) +{ + if (options->BridgeAuthoritativeDir) { + bridgeauth_dump_bridge_status_to_file(now); +#define BRIDGE_STATUSFILE_INTERVAL (30*60) + return BRIDGE_STATUSFILE_INTERVAL; + } + return PERIODIC_EVENT_NO_UPDATE; +} + +DECLARE_EVENT(write_bridge_ns, BRIDGEAUTH, 0); + +void +dirauth_register_periodic_events(void) +{ + periodic_events_register(&downrate_stability_event); + periodic_events_register(&launch_reachability_tests_event); + periodic_events_register(&save_stability_event); + periodic_events_register(&check_authority_cert_event); + periodic_events_register(&dirvote_event); + periodic_events_register(&write_bridge_ns_event); +} diff --git a/src/feature/dirauth/dirauth_periodic.h b/src/feature/dirauth/dirauth_periodic.h new file mode 100644 index 0000000000..ccdda92a77 --- /dev/null +++ b/src/feature/dirauth/dirauth_periodic.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_periodic.h + * @brief Header for dirauth_periodic.c + **/ + +#ifndef DIRVOTE_PERIODIC_H +#define DIRVOTE_PERIODIC_H + +#ifdef HAVE_MODULE_DIRAUTH + +void dirauth_register_periodic_events(void); +void reschedule_dirvote(const or_options_t *options); + +#else /* !defined(HAVE_MODULE_DIRAUTH) */ + +static inline void +reschedule_dirvote(const or_options_t *options) +{ + (void)options; +} + +#endif /* defined(HAVE_MODULE_DIRAUTH) */ + +#endif /* !defined(DIRVOTE_PERIODIC_H) */ diff --git a/src/feature/dirauth/dirauth_stub.c b/src/feature/dirauth/dirauth_stub.c new file mode 100644 index 0000000000..15a195b0fb --- /dev/null +++ b/src/feature/dirauth/dirauth_stub.c @@ -0,0 +1,33 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_stub.c + * @brief Stub declarations for use when dirauth module is disabled. + **/ + +#include "orconfig.h" +#include "feature/dirauth/dirauth_sys.h" +#include "lib/conf/conftypes.h" +#include "lib/conf/confdecl.h" +#include "lib/subsys/subsys.h" + +/* Declare the options field table for dirauth_options */ +#define CONF_CONTEXT STUB_TABLE +#include "feature/dirauth/dirauth_options.inc" +#undef CONF_CONTEXT + +static const config_format_t dirauth_options_stub_fmt = { + .vars = dirauth_options_t_vars, +}; + +const struct subsys_fns_t sys_dirauth = { + .name = "dirauth", + .supported = false, + .level = DIRAUTH_SUBSYS_LEVEL, + + .options_format = &dirauth_options_stub_fmt +}; diff --git a/src/feature/dirauth/dirauth_sys.c b/src/feature/dirauth/dirauth_sys.c new file mode 100644 index 0000000000..56ac501e16 --- /dev/null +++ b/src/feature/dirauth/dirauth_sys.c @@ -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-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_sys.c + * @brief Directory authority subsystem declarations + **/ + +#include "core/or/or.h" + +#define DIRAUTH_SYS_PRIVATE +#include "feature/dirauth/bwauth.h" +#include "feature/dirauth/dirauth_sys.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/dirauth_periodic.h" +#include "feature/dirauth/keypin.h" +#include "feature/dirauth/process_descs.h" +#include "feature/dirauth/dirauth_config.h" + +#include "feature/dirauth/dirauth_options_st.h" + +#include "lib/subsys/subsys.h" + +static const dirauth_options_t *global_dirauth_options; + +static int +subsys_dirauth_initialize(void) +{ + dirauth_register_periodic_events(); + return 0; +} + +static void +subsys_dirauth_shutdown(void) +{ + dirserv_free_fingerprint_list(); + dirvote_free_all(); + dirserv_clear_measured_bw_cache(); + keypin_close_journal(); + global_dirauth_options = NULL; +} + +const dirauth_options_t * +dirauth_get_options(void) +{ + tor_assert(global_dirauth_options); + return global_dirauth_options; +} + +STATIC int +dirauth_set_options(void *arg) +{ + dirauth_options_t *opts = arg; + global_dirauth_options = opts; + return 0; +} + +const struct subsys_fns_t sys_dirauth = { + .name = "dirauth", + .supported = true, + .level = DIRAUTH_SUBSYS_LEVEL, + .initialize = subsys_dirauth_initialize, + .shutdown = subsys_dirauth_shutdown, + + .options_format = &dirauth_options_fmt, + .set_options = dirauth_set_options, +}; diff --git a/src/feature/dirauth/dirauth_sys.h b/src/feature/dirauth/dirauth_sys.h new file mode 100644 index 0000000000..c512b91b33 --- /dev/null +++ b/src/feature/dirauth/dirauth_sys.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirauth_sys.h + * @brief Header for dirauth_sys.c + **/ + +#ifndef DIRAUTH_SYS_H +#define DIRAUTH_SYS_H + +struct dirauth_options_t; +const struct dirauth_options_t *dirauth_get_options(void); + +extern const struct subsys_fns_t sys_dirauth; + +/** + * Subsystem level for the directory-authority system. + * + * Defined here so that it can be shared between the real and stub + * definitions. + **/ +#define DIRAUTH_SUBSYS_LEVEL 70 + +#ifdef DIRAUTH_SYS_PRIVATE +STATIC int dirauth_set_options(void *arg); +#endif + +#endif /* !defined(DIRAUTH_SYS_H) */ diff --git a/src/feature/dirauth/dircollate.c b/src/feature/dirauth/dircollate.c index 7992e3a85f..b35cb021ff 100644 --- a/src/feature/dirauth/dircollate.c +++ b/src/feature/dirauth/dircollate.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,8 +32,8 @@ static void dircollator_collate_by_ed25519(dircollator_t *dc); /** Hashtable entry mapping a pair of digests (actually an ed25519 key and an * RSA SHA1 digest) to an array of vote_routerstatus_t. */ -typedef struct ddmap_entry_s { - HT_ENTRY(ddmap_entry_s) node; +typedef struct ddmap_entry_t { + HT_ENTRY(ddmap_entry_t) node; /** A SHA1-RSA1024 identity digest and Ed25519 identity key, * concatenated. (If there is no ed25519 identity key, there is no * entry in this table.) */ @@ -89,9 +89,9 @@ ddmap_entry_set_digests(ddmap_entry_t *ent, memcpy(ent->d + DIGEST_LEN, ed25519, DIGEST256_LEN); } -HT_PROTOTYPE(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash, +HT_PROTOTYPE(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash, ddmap_entry_eq) -HT_GENERATE2(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash, +HT_GENERATE2(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash, ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_) /** Helper: add a single vote_routerstatus_t <b>vrs</b> to the collator diff --git a/src/feature/dirauth/dircollate.h b/src/feature/dirauth/dircollate.h index 754a094817..90c6bddad5 100644 --- a/src/feature/dirauth/dircollate.h +++ b/src/feature/dirauth/dircollate.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,7 +15,7 @@ #include "lib/testsupport/testsupport.h" #include "core/or/or.h" -typedef struct dircollator_s dircollator_t; +typedef struct dircollator_t dircollator_t; dircollator_t *dircollator_new(int n_votes, int n_authorities); void dircollator_free_(dircollator_t *obj); @@ -30,11 +30,11 @@ vote_routerstatus_t **dircollator_get_votes_for_router(dircollator_t *dc, int idx); #ifdef DIRCOLLATE_PRIVATE -struct ddmap_entry_s; -typedef HT_HEAD(double_digest_map, ddmap_entry_s) double_digest_map_t; +struct ddmap_entry_t; +typedef HT_HEAD(double_digest_map, ddmap_entry_t) double_digest_map_t; /** A dircollator keeps track of all the routerstatus entries in a * set of networkstatus votes, and matches them by an appropriate rule. */ -struct dircollator_s { +struct dircollator_t { /** True iff we have run the collation algorithm. */ int is_collated; /** The total number of votes that we received. */ diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index af8b3dc207..e230815ca3 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRVOTE_PRIVATE @@ -28,6 +28,7 @@ #include "feature/nodelist/fmt_routerstatus.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodefamily.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/relay/router.h" @@ -40,10 +41,12 @@ #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/shared_random_state.h" +#include "feature/dirauth/dirauth_sys.h" #include "feature/nodelist/authority_cert_st.h" #include "feature/dircache/cached_dir_st.h" #include "feature/dirclient/dir_server_st.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/document_signature_st.h" #include "feature/nodelist/microdesc_st.h" #include "feature/nodelist/networkstatus_st.h" @@ -60,6 +63,9 @@ #include "lib/encoding/confline.h" #include "lib/crypt_ops/crypto_format.h" +/* Algorithm to use for the bandwidth file digest. */ +#define DIGEST_ALG_BW_FILE DIGEST_SHA256 + /** * \file dirvote.c * \brief Functions to compute directory consensus, and schedule voting. @@ -216,7 +222,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, networkstatus_t *v3_ns) { smartlist_t *chunks = smartlist_new(); - char *packages = NULL; char fingerprint[FINGERPRINT_LEN+1]; char digest[DIGEST_LEN]; uint32_t addr; @@ -242,19 +247,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, v3_ns->server_versions); protocols_lines = format_protocols_lines_for_vote(v3_ns); - if (v3_ns->package_lines) { - smartlist_t *tmp = smartlist_new(); - SMARTLIST_FOREACH(v3_ns->package_lines, const char *, p, - if (validate_recommended_package_line(p)) - smartlist_add_asprintf(tmp, "package %s\n", p)); - smartlist_sort_strings(tmp); - packages = smartlist_join_strings(tmp, "", 0, NULL); - SMARTLIST_FOREACH(tmp, char *, cp, tor_free(cp)); - smartlist_free(tmp); - } else { - packages = tor_strdup(""); - } - /* Get shared random commitments/reveals line(s). */ shared_random_vote_str = sr_get_string_for_vote(); @@ -268,6 +260,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, char *flag_thresholds = dirserv_get_flag_thresholds_line(); char *params; char *bw_headers_line = NULL; + char *bw_file_digest = NULL; authority_cert_t *cert = v3_ns->cert; char *methods = make_consensus_method_list(MIN_SUPPORTED_CONSENSUS_METHOD, @@ -307,6 +300,27 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, tor_free(bw_file_headers); } + /* Create bandwidth-file-digest if applicable. + * v3_ns->b64_digest_bw_file will contain the digest when V3BandwidthsFile + * is configured and the bandwidth file could be read, even if it was not + * parseable. + */ + if (!tor_digest256_is_zero((const char *)v3_ns->bw_file_digest256)) { + /* Encode the digest. */ + char b64_digest_bw_file[BASE64_DIGEST256_LEN+1] = {0}; + digest256_to_base64(b64_digest_bw_file, + (const char *)v3_ns->bw_file_digest256); + /* "bandwidth-file-digest" 1*(SP algorithm "=" digest) NL */ + char *digest_algo_b64_digest_bw_file = NULL; + tor_asprintf(&digest_algo_b64_digest_bw_file, "%s=%s", + crypto_digest_algorithm_get_name(DIGEST_ALG_BW_FILE), + b64_digest_bw_file); + /* No need for tor_strdup(""), format_line_if_present does it. */ + bw_file_digest = format_line_if_present( + "bandwidth-file-digest", digest_algo_b64_digest_bw_file); + tor_free(digest_algo_b64_digest_bw_file); + } + smartlist_add_asprintf(chunks, "network-status-version 3\n" "vote-status %s\n" @@ -318,11 +332,11 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, "voting-delay %d %d\n" "%s%s" /* versions */ "%s" /* protocols */ - "%s" /* packages */ "known-flags %s\n" "flag-thresholds %s\n" "params %s\n" "%s" /* bandwidth file headers */ + "%s" /* bandwidth file digest */ "dir-source %s %s %s %s %d %d\n" "contact %s\n" "%s" /* shared randomness information */ @@ -334,11 +348,11 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, client_versions_line, server_versions_line, protocols_lines, - packages, flags, flag_thresholds, params, bw_headers_line ? bw_headers_line : "", + bw_file_digest ? bw_file_digest: "", voter->nickname, fingerprint, voter->address, fmt_addr32(addr), voter->dir_port, voter->or_port, voter->contact, @@ -351,6 +365,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, tor_free(methods); tor_free(shared_random_vote_str); tor_free(bw_headers_line); + tor_free(bw_file_digest); if (!tor_digest_is_zero(voter->legacy_id_digest)) { char fpbuf[HEX_DIGEST_LEN+1]; @@ -369,7 +384,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, rsf = routerstatus_format_entry(&vrs->status, vrs->version, vrs->protocols, NS_V3_VOTE, - ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD, vrs); if (rsf) smartlist_add(chunks, rsf); @@ -412,7 +426,8 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, { networkstatus_t *v; - if (!(v = networkstatus_parse_vote_from_string(status, NULL, + if (!(v = networkstatus_parse_vote_from_string(status, strlen(status), + NULL, v3_ns->type))) { log_err(LD_BUG,"Generated a networkstatus %s we couldn't parse: " "<<%s>>", @@ -430,7 +445,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, tor_free(client_versions_line); tor_free(server_versions_line); tor_free(protocols_lines); - tor_free(packages); SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); smartlist_free(chunks); @@ -1525,14 +1539,11 @@ networkstatus_compute_consensus(smartlist_t *votes, consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD; } - if (consensus_method >= MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE) { + { /* It's smarter to initialize these weights to 1, so that later on, * we can't accidentally divide by zero. */ G = M = E = D = 1; T = 4; - } else { - /* ...but originally, they were set to zero. */ - G = M = E = D = T = 0; } /* Compute medians of time-related things, and figure out how many @@ -2233,7 +2244,7 @@ networkstatus_compute_consensus(smartlist_t *votes, /* Okay!! Now we can write the descriptor... */ /* First line goes into "buf". */ buf = routerstatus_format_entry(&rs_out, NULL, NULL, - rs_format, consensus_method, NULL); + rs_format, NULL); if (buf) smartlist_add(chunks, buf); } @@ -2253,8 +2264,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add_strdup(chunks, chosen_version); } smartlist_add_strdup(chunks, "\n"); - if (chosen_protocol_list && - consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) { + if (chosen_protocol_list) { smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list); } /* Now the weight line. */ @@ -2409,7 +2419,8 @@ networkstatus_compute_consensus(smartlist_t *votes, { networkstatus_t *c; - if (!(c = networkstatus_parse_vote_from_string(result, NULL, + if (!(c = networkstatus_parse_vote_from_string(result, strlen(result), + NULL, NS_TYPE_CONSENSUS))) { log_err(LD_BUG, "Generated a networkstatus consensus we couldn't " "parse."); @@ -2516,9 +2527,12 @@ compute_consensus_package_lines(smartlist_t *votes) * any new signatures in <b>src_voter_list</b> that should be added to * <b>target</b>. (A signature should be added if we have no signature for that * voter in <b>target</b> yet, or if we have no verifiable signature and the - * new signature is verifiable.) Return the number of signatures added or - * changed, or -1 if the document signed by <b>sigs</b> isn't the same - * document as <b>target</b>. */ + * new signature is verifiable.) + * + * Return the number of signatures added or changed, or -1 if the document + * signatures are invalid. Sets *<b>msg_out</b> to a string constant + * describing the signature status. + */ STATIC int networkstatus_add_detached_signatures(networkstatus_t *target, ns_detached_signatures_t *sigs, @@ -2567,7 +2581,7 @@ networkstatus_add_detached_signatures(networkstatus_t *target, return -1; } for (alg = DIGEST_SHA1; alg < N_COMMON_DIGEST_ALGORITHMS; ++alg) { - if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) { + if (!fast_mem_is_zero(digests->d[alg], DIGEST256_LEN)) { if (fast_memeq(target->digests.d[alg], digests->d[alg], DIGEST256_LEN)) { ++n_matches; @@ -2763,7 +2777,7 @@ networkstatus_get_detached_signatures(smartlist_t *consensuses) char d[HEX_DIGEST256_LEN+1]; const char *alg_name = crypto_digest_algorithm_get_name(alg); - if (tor_mem_is_zero(ns->digests.d[alg], DIGEST256_LEN)) + if (fast_mem_is_zero(ns->digests.d[alg], DIGEST256_LEN)) continue; base16_encode(d, sizeof(d), ns->digests.d[alg], DIGEST256_LEN); smartlist_add_asprintf(elements, "additional-digest %s %s %s\n", @@ -3132,7 +3146,8 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) *msg_out = NULL; again: - vote = networkstatus_parse_vote_from_string(vote_body, &end_of_vote, + vote = networkstatus_parse_vote_from_string(vote_body, strlen(vote_body), + &end_of_vote, NS_TYPE_VOTE); if (!end_of_vote) end_of_vote = vote_body + strlen(vote_body); @@ -3390,7 +3405,9 @@ dirvote_compute_consensuses(void) flavor_name); continue; } - consensus = networkstatus_parse_vote_from_string(consensus_body, NULL, + consensus = networkstatus_parse_vote_from_string(consensus_body, + strlen(consensus_body), + NULL, NS_TYPE_CONSENSUS); if (!consensus) { log_warn(LD_DIR, "Couldn't parse %s consensus we generated!", @@ -3529,7 +3546,7 @@ dirvote_add_signatures_to_pending_consensus( * just in case we break detached signature processing at some point. */ { networkstatus_t *v = networkstatus_parse_vote_from_string( - pc->body, NULL, + pc->body, strlen(pc->body), NULL, NS_TYPE_CONSENSUS); tor_assert(v); networkstatus_vote_free(v); @@ -3550,6 +3567,14 @@ dirvote_add_signatures_to_pending_consensus( return r; } +/** Helper: we just got the <b>detached_signatures_body</b> sent to us as + * signatures on the currently pending consensus. Add them to the pending + * consensus (if we have one). + * + * Set *<b>msg</b> to a string constant describing the status, regardless of + * success or failure. + * + * Return negative on failure, nonnegative on success. */ static int dirvote_add_signatures_to_all_pending_consensuses( const char *detached_signatures_body, @@ -3612,7 +3637,12 @@ dirvote_add_signatures_to_all_pending_consensuses( /** Helper: we just got the <b>detached_signatures_body</b> sent to us as * signatures on the currently pending consensus. Add them to the pending * consensus (if we have one); otherwise queue them until we have a - * consensus. Return negative on failure, nonnegative on success. */ + * consensus. + * + * Set *<b>msg</b> to a string constant describing the status, regardless of + * success or failure. + * + * Return negative on failure, nonnegative on success. */ int dirvote_add_signatures(const char *detached_signatures_body, const char *source, @@ -3654,7 +3684,9 @@ dirvote_publish_consensus(void) continue; } - if (networkstatus_set_current_consensus(pending->body, name, 0, NULL)) + if (networkstatus_set_current_consensus(pending->body, + strlen(pending->body), + name, 0, NULL)) log_warn(LD_DIR, "Error publishing %s consensus", name); else log_notice(LD_DIR, "Published %s consensus", name); @@ -3784,15 +3816,16 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method) smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); } - /* We originally put a lines in the micrdescriptors, but then we worked out - * that we needed them in the microdesc consensus. See #20916. */ - if (consensus_method < MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC && - !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport) - smartlist_add_asprintf(chunks, "a %s\n", - fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); - - if (family) - smartlist_add_asprintf(chunks, "family %s\n", family); + if (family) { + if (consensus_method < MIN_METHOD_FOR_CANONICAL_FAMILIES_IN_MICRODESCS) { + smartlist_add_asprintf(chunks, "family %s\n", family); + } else { + const uint8_t *id = (const uint8_t *)ri->cache_info.identity_digest; + char *canonical_family = nodefamily_canonicalize(family, id, 0); + smartlist_add_asprintf(chunks, "family %s\n", canonical_family); + tor_free(canonical_family); + } + } if (summary && strcmp(summary, "reject 1-65535")) smartlist_add_asprintf(chunks, "p %s\n", summary); @@ -3869,8 +3902,7 @@ dirvote_format_microdesc_vote_line(char *out_buf, size_t out_buf_len, ","); tor_assert(microdesc_consensus_methods); - if (digest256_to_base64(d64, md->digest)<0) - goto out; + digest256_to_base64(d64, md->digest); if (tor_snprintf(out_buf, out_buf_len, "m %s sha256=%s\n", microdesc_consensus_methods, d64)<0) @@ -3889,8 +3921,10 @@ static const struct consensus_method_range_t { int low; int high; } microdesc_consensus_methods[] = { - {MIN_SUPPORTED_CONSENSUS_METHOD, MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC - 1}, - {MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC, MAX_SUPPORTED_CONSENSUS_METHOD}, + {MIN_SUPPORTED_CONSENSUS_METHOD, + MIN_METHOD_FOR_CANONICAL_FAMILIES_IN_MICRODESCS - 1}, + {MIN_METHOD_FOR_CANONICAL_FAMILIES_IN_MICRODESCS, + MAX_SUPPORTED_CONSENSUS_METHOD}, {-1, -1} }; @@ -4199,7 +4233,7 @@ compare_routerinfo_by_ip_and_bw_(const void **a, const void **b) static digestmap_t * get_possible_sybil_list(const smartlist_t *routers) { - const or_options_t *options = get_options(); + const dirauth_options_t *options = dirauth_get_options(); digestmap_t *omit_as_sybil; smartlist_t *routers_by_ip = smartlist_new(); uint32_t last_addr; @@ -4364,6 +4398,23 @@ clear_status_flags_on_sybil(routerstatus_t *rs) * forget to add it to this clause. */ } +/** Space-separated list of all the flags that we will always vote on. */ +const char DIRVOTE_UNIVERSAL_FLAGS[] = + "Authority " + "Exit " + "Fast " + "Guard " + "HSDir " + "Stable " + "StaleDesc " + "V2Dir " + "Valid"; +/** Space-separated list of all flags that we may or may not vote on, + * depending on our configuration. */ +const char DIRVOTE_OPTIONAL_FLAGS[] = + "BadExit " + "Running"; + /** Return a new networkstatus_t* containing our current opinion. (For v3 * authorities) */ networkstatus_t * @@ -4371,6 +4422,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, authority_cert_t *cert) { const or_options_t *options = get_options(); + const dirauth_options_t *d_options = dirauth_get_options(); networkstatus_t *v3_out = NULL; uint32_t addr; char *hostname = NULL, *client_versions = NULL, *server_versions = NULL; @@ -4378,7 +4430,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, smartlist_t *routers, *routerstatuses; char identity_digest[DIGEST_LEN]; char signing_key_digest[DIGEST_LEN]; - int listbadexits = options->AuthDirListBadExits; + const int listbadexits = d_options->AuthDirListBadExits; routerlist_t *rl = router_get_routerlist(); time_t now = time(NULL); time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; @@ -4388,6 +4440,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, const int vote_on_reachability = running_long_enough_to_decide_unreachable(); smartlist_t *microdescriptors = NULL; smartlist_t *bw_file_headers = NULL; + uint8_t bw_file_digest256[DIGEST256_LEN] = {0}; tor_assert(private_key); tor_assert(cert); @@ -4409,11 +4462,11 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, hostname = tor_dup_ip(addr); } - if (options->VersioningAuthoritativeDir) { + if (d_options->VersioningAuthoritativeDirectory) { client_versions = - format_recommended_version_list(options->RecommendedClientVersions, 0); + format_recommended_version_list(d_options->RecommendedClientVersions, 0); server_versions = - format_recommended_version_list(options->RecommendedServerVersions, 0); + format_recommended_version_list(d_options->RecommendedServerVersions, 0); } contact = get_options()->ContactInfo; @@ -4425,7 +4478,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, * set_routerstatus_from_routerinfo() see up-to-date bandwidth info. */ if (options->V3BandwidthsFile) { - dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL, NULL); + dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL, NULL, + NULL); } else { /* * No bandwidths file; clear the measured bandwidth cache in case we had @@ -4479,8 +4533,8 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); rs = &vrs->status; - set_routerstatus_from_routerinfo(rs, node, ri, now, - listbadexits); + dirauth_set_routerstatus_from_routerinfo(rs, node, ri, now, + listbadexits); if (ri->cache_info.signing_key_cert) { memcpy(vrs->ed25519_id, @@ -4530,7 +4584,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, /* Only set bw_file_headers when V3BandwidthsFile is configured */ bw_file_headers = smartlist_new(); dirserv_read_measured_bandwidths(options->V3BandwidthsFile, - routerstatuses, bw_file_headers); + routerstatuses, bw_file_headers, + bw_file_digest256); + } else { /* * No bandwidths file; clear the measured bandwidth cache in case we had @@ -4601,18 +4657,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, tor_assert_nonfatal(protover_all_supported( v3_out->recommended_client_protocols, NULL)); - v3_out->package_lines = smartlist_new(); - { - config_line_t *cl; - for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) { - if (validate_recommended_package_line(cl->value)) - smartlist_add_strdup(v3_out->package_lines, cl->value); - } - } - v3_out->known_flags = smartlist_new(); smartlist_split_string(v3_out->known_flags, - "Authority Exit Fast Guard Stable V2Dir Valid HSDir", + DIRVOTE_UNIVERSAL_FLAGS, 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); if (vote_on_reachability) smartlist_add_strdup(v3_out->known_flags, "Running"); @@ -4620,13 +4667,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, smartlist_add_strdup(v3_out->known_flags, "BadExit"); smartlist_sort_strings(v3_out->known_flags); - if (options->ConsensusParams) { + if (d_options->ConsensusParams) { v3_out->net_params = smartlist_new(); smartlist_split_string(v3_out->net_params, - options->ConsensusParams, NULL, 0, 0); + d_options->ConsensusParams, NULL, 0, 0); smartlist_sort_strings(v3_out->net_params); } v3_out->bw_file_headers = bw_file_headers; + memcpy(v3_out->bw_file_digest256, bw_file_digest256, DIGEST256_LEN); voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); voter->nickname = tor_strdup(options->Nickname); diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h index 02d88d19d1..675f4ee148 100644 --- a/src/feature/dirauth/dirvote.h +++ b/src/feature/dirauth/dirvote.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -49,35 +49,17 @@ #define MIN_VOTE_INTERVAL_TESTING_INITIAL \ ((MIN_VOTE_SECONDS_TESTING)+(MIN_DIST_SECONDS_TESTING)+1) -/* A placeholder for routerstatus_format_entry() when the consensus method - * argument is not applicable. */ -#define ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD 0 - /** The lowest consensus method that we currently support. */ -#define MIN_SUPPORTED_CONSENSUS_METHOD 25 +#define MIN_SUPPORTED_CONSENSUS_METHOD 28 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 28 - -/** Lowest consensus method where authorities vote on required/recommended - * protocols. */ -#define MIN_METHOD_FOR_RECOMMENDED_PROTOCOLS 25 - -/** Lowest consensus method where authorities add protocols to routerstatus - * entries. */ -#define MIN_METHOD_FOR_RS_PROTOCOLS 25 - -/** Lowest consensus method where authorities initialize bandwidth weights to 1 - * instead of 0. See #14881 */ -#define MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE 26 - -/** Lowest consensus method where the microdesc consensus contains relay IPv6 - * addresses. See #23826 and #20916. */ -#define MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS 27 +#define MAX_SUPPORTED_CONSENSUS_METHOD 29 -/** Lowest consensus method where microdescriptors do not contain relay IPv6 - * addresses. See #23828 and #20916. */ -#define MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC 28 +/** + * Lowest consensus method where microdescriptor lines are put in canonical + * form for improved compressibility and ease of storage. See proposal 298. + **/ +#define MIN_METHOD_FOR_CANONICAL_FAMILIES_IN_MICRODESCS 29 /** Default bandwidth to clip unmeasured bandwidths to using method >= * MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not @@ -92,6 +74,9 @@ /** Maximum size of a line in a vote. */ #define MAX_BW_FILE_HEADERS_LINE_LEN 1024 +extern const char DIRVOTE_UNIVERSAL_FLAGS[]; +extern const char DIRVOTE_OPTIONAL_FLAGS[]; + /* * Public API. Used outside of the dirauth subsystem. * @@ -119,7 +104,7 @@ struct config_line_t; char *format_recommended_version_list(const struct config_line_t *line, int warn); -#else /* HAVE_MODULE_DIRAUTH */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline time_t dirvote_act(const or_options_t *options, time_t now) @@ -177,14 +162,14 @@ dirvote_add_signatures(const char *detached_signatures_body, { (void) detached_signatures_body; (void) source; - (void) msg_out; + *msg_out = "No directory authority support"; /* If the dirauth module is disabled, this should NEVER be called else we * failed to safeguard the dirauth module. */ tor_assert_nonfatal_unreached(); return 0; } -#endif /* HAVE_MODULE_DIRAUTH */ +#endif /* defined(HAVE_MODULE_DIRAUTH) */ /* Item access */ MOCK_DECL(const char*, dirvote_get_pending_consensus, diff --git a/src/feature/dirauth/dsigs_parse.c b/src/feature/dirauth/dsigs_parse.c index d88176fee9..d0bb931814 100644 --- a/src/feature/dirauth/dsigs_parse.c +++ b/src/feature/dirauth/dsigs_parse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -127,7 +127,7 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos) } digests = detached_get_digests(sigs, flavor); tor_assert(digests); - if (!tor_mem_is_zero(digests->d[alg], digest_length)) { + if (!fast_mem_is_zero(digests->d[alg], digest_length)) { log_warn(LD_DIR, "Multiple digests for %s with %s on detached " "signatures document", flavor, algname); continue; diff --git a/src/feature/dirauth/dsigs_parse.h b/src/feature/dirauth/dsigs_parse.h index fec51ba488..b25e3e0b28 100644 --- a/src/feature/dirauth/dsigs_parse.h +++ b/src/feature/dirauth/dsigs_parse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,4 +19,4 @@ void ns_detached_signatures_free_(ns_detached_signatures_t *s); #define ns_detached_signatures_free(s) \ FREE_AND_NULL(ns_detached_signatures_t, ns_detached_signatures_free_, (s)) -#endif +#endif /* !defined(TOR_DSIGS_PARSE_H) */ diff --git a/src/feature/dirauth/feature_dirauth.md b/src/feature/dirauth/feature_dirauth.md new file mode 100644 index 0000000000..b152b94894 --- /dev/null +++ b/src/feature/dirauth/feature_dirauth.md @@ -0,0 +1,9 @@ +@dir /feature/dirauth +@brief feature/dirauth: Directory authority implementation. + +This module handles running Tor as a directory authority. + +The directory protocol is specified in +[dir-spec.txt](https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt). + + diff --git a/src/feature/dirauth/guardfraction.c b/src/feature/dirauth/guardfraction.c index d1a7f194d4..40189ce494 100644 --- a/src/feature/dirauth/guardfraction.c +++ b/src/feature/dirauth/guardfraction.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirauth/guardfraction.h b/src/feature/dirauth/guardfraction.h index 72404907a4..c10fd9b7bb 100644 --- a/src/feature/dirauth/guardfraction.h +++ b/src/feature/dirauth/guardfraction.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,9 +16,9 @@ STATIC int dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, smartlist_t *vote_routerstatuses); -#endif /* defined(DIRSERV_PRIVATE) */ +#endif int dirserv_read_guardfraction_file(const char *fname, smartlist_t *vote_routerstatuses); -#endif +#endif /* !defined(TOR_GUARDFRACTION_H) */ diff --git a/src/feature/dirauth/include.am b/src/feature/dirauth/include.am new file mode 100644 index 0000000000..2ef629ae35 --- /dev/null +++ b/src/feature/dirauth/include.am @@ -0,0 +1,52 @@ + +# The Directory Authority module. + +# ADD_C_FILE: INSERT SOURCES HERE. +MODULE_DIRAUTH_SOURCES = \ + src/feature/dirauth/authmode.c \ + src/feature/dirauth/bridgeauth.c \ + src/feature/dirauth/bwauth.c \ + src/feature/dirauth/dirauth_config.c \ + src/feature/dirauth/dirauth_periodic.c \ + src/feature/dirauth/dirauth_sys.c \ + src/feature/dirauth/dircollate.c \ + src/feature/dirauth/dirvote.c \ + src/feature/dirauth/dsigs_parse.c \ + src/feature/dirauth/guardfraction.c \ + src/feature/dirauth/keypin.c \ + src/feature/dirauth/process_descs.c \ + src/feature/dirauth/reachability.c \ + src/feature/dirauth/recommend_pkg.c \ + src/feature/dirauth/shared_random.c \ + src/feature/dirauth/shared_random_state.c \ + src/feature/dirauth/voteflags.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/dirauth/authmode.h \ + src/feature/dirauth/bridgeauth.h \ + src/feature/dirauth/bwauth.h \ + src/feature/dirauth/dirauth_config.h \ + src/feature/dirauth/dirauth_options.inc \ + src/feature/dirauth/dirauth_options_st.h \ + src/feature/dirauth/dirauth_periodic.h \ + src/feature/dirauth/dirauth_sys.h \ + src/feature/dirauth/dircollate.h \ + src/feature/dirauth/dirvote.h \ + src/feature/dirauth/dsigs_parse.h \ + src/feature/dirauth/guardfraction.h \ + src/feature/dirauth/keypin.h \ + src/feature/dirauth/ns_detached_signatures_st.h \ + src/feature/dirauth/reachability.h \ + src/feature/dirauth/recommend_pkg.h \ + src/feature/dirauth/process_descs.h \ + src/feature/dirauth/shared_random.h \ + src/feature/dirauth/shared_random_state.h \ + src/feature/dirauth/vote_microdesc_hash_st.h \ + src/feature/dirauth/voteflags.h + +if BUILD_MODULE_DIRAUTH +LIBTOR_APP_A_SOURCES += $(MODULE_DIRAUTH_SOURCES) +else +LIBTOR_APP_A_STUB_SOURCES += src/feature/dirauth/dirauth_stub.c +endif diff --git a/src/feature/dirauth/keypin.c b/src/feature/dirauth/keypin.c index 06cb9ba1ff..edf5ba5833 100644 --- a/src/feature/dirauth/keypin.c +++ b/src/feature/dirauth/keypin.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,8 +15,6 @@ #include "lib/cc/torint.h" #include "lib/crypt_ops/crypto_digest.h" #include "lib/crypt_ops/crypto_format.h" -#include "lib/crypt_ops/crypto_format.h" -#include "lib/ctime/di_ops.h" #include "lib/ctime/di_ops.h" #include "lib/encoding/binascii.h" #include "lib/encoding/time_fmt.h" @@ -438,7 +436,7 @@ keypin_load_journal_impl(const char *data, size_t size) tor_log(severity, LD_DIRSERV, "Loaded %d entries from keypin journal. " "Found %d corrupt lines (ignored), %d duplicates (harmless), " - "and %d conflicts (resolved in favor or more recent entry).", + "and %d conflicts (resolved in favor of more recent entry).", n_entries, n_corrupt_lines, n_duplicates, n_conflicts); return 0; diff --git a/src/feature/dirauth/keypin.h b/src/feature/dirauth/keypin.h index 722b6ca5fc..881f010f0e 100644 --- a/src/feature/dirauth/keypin.h +++ b/src/feature/dirauth/keypin.h @@ -1,6 +1,11 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file keypin.h + * @brief Header for keypin.c + **/ + #ifndef TOR_KEYPIN_H #define TOR_KEYPIN_H @@ -11,10 +16,25 @@ int keypin_check_and_add(const uint8_t *rsa_id_digest, const int replace_existing_entry); int keypin_check(const uint8_t *rsa_id_digest, const uint8_t *ed25519_id_key); +int keypin_close_journal(void); +#ifdef HAVE_MODULE_DIRAUTH int keypin_open_journal(const char *fname); -int keypin_close_journal(void); int keypin_load_journal(const char *fname); +#else +static inline int +keypin_open_journal(const char *fname) +{ + (void)fname; + return 0; +} +static inline int +keypin_load_journal(const char *fname) +{ + (void)fname; + return 0; +} +#endif /* defined(HAVE_MODULE_DIRAUTH) */ void keypin_clear(void); int keypin_check_lone_rsa(const uint8_t *rsa_id_digest); @@ -25,6 +45,8 @@ int keypin_check_lone_rsa(const uint8_t *rsa_id_digest); #ifdef KEYPIN_PRIVATE +#include "ext/ht.h" + /** * In-memory representation of a key-pinning table entry. */ @@ -44,4 +66,3 @@ MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent)); #endif /* defined(KEYPIN_PRIVATE) */ #endif /* !defined(TOR_KEYPIN_H) */ - diff --git a/src/feature/dirauth/ns_detached_signatures_st.h b/src/feature/dirauth/ns_detached_signatures_st.h index 0f92be2f0d..f409431ec1 100644 --- a/src/feature/dirauth/ns_detached_signatures_st.h +++ b/src/feature/dirauth/ns_detached_signatures_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file ns_detached_signatures_st.h + * @brief Detached consensus signatures structure. + **/ + #ifndef NS_DETACHED_SIGNATURES_ST_H #define NS_DETACHED_SIGNATURES_ST_H @@ -18,5 +23,4 @@ struct ns_detached_signatures_t { * document_signature_t */ }; -#endif - +#endif /* !defined(NS_DETACHED_SIGNATURES_ST_H) */ diff --git a/src/feature/dirauth/process_descs.c b/src/feature/dirauth/process_descs.c index 21b8e239ec..5025d0ae39 100644 --- a/src/feature/dirauth/process_descs.c +++ b/src/feature/dirauth/process_descs.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,17 +12,21 @@ * them make those decisions. **/ +#define PROCESS_DESCS_PRIVATE + #include "core/or/or.h" #include "feature/dirauth/process_descs.h" #include "app/config/config.h" #include "core/or/policies.h" #include "core/or/versions.h" +#include "feature/dirauth/dirauth_sys.h" #include "feature/dirauth/keypin.h" #include "feature/dirauth/reachability.h" #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/describe.h" +#include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerinfo.h" @@ -32,46 +36,28 @@ #include "feature/relay/router.h" #include "core/or/tor_version_st.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/extrainfo_st.h" #include "feature/nodelist/node_st.h" +#include "feature/nodelist/microdesc_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" +#include "feature/nodelist/vote_routerstatus_st.h" #include "lib/encoding/confline.h" +#include "lib/crypt_ops/crypto_format.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; +dirserv_get_status_impl(const char *id_digest, + const ed25519_public_key_t *ed25519_public_key, + const char *nickname, uint32_t addr, uint16_t or_port, + const char *platform, const char **msg, int severity); /** Should be static; exposed for testing. */ static authdir_config_t *fingerprint_list = NULL; @@ -83,20 +69,39 @@ 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(); + list->status_by_digest256 = digest256map_new(); return list; } +#ifdef TOR_UNIT_TESTS + +/** Initialize fingerprint_list to a new authdir_config_t. Used for tests. */ +void +authdir_init_fingerprint_list(void) +{ + fingerprint_list = authdir_config_new(); +} + +/* Return the current fingerprint_list. Used for tests. */ +authdir_config_t * +authdir_return_fingerprint_list(void) +{ + return fingerprint_list; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + /** 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) +int +add_rsa_fingerprint_to_dir(const char *fp, authdir_config_t *list, + 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); @@ -107,24 +112,52 @@ add_fingerprint_to_dir(const char *fp, authdir_config_t *list, log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"", escaped(fp)); tor_free(fingerprint); - return; + return -1; } 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); } tor_free(fingerprint); *status |= add_status; - return; + return 0; +} + +/** Add the ed25519 key <b>edkey</b> to the smartlist of fingerprint_entry_t's + * <b>list</b>, or-ing the currently set status flags with <b>add_status</b>. + * Return -1 if we were unable to decode the key, else return 0. + */ +int +add_ed25519_to_dir(const ed25519_public_key_t *edkey, authdir_config_t *list, + rtr_flags_t add_status) +{ + rtr_flags_t *status; + + tor_assert(edkey); + tor_assert(list); + + if (ed25519_validate_pubkey(edkey) < 0) { + log_warn(LD_DIRSERV, "Invalid ed25519 key \"%s\"", ed25519_fmt(edkey)); + return -1; + } + + status = digest256map_get(list->status_by_digest256, edkey->pubkey); + if (!status) { + status = tor_malloc_zero(sizeof(rtr_flags_t)); + digest256map_set(list->status_by_digest256, edkey->pubkey, status); + } + + *status |= add_status; + return 0; } /** Add the fingerprint for this OR to the global list of recognized * identity key fingerprints. */ int -dirserv_add_own_fingerprint(crypto_pk_t *pk) +dirserv_add_own_fingerprint(crypto_pk_t *pk, const ed25519_public_key_t *edkey) { char fp[FINGERPRINT_LEN+1]; if (crypto_pk_get_fingerprint(pk, fp, 0)<0) { @@ -133,7 +166,14 @@ dirserv_add_own_fingerprint(crypto_pk_t *pk) } if (!fingerprint_list) fingerprint_list = authdir_config_new(); - add_fingerprint_to_dir(fp, fingerprint_list, 0); + if (add_rsa_fingerprint_to_dir(fp, fingerprint_list, 0) < 0) { + log_err(LD_BUG, "Error adding RSA fingerprint"); + return -1; + } + if (add_ed25519_to_dir(edkey, fingerprint_list, 0) < 0) { + log_err(LD_BUG, "Error adding ed25519 key"); + return -1; + } return 0; } @@ -174,27 +214,46 @@ dirserv_load_fingerprint_file(void) fingerprint_list_new = authdir_config_new(); 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 || - 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; - } + + /* Determine what we should do with the relay with the nickname field. */ 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; + } + + /* Check if fingerprint is RSA or ed25519 by verifying it. */ + int ed25519_not_ok = -1, rsa_not_ok = -1; + + /* Attempt to add the RSA key. */ + if (strlen(fingerprint) == HEX_DIGEST_LEN) { + rsa_not_ok = add_rsa_fingerprint_to_dir(fingerprint, + fingerprint_list_new, + add_status); + } + + /* Check ed25519 key. We check the size to prevent buffer overflows. + * If valid, attempt to add it, */ + ed25519_public_key_t ed25519_pubkey_tmp; + if (strlen(fingerprint) == BASE64_DIGEST256_LEN) { + if (!digest256_from_base64((char *) ed25519_pubkey_tmp.pubkey, + fingerprint)) { + ed25519_not_ok = add_ed25519_to_dir(&ed25519_pubkey_tmp, + fingerprint_list_new, add_status); + } + } + + /* If both keys are invalid (or missing), log and skip. */ + if (ed25519_not_ok && rsa_not_ok) { + log_warn(LD_CONFIG, "Invalid fingerprint (nickname '%s', " + "fingerprint %s). Skipping.", nickname, fingerprint); + continue; } - add_fingerprint_to_dir(fingerprint, fingerprint_list_new, add_status); } config_free_lines(front); @@ -216,30 +275,42 @@ dirserv_load_fingerprint_file(void) #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. +/** Check whether <b>router</b> has: + * - a nickname/identity key combination that we recognize from the fingerprint + * list, + * - an IP we automatically act on according to our configuration, + * - an appropriate version, and + * - matching pinned keys. + * + * 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. */ + * If the status is 'RTR_REJECT' and <b>msg</b> is provided, set + * *<b>msg</b> to a string constant explaining 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; + const int key_pinning = dirauth_get_options()->AuthDirPinKeys; + uint32_t r; + ed25519_public_key_t *signing_key = NULL; 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; + return RTR_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); + /* First, check for the more common reasons to reject a router. */ + if (router->cache_info.signing_key_cert) { + /* This has an ed25519 identity key. */ + signing_key = &router->cache_info.signing_key_cert->signing_key; + } + r = dirserv_get_status_impl(d, signing_key, router->nickname, router->addr, + router->or_port, router->platform, msg, + severity); + if (r) return r; @@ -254,7 +325,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) { @@ -270,7 +341,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 { @@ -287,7 +358,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) */ } @@ -299,31 +370,74 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, /** 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) +dirserv_would_reject_router(const routerstatus_t *rs, + const vote_routerstatus_t *vrs) { uint32_t res; + struct ed25519_public_key_t pk; + memcpy(&pk.pubkey, vrs->ed25519_id, ED25519_PUBKEY_LEN); - res = dirserv_get_status_impl(rs->identity_digest, rs->nickname, - rs->addr, rs->or_port, - NULL, NULL, LOG_DEBUG); + res = dirserv_get_status_impl(rs->identity_digest, &pk, rs->nickname, + rs->addr, rs->or_port, NULL, NULL, LOG_DEBUG); - return (res & FP_REJECT) != 0; + return (res & RTR_REJECT) != 0; +} + +/** + * Check whether the platform string in <b>platform</b> describes a platform + * that, as a directory authority, we want to reject. If it does, return + * true, and set *<b>msg</b> (if present) to a rejection message. Otherwise + * return false. + */ +STATIC bool +dirserv_rejects_tor_version(const char *platform, + const char **msg) +{ + if (!platform) + return false; + + static const char please_upgrade_string[] = + "Tor version is insecure or unsupported. Please upgrade!"; + + /* Versions before Tor 0.3.5 are unsupported. + * + * Also, reject unstable versions of 0.3.5, since (as of this writing) + * they are almost none of the network. */ + if (!tor_version_as_new_as(platform,"0.3.5.7")) { + if (msg) + *msg = please_upgrade_string; + return true; + } + + /* Series between Tor 0.3.6 and 0.4.1.4-rc inclusive are unsupported. + * Reject them. 0.3.6.0-alpha-dev only existed for a short time, before + * it was renamed to 0.4.0.0-alpha-dev. */ + if (tor_version_as_new_as(platform,"0.3.6.0-alpha-dev") && + !tor_version_as_new_as(platform,"0.4.1.5")) { + if (msg) { + *msg = please_upgrade_string; + } + return true; + } + + return false; } /** 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. + * (hex, no spaces), ed25519 key, 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, +dirserv_get_status_impl(const char *id_digest, + const ed25519_public_key_t *ed25519_public_key, + 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; + rtr_flags_t *status_by_digest; if (!fingerprint_list) fingerprint_list = authdir_config_new(); @@ -338,27 +452,13 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, if (msg) { *msg = "Malformed platform string."; } - return FP_REJECT; + return RTR_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; + /* Check whether the version is obsolete, broken, insecure, etc... */ + if (platform && dirserv_rejects_tor_version(platform, msg)) { + return RTR_REJECT; } status_by_digest = digestmap_get(fingerprint_list->status_by_digest, @@ -366,23 +466,30 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, if (status_by_digest) result |= *status_by_digest; - if (result & FP_REJECT) { + if (ed25519_public_key) { + status_by_digest = digest256map_get(fingerprint_list->status_by_digest256, + ed25519_public_key->pubkey); + if (status_by_digest) + result |= *status_by_digest; + } + + 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) { + *msg = "Fingerprint and/or ed25519 identity 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 RTR_REJECT; + } else if (result & RTR_INVALID) { if (msg) - *msg = "Fingerprint is marked invalid"; + *msg = "Fingerprint and/or ed25519 identity 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; + result |= RTR_BADEXIT; } if (!authdir_policy_permits_address(addr, or_port)) { @@ -393,13 +500,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; @@ -414,6 +521,7 @@ dirserv_free_fingerprint_list(void) strmap_free(fingerprint_list->fp_by_name, tor_free_); digestmap_free(fingerprint_list->status_by_digest, tor_free_); + digest256map_free(fingerprint_list->status_by_digest256, tor_free_); tor_free(fingerprint_list); } @@ -423,27 +531,40 @@ dirserv_free_fingerprint_list(void) /** 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 +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_null(&addr) || tor_addr_is_internal(&addr, 0)) { + log_info(LD_DIRSERV, + "Router %s published internal IPv4 address. Refusing.", + router_describe(ri)); + return -1; /* it's a private IP, we should reject it */ + } - if (tor_addr_is_internal(&addr, 0)) { + /* We only check internal v6 on non-null addresses because we do not require + * IPv6 and null IPv6 is normal. */ + if (!tor_addr_is_null(&ri->ipv6_addr) && + tor_addr_is_internal(&ri->ipv6_addr, 0)) { log_info(LD_DIRSERV, - "Router %s published internal IP address. Refusing.", + "Router %s published internal IPv6 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 the router is rejected, set *<b>msg</b> to a string constant explining + * why. * * If <b>complain</b> then explain at log-level 'notice' why we refused * a descriptor; else explain at log-level 'info'. @@ -457,7 +578,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? */ @@ -493,7 +614,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; } @@ -505,8 +626,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>. */ @@ -519,7 +640,8 @@ WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t 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, +dirserv_add_multiple_descriptors(const char *desc, size_t desclen, + uint8_t purpose, const char *source, const char **msg) { @@ -534,7 +656,12 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, int general = purpose == ROUTER_PURPOSE_GENERAL; tor_assert(msg); - r=ROUTER_ADDED_SUCCESSFULLY; /*Least severe return value. */ + r=ROUTER_ADDED_SUCCESSFULLY; /* Least severe return value. */ + + if (!string_is_utf8_no_bom(desc, desclen)) { + *msg = "descriptor(s) or extrainfo(s) not valid UTF-8 or had BOM."; + return ROUTER_AUTHDIR_REJECTS; + } format_iso_time(time_buf, now); if (tor_snprintf(annotation_buf, sizeof(annotation_buf), @@ -545,14 +672,12 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t 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; + return ROUTER_AUTHDIR_BUG_ANNOTATIONS; } s = desc; list = smartlist_new(); - if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 0, 0, + if (!router_parse_list_from_string(&s, s+desclen, list, SAVED_NOWHERE, 0, 0, annotation_buf, NULL)) { SMARTLIST_FOREACH(list, routerinfo_t *, ri, { msg_out = NULL; @@ -568,7 +693,7 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, smartlist_clear(list); s = desc; - if (!router_parse_list_from_string(&s, NULL, list, SAVED_NOWHERE, 1, 0, + if (!router_parse_list_from_string(&s, s+desclen, list, SAVED_NOWHERE, 1, 0, NULL, NULL)) { SMARTLIST_FOREACH(list, extrainfo_t *, ei, { msg_out = NULL; @@ -605,7 +730,8 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose, * 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. + * Return the status of the operation, and set *<b>msg</b> to a string + * constant describing the status. * * This function is only called when fresh descriptors are posted, not when * we re-load the cache. @@ -618,7 +744,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) 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; + const int key_pinning = dirauth_get_options()->AuthDirPinKeys; *msg = NULL; /* If it's too big, refuse it now. Otherwise we'll cache it all over the @@ -816,21 +942,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/dirauth/process_descs.h b/src/feature/dirauth/process_descs.h index ae2d6ad25d..1461ab697d 100644 --- a/src/feature/dirauth/process_descs.h +++ b/src/feature/dirauth/process_descs.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,27 +12,155 @@ #ifndef TOR_RECV_UPLOADS_H #define TOR_RECV_UPLOADS_H -int dirserv_load_fingerprint_file(void); +// for was_router_added_t. +#include "feature/nodelist/routerlist.h" + +#include "lib/crypt_ops/crypto_ed25519.h" + +struct authdir_config_t; + +/** Target of status_by_digest map. */ +typedef uint32_t rtr_flags_t; + +int add_rsa_fingerprint_to_dir(const char *fp, struct authdir_config_t *list, + rtr_flags_t add_status); + +int add_ed25519_to_dir(const ed25519_public_key_t *edkey, + struct authdir_config_t *list, + 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. */ + digest256map_t *status_by_digest256; /**< Map from digest256 to + * router_status_t. */ +} authdir_config_t; + +#if defined(PROCESS_DESCS_PRIVATE) || defined(TOR_UNIT_TESTS) + +/* 1 Historically used to indicate Named */ +#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 RTR_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */ +/* 32 Historically used to indicade Unnamed */ + +#endif /* defined(PROCESS_DESCS_PRIVATE) || defined(TOR_UNIT_TESTS) */ + +#ifdef TOR_UNIT_TESTS + +void authdir_init_fingerprint_list(void); + +authdir_config_t *authdir_return_fingerprint_list(void); + +#endif /* defined(TOR_UNIT_TESTS) */ + void dirserv_free_fingerprint_list(void); -int dirserv_add_own_fingerprint(crypto_pk_t *pk); +#ifdef HAVE_MODULE_DIRAUTH +int dirserv_load_fingerprint_file(void); enum was_router_added_t dirserv_add_multiple_descriptors( - const char *desc, uint8_t purpose, + const char *desc, size_t desclen, + uint8_t purpose, const char *source, const char **msg); enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source); +int dirserv_would_reject_router(const routerstatus_t *rs, + const vote_routerstatus_t *vrs); int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, int complain, int *valid_out); +int dirserv_add_own_fingerprint(crypto_pk_t *pk, + const ed25519_public_key_t *edkey); uint32_t dirserv_router_get_status(const routerinfo_t *router, const char **msg, int severity); void dirserv_set_node_flags_from_authoritative_status(node_t *node, uint32_t authstatus); +#else /* !defined(HAVE_MODULE_DIRAUTH) */ +static inline int +dirserv_load_fingerprint_file(void) +{ + return 0; +} +static inline enum was_router_added_t +dirserv_add_multiple_descriptors(const char *desc, size_t desclen, + uint8_t purpose, + const char *source, + const char **msg) +{ + (void)desc; + (void)desclen; + (void)purpose; + (void)source; + *msg = "No directory authority support"; + return (enum was_router_added_t)0; +} +static inline enum was_router_added_t +dirserv_add_descriptor(routerinfo_t *ri, + const char **msg, + const char *source) +{ + (void)ri; + (void)source; + *msg = "No directory authority support"; + return (enum was_router_added_t)0; +} +static inline int +dirserv_would_reject_router(const routerstatus_t *rs, + const vote_routerstatus_t *vrs) +{ + (void)rs; + (void)vrs; + return 0; +} +static inline int +authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, + int complain, + int *valid_out) +{ + (void)ri; + (void)complain; + *msg = "No directory authority support"; + *valid_out = 0; + return 0; +} +static inline int +dirserv_add_own_fingerprint(crypto_pk_t *pk, const ed25519_public_key_t *edkey) +{ + (void)pk; + (void)edkey; + return 0; +} +static inline uint32_t +dirserv_router_get_status(const routerinfo_t *router, + const char **msg, + int severity) +{ + (void)router; + (void)severity; + if (msg) + *msg = "No directory authority support"; + return 0; +} +static inline void +dirserv_set_node_flags_from_authoritative_status(node_t *node, + uint32_t authstatus) +{ + (void)node; + (void)authstatus; +} +#endif /* defined(HAVE_MODULE_DIRAUTH) */ -int dirserv_would_reject_router(const routerstatus_t *rs); +#ifdef TOR_UNIT_TESTS +STATIC int dirserv_router_has_valid_address(routerinfo_t *ri); +STATIC bool dirserv_rejects_tor_version(const char *platform, + const char **msg); +#endif /* defined(TOR_UNIT_TESTS) */ -#endif +#endif /* !defined(TOR_RECV_UPLOADS_H) */ diff --git a/src/feature/dirauth/reachability.c b/src/feature/dirauth/reachability.c index 883b692cbb..65fa27ed80 100644 --- a/src/feature/dirauth/reachability.c +++ b/src/feature/dirauth/reachability.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,6 +17,7 @@ #include "core/or/channeltls.h" #include "core/or/command.h" #include "feature/dirauth/authmode.h" +#include "feature/dirauth/dirauth_sys.h" #include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerinfo.h" @@ -24,6 +25,7 @@ #include "feature/nodelist/torcert.h" #include "feature/stats/rephist.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerlist_st.h" @@ -53,7 +55,7 @@ dirserv_orconn_tls_done(const tor_addr_t *addr, ri = node->ri; - if (get_options()->AuthDirTestEd25519LinkKeys && + if (dirauth_get_options()->AuthDirTestEd25519LinkKeys && node_supports_ed25519_link_authentication(node, 1) && ri->cache_info.signing_key_cert) { /* We allow the node to have an ed25519 key if we haven't been told one in @@ -125,7 +127,7 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri, void dirserv_single_reachability_test(time_t now, routerinfo_t *router) { - const or_options_t *options = get_options(); + const dirauth_options_t *dirauth_options = dirauth_get_options(); channel_t *chan = NULL; const node_t *node = NULL; tor_addr_t router_addr; @@ -136,7 +138,7 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router) node = node_get_by_id(router->cache_info.identity_digest); tor_assert(node); - if (options->AuthDirTestEd25519LinkKeys && + if (dirauth_options->AuthDirTestEd25519LinkKeys && node_supports_ed25519_link_authentication(node, 1) && router->cache_info.signing_key_cert) { ed_id_key = &router->cache_info.signing_key_cert->signing_key; @@ -154,7 +156,7 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router) if (chan) command_setup_channel(chan); /* Possible IPv6. */ - if (get_options()->AuthDirHasIPv6Connectivity == 1 && + if (dirauth_get_options()->AuthDirHasIPv6Connectivity == 1 && !tor_addr_is_null(&router->ipv6_addr)) { char addrstr[TOR_ADDR_BUF_LEN]; log_debug(LD_OR, "Testing reachability of %s at %s:%u.", diff --git a/src/feature/dirauth/reachability.h b/src/feature/dirauth/reachability.h index 5a938673ff..19448a67f3 100644 --- a/src/feature/dirauth/reachability.h +++ b/src/feature/dirauth/reachability.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -24,13 +24,27 @@ #define REACHABILITY_TEST_CYCLE_PERIOD \ (REACHABILITY_TEST_INTERVAL*REACHABILITY_MODULO_PER_TEST) +#ifdef HAVE_MODULE_DIRAUTH +void dirserv_single_reachability_test(time_t now, routerinfo_t *router); +void dirserv_test_reachability(time_t now); + +int dirserv_should_launch_reachability_test(const routerinfo_t *ri, + const routerinfo_t *ri_old); void dirserv_orconn_tls_done(const tor_addr_t *addr, uint16_t or_port, const char *digest_rcvd, const struct ed25519_public_key_t *ed_id_rcvd); -int dirserv_should_launch_reachability_test(const routerinfo_t *ri, - const routerinfo_t *ri_old); -void dirserv_single_reachability_test(time_t now, routerinfo_t *router); -void dirserv_test_reachability(time_t now); +#else /* !defined(HAVE_MODULE_DIRAUTH) */ +#define dirserv_single_reachability_test(now, router) \ + (((void)(now)),((void)(router))) +#define dirserv_test_reachability(now) \ + (((void)(now))) + +#define dirserv_should_launch_reachability_test(ri, ri_old) \ + (((void)(ri)),((void)(ri_old)),0) +#define dirserv_orconn_tls_done(addr, or_port, digest_rcvd, ed_id_rcvd) \ + (((void)(addr)),((void)(or_port)),((void)(digest_rcvd)), \ + ((void)(ed_id_rcvd))) +#endif /* defined(HAVE_MODULE_DIRAUTH) */ -#endif +#endif /* !defined(TOR_REACHABILITY_H) */ diff --git a/src/feature/dirauth/recommend_pkg.c b/src/feature/dirauth/recommend_pkg.c index 0456ff8463..84254566c6 100644 --- a/src/feature/dirauth/recommend_pkg.c +++ b/src/feature/dirauth/recommend_pkg.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirauth/recommend_pkg.h b/src/feature/dirauth/recommend_pkg.h index 8200d78f72..dcd9f8be8a 100644 --- a/src/feature/dirauth/recommend_pkg.h +++ b/src/feature/dirauth/recommend_pkg.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,6 +12,18 @@ #ifndef TOR_RECOMMEND_PKG_H #define TOR_RECOMMEND_PKG_H +#ifdef HAVE_MODULE_DIRAUTH int validate_recommended_package_line(const char *line); -#endif +#else + +static inline int +validate_recommended_package_line(const char *line) +{ + (void) line; + return 0; +} + +#endif /* defined(HAVE_MODULE_DIRAUTH) */ + +#endif /* !defined(TOR_RECOMMEND_PKG_H) */ diff --git a/src/feature/dirauth/shared_random.c b/src/feature/dirauth/shared_random.c index 34b2283250..48e2147ea6 100644 --- a/src/feature/dirauth/shared_random.c +++ b/src/feature/dirauth/shared_random.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -90,7 +90,7 @@ #include "core/or/or.h" #include "feature/dirauth/shared_random.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confmgt.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/nodelist/networkstatus.h" @@ -103,25 +103,27 @@ #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" +#include "feature/dirauth/dirauth_sys.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/authority_cert_st.h" #include "feature/nodelist/networkstatus_st.h" -/* String prefix of shared random values in votes/consensuses. */ +/** String prefix of shared random values in votes/consensuses. */ static const char previous_srv_str[] = "shared-rand-previous-value"; static const char current_srv_str[] = "shared-rand-current-value"; static const char commit_ns_str[] = "shared-rand-commit"; static const char sr_flag_ns_str[] = "shared-rand-participate"; -/* The value of the consensus param AuthDirNumSRVAgreements found in the +/** The value of the consensus param AuthDirNumSRVAgreements found in the * vote. This is set once the consensus creation subsystem requests the * SRV(s) that should be put in the consensus. We use this value to decide * if we keep or not an SRV. */ static int32_t num_srv_agreements_from_vote; -/* Return a heap allocated copy of the SRV <b>orig</b>. */ -STATIC sr_srv_t * -srv_dup(const sr_srv_t *orig) +/** Return a heap allocated copy of the SRV <b>orig</b>. */ +sr_srv_t * +sr_srv_dup(const sr_srv_t *orig) { sr_srv_t *duplicate = NULL; @@ -135,7 +137,7 @@ srv_dup(const sr_srv_t *orig) return duplicate; } -/* Allocate a new commit object and initializing it with <b>rsa_identity</b> +/** Allocate a new commit object and initializing it with <b>rsa_identity</b> * that MUST be provided. The digest algorithm is set to the default one * that is supported. The rest is uninitialized. This never returns NULL. */ static sr_commit_t * @@ -153,7 +155,7 @@ commit_new(const char *rsa_identity) return commit; } -/* Issue a log message describing <b>commit</b>. */ +/** Issue a log message describing <b>commit</b>. */ static void commit_log(const sr_commit_t *commit) { @@ -166,7 +168,7 @@ commit_log(const sr_commit_t *commit) commit->reveal_ts, safe_str(commit->encoded_reveal)); } -/* Make sure that the commitment and reveal information in <b>commit</b> +/** Make sure that the commitment and reveal information in <b>commit</b> * match. If they match return 0, return -1 otherwise. This function MUST be * used everytime we receive a new reveal value. Furthermore, the commit * object MUST have a reveal value and the hash of the reveal value. */ @@ -220,15 +222,15 @@ verify_commit_and_reveal(const sr_commit_t *commit) return -1; } -/* Return true iff the commit contains an encoded reveal value. */ +/** Return true iff the commit contains an encoded reveal value. */ STATIC int commit_has_reveal_value(const sr_commit_t *commit) { - return !tor_mem_is_zero(commit->encoded_reveal, + return !fast_mem_is_zero(commit->encoded_reveal, sizeof(commit->encoded_reveal)); } -/* Parse the encoded commit. The format is: +/** Parse the encoded commit. The format is: * base64-encode( TIMESTAMP || H(REVEAL) ) * * If successfully decoded and parsed, commit is updated and 0 is returned. @@ -283,7 +285,7 @@ commit_decode(const char *encoded, sr_commit_t *commit) return -1; } -/* Parse the b64 blob at <b>encoded</b> containing reveal information and +/** Parse the b64 blob at <b>encoded</b> containing reveal information and * store the information in-place in <b>commit</b>. Return 0 on success else * a negative value. */ STATIC int @@ -333,7 +335,7 @@ reveal_decode(const char *encoded, sr_commit_t *commit) return -1; } -/* Encode a reveal element using a given commit object to dst which is a +/** Encode a reveal element using a given commit object to dst which is a * buffer large enough to put the base64-encoded reveal construction. The * format is as follow: * REVEAL = base64-encode( TIMESTAMP || H(RN) ) @@ -362,7 +364,7 @@ reveal_encode(const sr_commit_t *commit, char *dst, size_t len) return ret; } -/* Encode the given commit object to dst which is a buffer large enough to +/** Encode the given commit object to dst which is a buffer large enough to * put the base64-encoded commit. The format is as follow: * COMMIT = base64-encode( TIMESTAMP || H(H(RN)) ) * Return base64 encoded length on success else a negative value. @@ -388,14 +390,14 @@ commit_encode(const sr_commit_t *commit, char *dst, size_t len) return base64_encode(dst, len, buf, sizeof(buf), 0); } -/* Cleanup both our global state and disk state. */ +/** Cleanup both our global state and disk state. */ static void sr_cleanup(void) { sr_state_free_all(); } -/* Using <b>commit</b>, return a newly allocated string containing the commit +/** Using <b>commit</b>, return a newly allocated string containing the commit * information that should be used during SRV calculation. It's the caller * responsibility to free the memory. Return NULL if this is not a commit to be * used for SRV calculation. */ @@ -414,7 +416,7 @@ get_srv_element_from_commit(const sr_commit_t *commit) return element; } -/* Return a srv object that is built with the construction: +/** Return a srv object that is built with the construction: * SRV = SHA3-256("shared-random" | INT_8(reveal_num) | * INT_4(version) | HASHED_REVEALS | previous_SRV) * This function cannot fail. */ @@ -456,7 +458,7 @@ generate_srv(const char *hashed_reveals, uint64_t reveal_num, return srv; } -/* Compare reveal values and return the result. This should exclusively be +/** Compare reveal values and return the result. This should exclusively be * used by smartlist_sort(). */ static int compare_reveal_(const void **_a, const void **_b) @@ -466,7 +468,7 @@ compare_reveal_(const void **_a, const void **_b) sizeof(a->hashed_reveal)); } -/* Given <b>commit</b> give the line that we should place in our votes. +/** Given <b>commit</b> give the line that we should place in our votes. * It's the responsibility of the caller to free the string. */ static char * get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) @@ -486,7 +488,7 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) { /* Send a reveal value for this commit if we have one. */ const char *reveal_str = commit->encoded_reveal; - if (tor_mem_is_zero(commit->encoded_reveal, + if (fast_mem_is_zero(commit->encoded_reveal, sizeof(commit->encoded_reveal))) { reveal_str = ""; } @@ -506,7 +508,7 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) return vote_line; } -/* Return a heap allocated string that contains the given <b>srv</b> string +/** Return a heap allocated string that contains the given <b>srv</b> string * representation formatted for a networkstatus document using the * <b>key</b> as the start of the line. This doesn't return NULL. */ static char * @@ -524,7 +526,7 @@ srv_to_ns_string(const sr_srv_t *srv, const char *key) return srv_str; } -/* Given the previous SRV and the current SRV, return a heap allocated +/** Given the previous SRV and the current SRV, return a heap allocated * string with their data that could be put in a vote or a consensus. Caller * must free the returned string. Return NULL if no SRVs were provided. */ static char * @@ -557,7 +559,7 @@ get_ns_str_from_sr_values(const sr_srv_t *prev_srv, const sr_srv_t *cur_srv) return srv_str; } -/* Return 1 iff the two commits have the same commitment values. This +/** Return 1 iff the two commits have the same commitment values. This * function does not care about reveal values. */ STATIC int commitments_are_the_same(const sr_commit_t *commit_one, @@ -572,7 +574,7 @@ commitments_are_the_same(const sr_commit_t *commit_one, return 1; } -/* We just received a commit from the vote of authority with +/** We just received a commit from the vote of authority with * <b>identity_digest</b>. Return 1 if this commit is authorititative that * is, it belongs to the authority that voted it. Else return 0 if not. */ STATIC int @@ -586,7 +588,7 @@ commit_is_authoritative(const sr_commit_t *commit, sizeof(commit->rsa_identity)); } -/* Decide if the newly received <b>commit</b> should be kept depending on +/** Decide if the newly received <b>commit</b> should be kept depending on * the current phase and state of the protocol. The <b>voter_key</b> is the * RSA identity key fingerprint of the authority's vote from which the * commit comes from. The <b>phase</b> is the phase we should be validating @@ -705,7 +707,7 @@ should_keep_commit(const sr_commit_t *commit, const char *voter_key, return 0; } -/* We are in reveal phase and we found a valid and verified <b>commit</b> in +/** We are in reveal phase and we found a valid and verified <b>commit</b> in * a vote that contains reveal values that we could use. Update the commit * we have in our state. Never call this with an unverified commit. */ STATIC void @@ -726,7 +728,7 @@ save_commit_during_reveal_phase(const sr_commit_t *commit) sr_state_copy_reveal_info(saved_commit, commit); } -/* Save <b>commit</b> to our persistent state. Depending on the current +/** Save <b>commit</b> to our persistent state. Depending on the current * phase, different actions are taken. Steals reference of <b>commit</b>. * The commit object MUST be valid and verified before adding it to the * state. */ @@ -751,7 +753,7 @@ save_commit_to_state(sr_commit_t *commit) } } -/* Return 1 if we should we keep an SRV voted by <b>n_agreements</b> auths. +/** Return 1 if we should we keep an SRV voted by <b>n_agreements</b> auths. * Return 0 if we should ignore it. */ static int should_keep_srv(int n_agreements) @@ -781,7 +783,7 @@ should_keep_srv(int n_agreements) return 1; } -/* Helper: compare two DIGEST256_LEN digests. */ +/** Helper: compare two DIGEST256_LEN digests. */ static int compare_srvs_(const void **_a, const void **_b) { @@ -789,7 +791,7 @@ compare_srvs_(const void **_a, const void **_b) return tor_memcmp(a->value, b->value, sizeof(a->value)); } -/* Return the most frequent member of the sorted list of DIGEST256_LEN +/** Return the most frequent member of the sorted list of DIGEST256_LEN * digests in <b>sl</b> with the count of that most frequent element. */ static sr_srv_t * smartlist_get_most_frequent_srv(const smartlist_t *sl, int *count_out) @@ -806,7 +808,7 @@ compare_srv_(const void **_a, const void **_b) sizeof(a->value)); } -/* Using a list of <b>votes</b>, return the SRV object from them that has +/** Using a list of <b>votes</b>, return the SRV object from them that has * been voted by the majority of dirauths. If <b>current</b> is set, we look * for the current SRV value else the previous one. The returned pointer is * an object located inside a vote. NULL is returned if no appropriate value @@ -868,7 +870,7 @@ get_majority_srv_from_votes(const smartlist_t *votes, int current) return the_srv; } -/* Free a commit object. */ +/** Free a commit object. */ void sr_commit_free_(sr_commit_t *commit) { @@ -880,7 +882,7 @@ sr_commit_free_(sr_commit_t *commit) tor_free(commit); } -/* Generate the commitment/reveal value for the protocol run starting at +/** Generate the commitment/reveal value for the protocol run starting at * <b>timestamp</b>. <b>my_rsa_cert</b> is our authority RSA certificate. */ sr_commit_t * sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) @@ -937,7 +939,8 @@ sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) return NULL; } -/* Compute the shared random value based on the active commits in our state. */ +/** Compute the shared random value based on the active commits in our + * state. */ void sr_compute_srv(void) { @@ -1010,7 +1013,7 @@ sr_compute_srv(void) tor_free(reveals); } -/* Parse a commit from a vote or from our disk state and return a newly +/** Parse a commit from a vote or from our disk state and return a newly * allocated commit object. NULL is returned on error. * * The commit's data is in <b>args</b> and the order matters very much: @@ -1082,7 +1085,7 @@ sr_parse_commit(const smartlist_t *args) return NULL; } -/* Called when we are done parsing a vote by <b>voter_key</b> that might +/** Called when we are done parsing a vote by <b>voter_key</b> that might * contain some useful <b>commits</b>. Find if any of them should be kept * and update our state accordingly. Once done, the list of commitments will * be empty. */ @@ -1120,7 +1123,7 @@ sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key) } SMARTLIST_FOREACH_END(commit); } -/* Return a heap-allocated string containing commits that should be put in +/** Return a heap-allocated string containing commits that should be put in * the votes. It's the responsibility of the caller to free the string. * This always return a valid string, either empty or with line(s). */ char * @@ -1129,7 +1132,7 @@ sr_get_string_for_vote(void) char *vote_str = NULL; digestmap_t *state_commits; smartlist_t *chunks = smartlist_new(); - const or_options_t *options = get_options(); + const dirauth_options_t *options = dirauth_get_options(); /* Are we participating in the protocol? */ if (!options->AuthDirSharedRandomness) { @@ -1178,7 +1181,7 @@ sr_get_string_for_vote(void) return vote_str; } -/* Return a heap-allocated string that should be put in the consensus and +/** Return a heap-allocated string that should be put in the consensus and * contains the shared randomness values. It's the responsibility of the * caller to free the string. NULL is returned if no SRV(s) available. * @@ -1194,7 +1197,7 @@ sr_get_string_for_consensus(const smartlist_t *votes, int32_t num_srv_agreements) { char *srv_str; - const or_options_t *options = get_options(); + const dirauth_options_t *options = dirauth_get_options(); tor_assert(votes); @@ -1222,7 +1225,7 @@ sr_get_string_for_consensus(const smartlist_t *votes, return NULL; } -/* We just computed a new <b>consensus</b>. Update our state with the SRVs +/** We just computed a new <b>consensus</b>. Update our state with the SRVs * from the consensus (might be NULL as well). Register the SRVs in our SR * state and prepare for the upcoming protocol round. */ void @@ -1253,15 +1256,15 @@ sr_act_post_consensus(const networkstatus_t *consensus) * decided by the majority. */ sr_state_unset_fresh_srv(); /* Set the SR values from the given consensus. */ - sr_state_set_previous_srv(srv_dup(consensus->sr_info.previous_srv)); - sr_state_set_current_srv(srv_dup(consensus->sr_info.current_srv)); + sr_state_set_previous_srv(sr_srv_dup(consensus->sr_info.previous_srv)); + sr_state_set_current_srv(sr_srv_dup(consensus->sr_info.current_srv)); } /* Prepare our state so that it's ready for the next voting period. */ sr_state_update(voting_schedule_get_next_valid_after_time()); } -/* Initialize shared random subsystem. This MUST be called early in the boot +/** Initialize shared random subsystem. This MUST be called early in the boot * process of tor. Return 0 on success else -1 on error. */ int sr_init(int save_to_disk) @@ -1269,7 +1272,7 @@ sr_init(int save_to_disk) return sr_state_init(save_to_disk, 1); } -/* Save our state to disk and cleanup everything. */ +/** Save our state to disk and cleanup everything. */ void sr_save_and_cleanup(void) { @@ -1279,7 +1282,7 @@ sr_save_and_cleanup(void) #ifdef TOR_UNIT_TESTS -/* Set the global value of number of SRV agreements so the test can play +/** Set the global value of number of SRV agreements so the test can play * along by calling specific functions that don't parse the votes prior for * the AuthDirNumSRVAgreements value. */ void diff --git a/src/feature/dirauth/shared_random.h b/src/feature/dirauth/shared_random.h index 25d95ebbc7..c4e259dcdb 100644 --- a/src/feature/dirauth/shared_random.h +++ b/src/feature/dirauth/shared_random.h @@ -1,86 +1,88 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_SHARED_RANDOM_H #define TOR_SHARED_RANDOM_H -/* - * This file contains ABI/API of the shared random protocol defined in +/** + * \file shared_random.h + * + * \brief This file contains ABI/API of the shared random protocol defined in * proposal #250. Every public functions and data structure are namespaced * with "sr_" which stands for shared random. */ #include "core/or/or.h" -/* Protocol version */ +/** Protocol version */ #define SR_PROTO_VERSION 1 -/* Default digest algorithm. */ +/** Default digest algorithm. */ #define SR_DIGEST_ALG DIGEST_SHA3_256 -/* Invariant token in the SRV calculation. */ +/** Invariant token in the SRV calculation. */ #define SR_SRV_TOKEN "shared-random" -/* Don't count the NUL terminated byte even though the TOKEN has it. */ +/** Don't count the NUL terminated byte even though the TOKEN has it. */ #define SR_SRV_TOKEN_LEN (sizeof(SR_SRV_TOKEN) - 1) -/* Length of the random number (in bytes). */ +/** Length of the random number (in bytes). */ #define SR_RANDOM_NUMBER_LEN 32 -/* Size of a decoded commit value in a vote or state. It's a hash and a +/** Size of a decoded commit value in a vote or state. It's a hash and a * timestamp. It adds up to 40 bytes. */ #define SR_COMMIT_LEN (sizeof(uint64_t) + DIGEST256_LEN) -/* Size of a decoded reveal value from a vote or state. It's a 64 bit +/** Size of a decoded reveal value from a vote or state. It's a 64 bit * timestamp and the hashed random number. This adds up to 40 bytes. */ #define SR_REVEAL_LEN (sizeof(uint64_t) + DIGEST256_LEN) -/* Size of SRV message length. The construction is has follow: +/** Size of SRV message length. The construction is has follow: * "shared-random" | INT_8(reveal_num) | INT_4(version) | PREV_SRV */ #define SR_SRV_MSG_LEN \ (SR_SRV_TOKEN_LEN + sizeof(uint64_t) + sizeof(uint32_t) + DIGEST256_LEN) -/* Length of base64 encoded commit NOT including the NUL terminated byte. +/** Length of base64 encoded commit NOT including the NUL terminated byte. * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ #define SR_COMMIT_BASE64_LEN (BASE64_LEN(SR_COMMIT_LEN)) -/* Length of base64 encoded reveal NOT including the NUL terminated byte. +/** Length of base64 encoded reveal NOT including the NUL terminated byte. * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ #define SR_REVEAL_BASE64_LEN (BASE64_LEN(SR_REVEAL_LEN)) -/* Length of base64 encoded shared random value. It's 32 bytes long so 44 +/** Length of base64 encoded shared random value. It's 32 bytes long so 44 * bytes from the base64_encode_size formula. That includes the '=' * character at the end. */ #define SR_SRV_VALUE_BASE64_LEN (BASE64_LEN(DIGEST256_LEN)) -/* Assert if commit valid flag is not set. */ +/** Assert if commit valid flag is not set. */ #define ASSERT_COMMIT_VALID(c) tor_assert((c)->valid) -/* Protocol phase. */ +/** Protocol phase. */ typedef enum { - /* Commitment phase */ + /** Commitment phase */ SR_PHASE_COMMIT = 1, - /* Reveal phase */ + /** Reveal phase */ SR_PHASE_REVEAL = 2, } sr_phase_t; -/* A shared random value (SRV). */ +/** A shared random value (SRV). */ typedef struct sr_srv_t { - /* The number of reveal values used to derive this SRV. */ + /** The number of reveal values used to derive this SRV. */ uint64_t num_reveals; - /* The actual value. This is the stored result of SHA3-256. */ + /** The actual value. This is the stored result of SHA3-256. */ uint8_t value[DIGEST256_LEN]; } sr_srv_t; -/* A commit (either ours or from another authority). */ +/** A commit (either ours or from another authority). */ typedef struct sr_commit_t { - /* Hashing algorithm used. */ + /** Hashing algorithm used. */ digest_algorithm_t alg; - /* Indicate if this commit has been verified thus valid. */ + /** Indicate if this commit has been verified thus valid. */ unsigned int valid:1; /* Commit owner info */ - /* The RSA identity key of the authority and its base16 representation, + /** The RSA identity key of the authority and its base16 representation, * which includes the NUL terminated byte. */ char rsa_identity[DIGEST_LEN]; char rsa_identity_hex[HEX_DIGEST_LEN + 1]; /* Commitment information */ - /* Timestamp of reveal. Correspond to TIMESTAMP. */ + /** Timestamp of reveal. Correspond to TIMESTAMP. */ uint64_t reveal_ts; /* H(REVEAL) as found in COMMIT message. */ char hashed_reveal[DIGEST256_LEN]; @@ -89,13 +91,13 @@ typedef struct sr_commit_t { /* Reveal information */ - /* H(RN) which is what we used as the random value for this commit. We + /** H(RN) which is what we used as the random value for this commit. We * don't use the raw bytes since those are sent on the network thus * avoiding possible information leaks of our PRNG. */ uint8_t random_number[SR_RANDOM_NUMBER_LEN]; - /* Timestamp of commit. Correspond to TIMESTAMP. */ + /** Timestamp of commit. Correspond to TIMESTAMP. */ uint64_t commit_ts; - /* This is the whole reveal message. We use it during verification */ + /** This is the whole reveal message. We use it during verification */ char encoded_reveal[SR_REVEAL_BASE64_LEN + 1]; } sr_commit_t; @@ -110,7 +112,7 @@ int sr_init(int save_to_disk); void sr_save_and_cleanup(void); void sr_act_post_consensus(const networkstatus_t *consensus); -#else /* HAVE_MODULE_DIRAUTH */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline int sr_init(int save_to_disk) @@ -131,7 +133,7 @@ sr_act_post_consensus(const networkstatus_t *consensus) (void) consensus; } -#endif /* HAVE_MODULE_DIRAUTH */ +#endif /* defined(HAVE_MODULE_DIRAUTH) */ /* Public methods used only by dirauth code. */ @@ -154,6 +156,7 @@ const char *sr_commit_get_rsa_fpr(const sr_commit_t *commit) void sr_compute_srv(void); sr_commit_t *sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert); +sr_srv_t *sr_srv_dup(const sr_srv_t *orig); #ifdef SHARED_RANDOM_PRIVATE @@ -172,7 +175,6 @@ STATIC sr_srv_t *get_majority_srv_from_votes(const smartlist_t *votes, int current); STATIC void save_commit_to_state(sr_commit_t *commit); -STATIC sr_srv_t *srv_dup(const sr_srv_t *orig); STATIC int commitments_are_the_same(const sr_commit_t *commit_one, const sr_commit_t *commit_two); STATIC int commit_is_authoritative(const sr_commit_t *commit, @@ -191,4 +193,3 @@ void set_num_srv_agreements(int32_t value); #endif /* TOR_UNIT_TESTS */ #endif /* !defined(TOR_SHARED_RANDOM_H) */ - diff --git a/src/feature/dirauth/shared_random_state.c b/src/feature/dirauth/shared_random_state.c index b3e4a4ef92..cfbfa4ec5b 100644 --- a/src/feature/dirauth/shared_random_state.c +++ b/src/feature/dirauth/shared_random_state.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,7 +12,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confmgt.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/dirauth/dirvote.h" #include "feature/nodelist/networkstatus.h" @@ -22,21 +22,22 @@ #include "feature/dirauth/shared_random_state.h" #include "feature/dircommon/voting_schedule.h" #include "lib/encoding/confline.h" +#include "lib/version/torversion.h" #include "app/config/or_state_st.h" -/* Default filename of the shared random state on disk. */ +/** Default filename of the shared random state on disk. */ static const char default_fname[] = "sr-state"; -/* String representation of a protocol phase. */ +/** String representation of a protocol phase. */ static const char *phase_str[] = { "unknown", "commit", "reveal" }; -/* Our shared random protocol state. There is only one possible state per +/** Our shared random protocol state. There is only one possible state per * protocol run so this is the global state which is reset at every run once * the shared random value has been computed. */ static sr_state_t *sr_state = NULL; -/* Representation of our persistent state on disk. The sr_state above +/** Representation of our persistent state on disk. The sr_state above * contains the data parsed from this state. When we save to disk, we * translate the sr_state to this sr_disk_state. */ static sr_disk_state_t *sr_disk_state = NULL; @@ -50,24 +51,17 @@ static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; * members with CONF_CHECK_VAR_TYPE. */ DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); -/* These next two are duplicates or near-duplicates from config.c */ -#define VAR(name, conftype, member, initvalue) \ - { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \ - initvalue CONF_TEST_MEMBERS(sr_disk_state_t, conftype, member) } -/* As VAR, but the option name and member name are the same. */ -#define V(member, conftype, initvalue) \ +#define VAR(varname,conftype,member,initvalue) \ + CONFIG_VAR_ETYPE(sr_disk_state_t, varname, conftype, member, 0, initvalue) +#define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) -/* Our persistent state magic number. */ -#define SR_DISK_STATE_MAGIC 0x98AB1254 -static int -disk_state_validate_cb(void *old_state, void *state, void *default_state, - int from_setconf, char **msg); -static void disk_state_free_cb(void *); +/** Our persistent state magic number. */ +#define SR_DISK_STATE_MAGIC 0x98AB1254 -/* Array of variables that are saved to disk as a persistent state. */ -static config_var_t state_vars[] = { - V(Version, UINT, "0"), +/** Array of variables that are saved to disk as a persistent state. */ +static const config_var_t state_vars[] = { + V(Version, POSINT, "0"), V(TorVersion, STRING, NULL), V(ValidAfter, ISOTIME, NULL), V(ValidUntil, ISOTIME, NULL), @@ -80,28 +74,43 @@ static config_var_t state_vars[] = { END_OF_CONFIG_VARS }; -/* "Extra" variable in the state that receives lines we can't parse. This +/** "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ -static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, - offsetof(sr_disk_state_t, ExtraLines), NULL - CONF_TEST_MEMBERS(sr_disk_state_t, LINELIST, ExtraLines) +static const struct_member_t state_extra_var = { + .name = "__extra", + .type = CONFIG_TYPE_LINELIST, + .offset = offsetof(sr_disk_state_t, ExtraLines), }; -/* Configuration format of sr_disk_state_t. */ +/** Configuration format of sr_disk_state_t. */ static const config_format_t state_format = { - sizeof(sr_disk_state_t), - SR_DISK_STATE_MAGIC, - offsetof(sr_disk_state_t, magic_), - NULL, - NULL, - state_vars, - disk_state_validate_cb, - disk_state_free_cb, - &state_extra_var, + .size = sizeof(sr_disk_state_t), + .magic = { + "sr_disk_state_t", + SR_DISK_STATE_MAGIC, + offsetof(sr_disk_state_t, magic_), + }, + .vars = state_vars, + .extra = &state_extra_var, }; -/* Return a string representation of a protocol phase. */ +/** Global configuration manager for the shared-random state file */ +static config_mgr_t *shared_random_state_mgr = NULL; + +/** Return the configuration manager for the shared-random state file. */ +static const config_mgr_t * +get_srs_mgr(void) +{ + if (PREDICT_UNLIKELY(shared_random_state_mgr == NULL)) { + shared_random_state_mgr = config_mgr_new(&state_format); + config_mgr_freeze(shared_random_state_mgr); + } + return shared_random_state_mgr; +} + +static void state_query_del_(sr_state_object_t obj_type, void *data); + +/** Return a string representation of a protocol phase. */ STATIC const char * get_phase_str(sr_phase_t phase) { @@ -119,7 +128,7 @@ get_phase_str(sr_phase_t phase) return the_string; } -/* Return the time we should expire the state file created at <b>now</b>. +/** Return the time we should expire the state file created at <b>now</b>. * We expire the state file in the beginning of the next protocol run. */ STATIC time_t get_state_valid_until_time(time_t now) @@ -150,7 +159,7 @@ get_state_valid_until_time(time_t now) return valid_until; } -/* Given the consensus 'valid-after' time, return the protocol phase we should +/** Given the consensus 'valid-after' time, return the protocol phase we should * be in. */ STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after) @@ -170,7 +179,7 @@ get_sr_protocol_phase(time_t valid_after) } } -/* Add the given <b>commit</b> to <b>state</b>. It MUST be a valid commit +/** Add the given <b>commit</b> to <b>state</b>. It MUST be a valid commit * and there shouldn't be a commit from the same authority in the state * already else verification hasn't been done prior. This takes ownership of * the commit once in our state. */ @@ -195,7 +204,7 @@ commit_add_to_state(sr_commit_t *commit, sr_state_t *state) } } -/* Helper: deallocate a commit object. (Used with digestmap_free(), which +/** Helper: deallocate a commit object. (Used with digestmap_free(), which * requires a function pointer whose argument is void *). */ static void commit_free_(void *p) @@ -206,7 +215,7 @@ commit_free_(void *p) #define state_free(val) \ FREE_AND_NULL(sr_state_t, state_free_, (val)) -/* Free a state that was allocated with state_new(). */ +/** Free a state that was allocated with state_new(). */ static void state_free_(sr_state_t *state) { @@ -220,7 +229,7 @@ state_free_(sr_state_t *state) tor_free(state); } -/* Allocate an sr_state_t object and returns it. If no <b>fname</b>, the +/** Allocate an sr_state_t object and returns it. If no <b>fname</b>, the * default file name is used. This function does NOT initialize the state * timestamp, phase or shared random value. NULL is never returned. */ static sr_state_t * @@ -239,7 +248,7 @@ state_new(const char *fname, time_t now) return new_state; } -/* Set our global state pointer with the one given. */ +/** Set our global state pointer with the one given. */ static void state_set(sr_state_t *state) { @@ -253,34 +262,33 @@ state_set(sr_state_t *state) #define disk_state_free(val) \ FREE_AND_NULL(sr_disk_state_t, disk_state_free_, (val)) -/* Free an allocated disk state. */ +/** Free an allocated disk state. */ static void disk_state_free_(sr_disk_state_t *state) { if (state == NULL) { return; } - config_free(&state_format, state); + config_free(get_srs_mgr(), state); } -/* Allocate a new disk state, initialize it and return it. */ +/** Allocate a new disk state, initialize it and return it. */ static sr_disk_state_t * disk_state_new(time_t now) { - sr_disk_state_t *new_state = tor_malloc_zero(sizeof(*new_state)); + sr_disk_state_t *new_state = config_new(get_srs_mgr()); - new_state->magic_ = SR_DISK_STATE_MAGIC; new_state->Version = SR_PROTO_VERSION; new_state->TorVersion = tor_strdup(get_version()); new_state->ValidUntil = get_state_valid_until_time(now); new_state->ValidAfter = now; /* Init config format. */ - config_init(&state_format, new_state); + config_init(get_srs_mgr(), new_state); return new_state; } -/* Set our global disk state with the given state. */ +/** Set our global disk state with the given state. */ static void disk_state_set(sr_disk_state_t *state) { @@ -291,7 +299,7 @@ disk_state_set(sr_disk_state_t *state) sr_disk_state = state; } -/* Return -1 if the disk state is invalid (something in there that we can't or +/** Return -1 if the disk state is invalid (something in there that we can't or * shouldn't use). Return 0 if everything checks out. */ static int disk_state_validate(const sr_disk_state_t *state) @@ -326,31 +334,7 @@ disk_state_validate(const sr_disk_state_t *state) return -1; } -/* Validate the disk state (NOP for now). */ -static int -disk_state_validate_cb(void *old_state, void *state, void *default_state, - int from_setconf, char **msg) -{ - /* We don't use these; only options do. */ - (void) from_setconf; - (void) default_state; - (void) old_state; - - /* This is called by config_dump which is just before we are about to - * write it to disk. At that point, our global memory state has been - * copied to the disk state so it's fair to assume it's trustable. */ - (void) state; - (void) msg; - return 0; -} - -static void -disk_state_free_cb(void *state) -{ - disk_state_free_(state); -} - -/* Parse the Commit line(s) in the disk state and translate them to the +/** Parse the Commit line(s) in the disk state and translate them to the * the memory state. Return 0 on success else -1 on error. */ static int disk_state_parse_commits(sr_state_t *state, @@ -405,7 +389,7 @@ disk_state_parse_commits(sr_state_t *state, return -1; } -/* Parse a share random value line from the disk state and save it to dst +/** Parse a share random value line from the disk state and save it to dst * which is an allocated srv object. Return 0 on success else -1. */ static int disk_state_parse_srv(const char *value, sr_srv_t *dst) @@ -440,7 +424,7 @@ disk_state_parse_srv(const char *value, sr_srv_t *dst) return ret; } -/* Parse both SharedRandCurrentValue and SharedRandPreviousValue line from +/** Parse both SharedRandCurrentValue and SharedRandPreviousValue line from * the state. Return 0 on success else -1. */ static int disk_state_parse_sr_values(sr_state_t *state, @@ -491,7 +475,7 @@ disk_state_parse_sr_values(sr_state_t *state, return -1; } -/* Parse the given disk state and set a newly allocated state. On success, +/** Parse the given disk state and set a newly allocated state. On success, * return that state else NULL. */ static sr_state_t * disk_state_parse(const sr_disk_state_t *new_disk_state) @@ -525,7 +509,7 @@ disk_state_parse(const sr_disk_state_t *new_disk_state) return NULL; } -/* From a valid commit object and an allocated config line, set the line's +/** From a valid commit object and an allocated config line, set the line's * value to the state string representation of a commit. */ static void disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) @@ -535,7 +519,7 @@ disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) tor_assert(commit); tor_assert(line); - if (!tor_mem_is_zero(commit->encoded_reveal, + if (!fast_mem_is_zero(commit->encoded_reveal, sizeof(commit->encoded_reveal))) { /* Add extra whitespace so we can format the line correctly. */ tor_asprintf(&reveal_str, " %s", commit->encoded_reveal); @@ -552,7 +536,7 @@ disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line) } } -/* From a valid srv object and an allocated config line, set the line's +/** From a valid srv object and an allocated config line, set the line's * value to the state string representation of a shared random value. */ static void disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) @@ -570,7 +554,7 @@ disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line) tor_asprintf(&line->value, "%" PRIu64 " %s", srv->num_reveals, encoded); } -/* Reset disk state that is free allocated memory and zeroed the object. */ +/** Reset disk state that is free allocated memory and zeroed the object. */ static void disk_state_reset(void) { @@ -580,15 +564,16 @@ disk_state_reset(void) config_free_lines(sr_disk_state->ExtraLines); tor_free(sr_disk_state->TorVersion); - /* Clean up the struct */ - memset(sr_disk_state, 0, sizeof(*sr_disk_state)); + /* Clear other fields. */ + sr_disk_state->ValidAfter = 0; + sr_disk_state->ValidUntil = 0; + sr_disk_state->Version = 0; /* Reset it with useful data */ - sr_disk_state->magic_ = SR_DISK_STATE_MAGIC; sr_disk_state->TorVersion = tor_strdup(get_version()); } -/* Update our disk state based on our global SR state. */ +/** Update our disk state based on our global SR state. */ static void disk_state_update(void) { @@ -632,7 +617,7 @@ disk_state_update(void) } DIGESTMAP_FOREACH_END; } -/* Load state from disk and put it into our disk state. If the state passes +/** Load state from disk and put it into our disk state. If the state passes * validation, our global state will be updated with it. Return 0 on * success. On error, -EINVAL is returned if the state on disk did contained * something malformed or is unreadable. -ENOENT is returned indicating that @@ -650,7 +635,7 @@ disk_state_load_from_disk(void) return ret; } -/* Helper for disk_state_load_from_disk(). */ +/** Helper for disk_state_load_from_disk(). */ STATIC int disk_state_load_from_disk_impl(const char *fname) { @@ -679,7 +664,7 @@ disk_state_load_from_disk_impl(const char *fname) } disk_state = disk_state_new(time(NULL)); - config_assign(&state_format, disk_state, lines, 0, &errmsg); + config_assign(get_srs_mgr(), disk_state, lines, 0, &errmsg); config_free_lines(lines); if (errmsg) { log_warn(LD_DIR, "SR: Reading state error: %s", errmsg); @@ -712,7 +697,7 @@ disk_state_load_from_disk_impl(const char *fname) return ret; } -/* Save the disk state to disk but before that update it from the current +/** Save the disk state to disk but before that update it from the current * state so we always have the latest. Return 0 on success else -1. */ static int disk_state_save_to_disk(void) @@ -732,7 +717,7 @@ disk_state_save_to_disk(void) /* Make sure that our disk state is up to date with our memory state * before saving it to disk. */ disk_state_update(); - state = config_dump(&state_format, NULL, sr_disk_state, 0, 0); + state = config_dump(get_srs_mgr(), NULL, sr_disk_state, 0, 0); format_local_iso_time(tbuf, now); tor_asprintf(&content, "# Tor shared random state file last generated on %s " @@ -756,7 +741,7 @@ disk_state_save_to_disk(void) return ret; } -/* Reset our state to prepare for a new protocol run. Once this returns, all +/** Reset our state to prepare for a new protocol run. Once this returns, all * commits in the state will be removed and freed. */ STATIC void reset_state_for_new_protocol_run(time_t valid_after) @@ -777,7 +762,7 @@ reset_state_for_new_protocol_run(time_t valid_after) sr_state_delete_commits(); } -/* This is the first round of the new protocol run starting at +/** This is the first round of the new protocol run starting at * <b>valid_after</b>. Do the necessary housekeeping. */ STATIC void new_protocol_run(time_t valid_after) @@ -811,7 +796,7 @@ new_protocol_run(time_t valid_after) } } -/* Return 1 iff the <b>next_phase</b> is a phase transition from the current +/** Return 1 iff the <b>next_phase</b> is a phase transition from the current * phase that is it's different. */ STATIC int is_phase_transition(sr_phase_t next_phase) @@ -819,7 +804,7 @@ is_phase_transition(sr_phase_t next_phase) return sr_state->phase != next_phase; } -/* Helper function: return a commit using the RSA fingerprint of the +/** Helper function: return a commit using the RSA fingerprint of the * authority or NULL if no such commit is known. */ static sr_commit_t * state_query_get_commit(const char *rsa_fpr) @@ -828,11 +813,14 @@ state_query_get_commit(const char *rsa_fpr) return digestmap_get(sr_state->commits, rsa_fpr); } -/* Helper function: This handles the GET state action using an +/** Helper function: This handles the GET state action using an * <b>obj_type</b> and <b>data</b> needed for the action. */ static void * state_query_get_(sr_state_object_t obj_type, const void *data) { + if (BUG(!sr_state)) + return NULL; + void *obj = NULL; switch (obj_type) { @@ -860,24 +848,45 @@ state_query_get_(sr_state_object_t obj_type, const void *data) return obj; } -/* Helper function: This handles the PUT state action using an - * <b>obj_type</b> and <b>data</b> needed for the action. */ +/** Helper function: This handles the PUT state action using an + * <b>obj_type</b> and <b>data</b> needed for the action. + * PUT frees the previous data before replacing it, if needed. */ static void state_query_put_(sr_state_object_t obj_type, void *data) { + if (BUG(!sr_state)) + return; + switch (obj_type) { case SR_STATE_OBJ_COMMIT: { sr_commit_t *commit = data; tor_assert(commit); + /* commit_add_to_state() frees the old commit, if there is one */ commit_add_to_state(commit, sr_state); break; } case SR_STATE_OBJ_CURSRV: - sr_state->current_srv = (sr_srv_t *) data; + /* Check if the new pointer is the same as the old one: if it is, it's + * probably a bug. The caller may have confused current and previous, + * or they may have forgotten to sr_srv_dup(). + * Putting NULL multiple times is allowed. */ + if (!BUG(data && sr_state->current_srv == (sr_srv_t *) data)) { + /* We own the old SRV, so we need to free it. */ + state_query_del_(SR_STATE_OBJ_CURSRV, NULL); + sr_state->current_srv = (sr_srv_t *) data; + } break; case SR_STATE_OBJ_PREVSRV: - sr_state->previous_srv = (sr_srv_t *) data; + /* Check if the new pointer is the same as the old one: if it is, it's + * probably a bug. The caller may have confused current and previous, + * or they may have forgotten to sr_srv_dup(). + * Putting NULL multiple times is allowed. */ + if (!BUG(data && sr_state->previous_srv == (sr_srv_t *) data)) { + /* We own the old SRV, so we need to free it. */ + state_query_del_(SR_STATE_OBJ_PREVSRV, NULL); + sr_state->previous_srv = (sr_srv_t *) data; + } break; case SR_STATE_OBJ_VALID_AFTER: sr_state->valid_after = *((time_t *) data); @@ -892,11 +901,14 @@ state_query_put_(sr_state_object_t obj_type, void *data) } } -/* Helper function: This handles the DEL_ALL state action using an +/** Helper function: This handles the DEL_ALL state action using an * <b>obj_type</b> and <b>data</b> needed for the action. */ static void state_query_del_all_(sr_state_object_t obj_type) { + if (BUG(!sr_state)) + return; + switch (obj_type) { case SR_STATE_OBJ_COMMIT: { @@ -907,7 +919,7 @@ state_query_del_all_(sr_state_object_t obj_type) } DIGESTMAP_FOREACH_END; break; } - /* The following object are _NOT_ suppose to be removed. */ + /* The following objects are _NOT_ supposed to be removed. */ case SR_STATE_OBJ_CURSRV: case SR_STATE_OBJ_PREVSRV: case SR_STATE_OBJ_PHASE: @@ -918,13 +930,16 @@ state_query_del_all_(sr_state_object_t obj_type) } } -/* Helper function: This handles the DEL state action using an +/** Helper function: This handles the DEL state action using an * <b>obj_type</b> and <b>data</b> needed for the action. */ static void state_query_del_(sr_state_object_t obj_type, void *data) { (void) data; + if (BUG(!sr_state)) + return; + switch (obj_type) { case SR_STATE_OBJ_PREVSRV: tor_free(sr_state->previous_srv); @@ -941,7 +956,7 @@ state_query_del_(sr_state_object_t obj_type, void *data) } } -/* Query state using an <b>action</b> for an object type <b>obj_type</b>. +/** Query state using an <b>action</b> for an object type <b>obj_type</b>. * The <b>data</b> pointer needs to point to an object that the action needs * to use and if anything is required to be returned, it is stored in * <b>out</b>. @@ -983,7 +998,7 @@ state_query(sr_state_action_t action, sr_state_object_t obj_type, } } -/* Delete the current SRV value from the state freeing it and the value is set +/** Delete the current SRV value from the state freeing it and the value is set * to NULL meaning empty. */ STATIC void state_del_current_srv(void) @@ -991,7 +1006,7 @@ state_del_current_srv(void) state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_CURSRV, NULL, NULL); } -/* Delete the previous SRV value from the state freeing it and the value is +/** Delete the previous SRV value from the state freeing it and the value is * set to NULL meaning empty. */ STATIC void state_del_previous_srv(void) @@ -999,20 +1014,20 @@ state_del_previous_srv(void) state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_PREVSRV, NULL, NULL); } -/* Rotate SRV value by freeing the previous value, assigning the current - * value to the previous one and nullifying the current one. */ +/** Rotate SRV value by setting the previous SRV to the current SRV, and + * clearing the current SRV. */ STATIC void state_rotate_srv(void) { /* First delete previous SRV from the state. Object will be freed. */ state_del_previous_srv(); - /* Set previous SRV with the current one. */ - sr_state_set_previous_srv(sr_state_get_current_srv()); - /* Nullify the current srv. */ + /* Set previous SRV to a copy of the current one. */ + sr_state_set_previous_srv(sr_srv_dup(sr_state_get_current_srv())); + /* Free and NULL the current srv. */ sr_state_set_current_srv(NULL); } -/* Set valid after time in the our state. */ +/** Set valid after time in the our state. */ void sr_state_set_valid_after(time_t valid_after) { @@ -1020,16 +1035,19 @@ sr_state_set_valid_after(time_t valid_after) (void *) &valid_after, NULL); } -/* Return the phase we are currently in according to our state. */ +/** Return the phase we are currently in according to our state. */ sr_phase_t sr_state_get_phase(void) { - void *ptr; + void *ptr=NULL; state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PHASE, NULL, &ptr); + tor_assert(ptr); return *(sr_phase_t *) ptr; } -/* Return the previous SRV value from our state. Value CAN be NULL. */ +/** Return the previous SRV value from our state. Value CAN be NULL. + * The state object owns the SRV, so the calling code should not free the SRV. + * Use sr_srv_dup() if you want to keep a copy of the SRV. */ const sr_srv_t * sr_state_get_previous_srv(void) { @@ -1039,7 +1057,7 @@ sr_state_get_previous_srv(void) return srv; } -/* Set the current SRV value from our state. Value CAN be NULL. The srv +/** Set the current SRV value from our state. Value CAN be NULL. The srv * object ownership is transferred to the state object. */ void sr_state_set_previous_srv(const sr_srv_t *srv) @@ -1048,7 +1066,9 @@ sr_state_set_previous_srv(const sr_srv_t *srv) NULL); } -/* Return the current SRV value from our state. Value CAN be NULL. */ +/** Return the current SRV value from our state. Value CAN be NULL. + * The state object owns the SRV, so the calling code should not free the SRV. + * Use sr_srv_dup() if you want to keep a copy of the SRV. */ const sr_srv_t * sr_state_get_current_srv(void) { @@ -1058,7 +1078,7 @@ sr_state_get_current_srv(void) return srv; } -/* Set the current SRV value from our state. Value CAN be NULL. The srv +/** Set the current SRV value from our state. Value CAN be NULL. The srv * object ownership is transferred to the state object. */ void sr_state_set_current_srv(const sr_srv_t *srv) @@ -1067,7 +1087,7 @@ sr_state_set_current_srv(const sr_srv_t *srv) NULL); } -/* Clean all the SRVs in our state. */ +/** Clean all the SRVs in our state. */ void sr_state_clean_srvs(void) { @@ -1076,7 +1096,7 @@ sr_state_clean_srvs(void) state_del_current_srv(); } -/* Return a pointer to the commits map from our state. CANNOT be NULL. */ +/** Return a pointer to the commits map from our state. CANNOT be NULL. */ digestmap_t * sr_state_get_commits(void) { @@ -1087,7 +1107,7 @@ sr_state_get_commits(void) return commits; } -/* Update the current SR state as needed for the upcoming voting round at +/** Update the current SR state as needed for the upcoming voting round at * <b>valid_after</b>. */ void sr_state_update(time_t valid_after) @@ -1151,7 +1171,7 @@ sr_state_update(time_t valid_after) } } -/* Return commit object from the given authority digest <b>rsa_identity</b>. +/** Return commit object from the given authority digest <b>rsa_identity</b>. * Return NULL if not found. */ sr_commit_t * sr_state_get_commit(const char *rsa_identity) @@ -1165,7 +1185,7 @@ sr_state_get_commit(const char *rsa_identity) return commit; } -/* Add <b>commit</b> to the permanent state. The commit object ownership is +/** Add <b>commit</b> to the permanent state. The commit object ownership is * transferred to the state so the caller MUST not free it. */ void sr_state_add_commit(sr_commit_t *commit) @@ -1180,14 +1200,14 @@ sr_state_add_commit(sr_commit_t *commit) sr_commit_get_rsa_fpr(commit)); } -/* Remove all commits from our state. */ +/** Remove all commits from our state. */ void sr_state_delete_commits(void) { state_query(SR_STATE_ACTION_DEL_ALL, SR_STATE_OBJ_COMMIT, NULL, NULL); } -/* Copy the reveal information from <b>commit</b> into <b>saved_commit</b>. +/** Copy the reveal information from <b>commit</b> into <b>saved_commit</b>. * This <b>saved_commit</b> MUST come from our current SR state. Once modified, * the disk state is updated. */ void @@ -1208,7 +1228,7 @@ sr_state_copy_reveal_info(sr_commit_t *saved_commit, const sr_commit_t *commit) sr_commit_get_rsa_fpr(saved_commit)); } -/* Set the fresh SRV flag from our state. This doesn't need to trigger a +/** Set the fresh SRV flag from our state. This doesn't need to trigger a * disk state synchronization so we directly change the state. */ void sr_state_set_fresh_srv(void) @@ -1216,7 +1236,7 @@ sr_state_set_fresh_srv(void) sr_state->is_srv_fresh = 1; } -/* Unset the fresh SRV flag from our state. This doesn't need to trigger a +/** Unset the fresh SRV flag from our state. This doesn't need to trigger a * disk state synchronization so we directly change the state. */ void sr_state_unset_fresh_srv(void) @@ -1224,14 +1244,14 @@ sr_state_unset_fresh_srv(void) sr_state->is_srv_fresh = 0; } -/* Return the value of the fresh SRV flag. */ +/** Return the value of the fresh SRV flag. */ unsigned int sr_state_srv_is_fresh(void) { return sr_state->is_srv_fresh; } -/* Cleanup and free our disk and memory state. */ +/** Cleanup and free our disk and memory state. */ void sr_state_free_all(void) { @@ -1240,9 +1260,10 @@ sr_state_free_all(void) /* Nullify our global state. */ sr_state = NULL; sr_disk_state = NULL; + config_mgr_free(shared_random_state_mgr); } -/* Save our current state in memory to disk. */ +/** Save our current state in memory to disk. */ void sr_state_save(void) { @@ -1250,7 +1271,7 @@ sr_state_save(void) state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL); } -/* Return 1 iff the state has been initialized that is it exists in memory. +/** Return 1 iff the state has been initialized that is it exists in memory. * Return 0 otherwise. */ int sr_state_is_initialized(void) @@ -1258,7 +1279,7 @@ sr_state_is_initialized(void) return sr_state == NULL ? 0 : 1; } -/* Initialize the disk and memory state. +/** Initialize the disk and memory state. * * If save_to_disk is set to 1, the state is immediately saved to disk after * creation else it's not thus only kept in memory. @@ -1321,7 +1342,7 @@ sr_state_init(int save_to_disk, int read_from_disk) #ifdef TOR_UNIT_TESTS -/* Set the current phase of the protocol. Used only by unit tests. */ +/** Set the current phase of the protocol. Used only by unit tests. */ void set_sr_phase(sr_phase_t phase) { @@ -1330,7 +1351,7 @@ set_sr_phase(sr_phase_t phase) sr_state->phase = phase; } -/* Get the SR state. Used only by unit tests */ +/** Get the SR state. Used only by unit tests */ sr_state_t * get_sr_state(void) { diff --git a/src/feature/dirauth/shared_random_state.h b/src/feature/dirauth/shared_random_state.h index 08f999f9d4..3a34bcc3e7 100644 --- a/src/feature/dirauth/shared_random_state.h +++ b/src/feature/dirauth/shared_random_state.h @@ -1,12 +1,17 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file shared_random_state.h + * @brief Header for shared_random_state.c + **/ + #ifndef TOR_SHARED_RANDOM_STATE_H #define TOR_SHARED_RANDOM_STATE_H #include "feature/dirauth/shared_random.h" -/* Action that can be performed on the state for any objects. */ +/** Action that can be performed on the state for any objects. */ typedef enum { SR_STATE_ACTION_GET = 1, SR_STATE_ACTION_PUT = 2, @@ -15,52 +20,53 @@ typedef enum { SR_STATE_ACTION_SAVE = 5, } sr_state_action_t; -/* Object in the state that can be queried through the state API. */ +/** Object in the state that can be queried through the state API. */ typedef enum { - /* Will return a single commit using an authority identity key. */ + /** Will return a single commit using an authority identity key. */ SR_STATE_OBJ_COMMIT, - /* Returns the entire list of commits from the state. */ + /** Returns the entire list of commits from the state. */ SR_STATE_OBJ_COMMITS, - /* Return the current SRV object pointer. */ + /** Return the current SRV object pointer. */ SR_STATE_OBJ_CURSRV, - /* Return the previous SRV object pointer. */ + /** Return the previous SRV object pointer. */ SR_STATE_OBJ_PREVSRV, - /* Return the phase. */ + /** Return the phase. */ SR_STATE_OBJ_PHASE, - /* Get or Put the valid after time. */ + /** Get or Put the valid after time. */ SR_STATE_OBJ_VALID_AFTER, } sr_state_object_t; -/* State of the protocol. It's also saved on disk in fname. This data +/** State of the protocol. It's also saved on disk in fname. This data * structure MUST be synchronized at all time with the one on disk. */ typedef struct sr_state_t { - /* Filename of the state file on disk. */ + /** Filename of the state file on disk. */ char *fname; - /* Version of the protocol. */ + /** Version of the protocol. */ uint32_t version; - /* The valid-after of the voting period we have prepared the state for. */ + /** The valid-after of the voting period we have prepared the state for. */ time_t valid_after; - /* Until when is this state valid? */ + /** Until when is this state valid? */ time_t valid_until; - /* Protocol phase. */ + /** Protocol phase. */ sr_phase_t phase; - /* Number of runs completed. */ + /** Number of runs completed. */ uint64_t n_protocol_runs; - /* The number of commitment rounds we've performed in this protocol run. */ + /** The number of commitment rounds we've performed in this protocol run. */ unsigned int n_commit_rounds; - /* The number of reveal rounds we've performed in this protocol run. */ + /** The number of reveal rounds we've performed in this protocol run. */ unsigned int n_reveal_rounds; - /* A map of all the received commitments for this protocol run. This is + /** A map of all the received commitments for this protocol run. This is * indexed by authority RSA identity digest. */ digestmap_t *commits; - /* Current and previous shared random value. */ + /** Current shared random value. */ sr_srv_t *previous_srv; + /** Previous shared random value. */ sr_srv_t *current_srv; - /* Indicate if the state contains an SRV that was _just_ generated. This is + /** Indicate if the state contains an SRV that was _just_ generated. This is * used during voting so that we know whether to use the super majority rule * or not when deciding on keeping it for the consensus. It is _always_ set * to 0 post consensus. @@ -73,22 +79,22 @@ typedef struct sr_state_t { unsigned int is_srv_fresh:1; } sr_state_t; -/* Persistent state of the protocol, as saved to disk. */ +/** Persistent state of the protocol, as saved to disk. */ typedef struct sr_disk_state_t { uint32_t magic_; - /* Version of the protocol. */ + /** Version of the protocol. */ int Version; - /* Version of our running tor. */ + /** Version of our running tor. */ char *TorVersion; - /* Creation time of this state */ + /** Creation time of this state */ time_t ValidAfter; - /* State valid until? */ + /** State valid until? */ time_t ValidUntil; - /* All commits seen that are valid. */ + /** All commits seen that are valid. */ struct config_line_t *Commit; - /* Previous and current shared random value. */ + /** Previous and current shared random value. */ struct config_line_t *SharedRandValues; - /* Extra Lines for configuration we might not know. */ + /** Extra Lines for configuration we might not know. */ struct config_line_t *ExtraLines; } sr_disk_state_t; diff --git a/src/feature/dirauth/vote_microdesc_hash_st.h b/src/feature/dirauth/vote_microdesc_hash_st.h index 92acdf1157..7f8ebf7fd7 100644 --- a/src/feature/dirauth/vote_microdesc_hash_st.h +++ b/src/feature/dirauth/vote_microdesc_hash_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file vote_microdesc_hash_st.h + * @brief Microdescriptor-hash voting strcture. + **/ + #ifndef VOTE_MICRODESC_HASH_ST_H #define VOTE_MICRODESC_HASH_ST_H @@ -18,5 +23,4 @@ struct vote_microdesc_hash_t { char *microdesc_hash_line; }; -#endif - +#endif /* !defined(VOTE_MICRODESC_HASH_ST_H) */ diff --git a/src/feature/dirauth/voteflags.c b/src/feature/dirauth/voteflags.c index 54c70b989a..477eb6f0b7 100644 --- a/src/feature/dirauth/voteflags.c +++ b/src/feature/dirauth/voteflags.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,6 +18,7 @@ #include "core/or/policies.h" #include "feature/dirauth/bwauth.h" #include "feature/dirauth/reachability.h" +#include "feature/dirauth/dirauth_sys.h" #include "feature/hibernate/hibernate.h" #include "feature/nodelist/dirlist.h" #include "feature/nodelist/networkstatus.h" @@ -27,8 +28,10 @@ #include "feature/relay/router.h" #include "feature/stats/rephist.h" +#include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" +#include "feature/nodelist/routerlist_st.h" #include "feature/nodelist/vote_routerstatus_st.h" #include "lib/container/order.h" @@ -95,7 +98,7 @@ real_uptime(const routerinfo_t *router, time_t now) */ static int dirserv_thinks_router_is_unreliable(time_t now, - routerinfo_t *router, + const routerinfo_t *router, int need_uptime, int need_capacity) { if (need_uptime) { @@ -144,7 +147,7 @@ router_is_active(const routerinfo_t *ri, const node_t *node, time_t now) * if TestingTorNetwork, and TestingMinExitFlagThreshold is non-zero */ if (!ri->bandwidthcapacity) { if (get_options()->TestingTorNetwork) { - if (get_options()->TestingMinExitFlagThreshold > 0) { + if (dirauth_get_options()->TestingMinExitFlagThreshold > 0) { /* If we're in a TestingTorNetwork, and TestingMinExitFlagThreshold is, * then require bandwidthcapacity */ return 0; @@ -174,14 +177,14 @@ dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, long uptime; /* If we haven't been running for at least - * get_options()->MinUptimeHidServDirectoryV2 seconds, we can't + * MinUptimeHidServDirectoryV2 seconds, we can't * have accurate data telling us a relay has been up for at least * that long. We also want to allow a bit of slack: Reachability * tests aren't instant. If we haven't been running long enough, * trust the relay. */ if (get_uptime() > - get_options()->MinUptimeHidServDirectoryV2 * 1.1) + dirauth_get_options()->MinUptimeHidServDirectoryV2 * 1.1) uptime = MIN(rep_hist_get_uptime(router->cache_info.identity_digest, now), real_uptime(router, now)); else @@ -190,7 +193,7 @@ dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, return (router->wants_to_be_hs_dir && router->supports_tunnelled_dir_requests && node->is_stable && node->is_fast && - uptime >= get_options()->MinUptimeHidServDirectoryV2 && + uptime >= dirauth_get_options()->MinUptimeHidServDirectoryV2 && router_is_active(router, node, now)); } @@ -213,9 +216,10 @@ router_counts_toward_thresholds(const node_t *node, time_t now, dirserv_has_measured_bw(node->identity); uint64_t min_bw_kb = ABSOLUTE_MIN_BW_VALUE_TO_CONSIDER_KB; const or_options_t *options = get_options(); + const dirauth_options_t *dirauth_options = dirauth_get_options(); if (options->TestingTorNetwork) { - min_bw_kb = (int64_t)options->TestingMinExitFlagThreshold / 1000; + min_bw_kb = (int64_t)dirauth_options->TestingMinExitFlagThreshold / 1000; } return node->ri && router_is_active(node->ri, node, now) && @@ -238,14 +242,15 @@ dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil) uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb; long *tks; double *mtbfs, *wfus; - smartlist_t *nodelist; + const smartlist_t *nodelist; time_t now = time(NULL); const or_options_t *options = get_options(); + const dirauth_options_t *dirauth_options = dirauth_get_options(); /* Require mbw? */ int require_mbw = (dirserv_get_last_n_measured_bws() > - options->MinMeasuredBWsForAuthToIgnoreAdvertised) ? 1 : 0; + dirauth_options->MinMeasuredBWsForAuthToIgnoreAdvertised) ? 1 : 0; /* initialize these all here, in case there are no routers */ stable_uptime = 0; @@ -337,7 +342,7 @@ dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil) ABSOLUTE_MIN_VALUE_FOR_FAST_FLAG, INT32_MAX); if (options->TestingTorNetwork) { - min_fast = (int32_t)options->TestingMinFastFlagThreshold; + min_fast = (int32_t)dirauth_options->TestingMinFastFlagThreshold; } max_fast = networkstatus_get_param(NULL, "FastFlagMaxThreshold", INT32_MAX, min_fast, INT32_MAX); @@ -351,9 +356,11 @@ dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil) } /* Protect sufficiently fast nodes from being pushed out of the set * of Fast nodes. */ - if (options->AuthDirFastGuarantee && - fast_bandwidth_kb > options->AuthDirFastGuarantee/1000) - fast_bandwidth_kb = (uint32_t)options->AuthDirFastGuarantee/1000; + { + const uint64_t fast_opt = dirauth_get_options()->AuthDirFastGuarantee; + if (fast_opt && fast_bandwidth_kb > fast_opt / 1000) + fast_bandwidth_kb = (uint32_t)(fast_opt / 1000); + } /* Now that we have a time-known that 7/8 routers are known longer than, * fill wfus with the wfu of every such "familiar" router. */ @@ -427,7 +434,7 @@ dirserv_get_flag_thresholds_line(void) { char *result=NULL; const int measured_threshold = - get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; + dirauth_get_options()->MinMeasuredBWsForAuthToIgnoreAdvertised; const int enough_measured_bw = dirserv_get_last_n_measured_bws() > measured_threshold; @@ -454,8 +461,9 @@ dirserv_get_flag_thresholds_line(void) int running_long_enough_to_decide_unreachable(void) { - return time_of_process_start - + get_options()->TestingAuthDirTimeToLearnReachability < approx_time(); + const dirauth_options_t *opts = dirauth_get_options(); + return time_of_process_start + + opts->TestingAuthDirTimeToLearnReachability < approx_time(); } /** Each server needs to have passed a reachability test no more @@ -480,6 +488,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) */ int answer; const or_options_t *options = get_options(); + const dirauth_options_t *dirauth_options = dirauth_get_options(); node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest); tor_assert(node); @@ -506,7 +515,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) IPv6 OR port since that'd kill all dual stack relays until a majority of the dir auths have IPv6 connectivity. */ answer = (now < node->last_reachable + REACHABLE_TIMEOUT && - (options->AuthDirHasIPv6Connectivity != 1 || + (dirauth_options->AuthDirHasIPv6Connectivity != 1 || tor_addr_is_null(&router->ipv6_addr) || now < node->last_reachable6 + REACHABLE_TIMEOUT)); } @@ -531,42 +540,49 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) node->is_running = answer; } -/** Extract status information from <b>ri</b> and from other authority - * functions and store it in <b>rs</b>. <b>rs</b> is zeroed out before it is - * set. - * - * We assume that ri-\>is_running has already been set, e.g. by - * dirserv_set_router_is_running(ri, now); +/* Check <b>node</b> and <b>ri</b> on whether or not we should publish a + * relay's IPv6 addresses. */ +static int +should_publish_node_ipv6(const node_t *node, const routerinfo_t *ri, + time_t now) +{ + const dirauth_options_t *options = dirauth_get_options(); + + return options->AuthDirHasIPv6Connectivity == 1 && + !tor_addr_is_null(&ri->ipv6_addr) && + ((node->last_reachable6 >= now - REACHABLE_TIMEOUT) || + router_is_me(ri)); +} + +/** + * Extract status information from <b>ri</b> and from other authority + * functions and store it in <b>rs</b>, as per + * <b>set_routerstatus_from_routerinfo</b>. Additionally, sets information + * in from the authority subsystem. */ void -set_routerstatus_from_routerinfo(routerstatus_t *rs, - node_t *node, - routerinfo_t *ri, - time_t now, - int listbadexits) +dirauth_set_routerstatus_from_routerinfo(routerstatus_t *rs, + node_t *node, + const routerinfo_t *ri, + time_t now, + int listbadexits) { const or_options_t *options = get_options(); uint32_t routerbw_kb = dirserv_get_credible_bandwidth_kb(ri); - memset(rs, 0, sizeof(routerstatus_t)); - - rs->is_authority = - router_digest_is_trusted_dir(ri->cache_info.identity_digest); - - /* Already set by compute_performance_thresholds. */ - rs->is_exit = node->is_exit; - rs->is_stable = node->is_stable = - !dirserv_thinks_router_is_unreliable(now, ri, 1, 0); - rs->is_fast = node->is_fast = - !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); - rs->is_flagged_running = node->is_running; /* computed above */ + /* Set these flags so that set_routerstatus_from_routerinfo can copy them. + */ + node->is_stable = !dirserv_thinks_router_is_unreliable(now, ri, 1, 0); + node->is_fast = !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); + node->is_hs_dir = dirserv_thinks_router_is_hs_dir(ri, node, now); - rs->is_valid = node->is_valid; + set_routerstatus_from_routerinfo(rs, node, ri); + /* Override rs->is_possible_guard. */ + const uint64_t bw_opt = dirauth_get_options()->AuthDirGuardBWGuarantee; if (node->is_fast && node->is_stable && ri->supports_tunnelled_dir_requests && - ((options->AuthDirGuardBWGuarantee && - routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) || + ((bw_opt && routerbw_kb >= bw_opt / 1000) || routerbw_kb >= MIN(guard_bandwidth_including_exits_kb, guard_bandwidth_excluding_exits_kb))) { long tk = rep_hist_get_weighted_time_known( @@ -578,29 +594,16 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, rs->is_possible_guard = 0; } + /* Override rs->is_bad_exit */ rs->is_bad_exit = listbadexits && node->is_bad_exit; - rs->is_hs_dir = node->is_hs_dir = - dirserv_thinks_router_is_hs_dir(ri, node, now); - - rs->is_named = rs->is_unnamed = 0; - - rs->published_on = ri->cache_info.published_on; - memcpy(rs->identity_digest, node->identity, DIGEST_LEN); - memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest, - DIGEST_LEN); - rs->addr = ri->addr; - strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname)); - rs->or_port = ri->or_port; - rs->dir_port = ri->dir_port; - rs->is_v2_dir = ri->supports_tunnelled_dir_requests; - if (options->AuthDirHasIPv6Connectivity == 1 && - !tor_addr_is_null(&ri->ipv6_addr) && - node->last_reachable6 >= now - REACHABLE_TIMEOUT) { - /* We're configured as having IPv6 connectivity. There's an IPv6 - OR port and it's reachable so copy it to the routerstatus. */ - tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr); - rs->ipv6_orport = ri->ipv6_orport; - } else { + + /* Set rs->is_staledesc. */ + rs->is_staledesc = + (ri->cache_info.published_on + DESC_IS_STALE_INTERVAL) < now; + + if (! should_publish_node_ipv6(node, ri, now)) { + /* We're not configured as having IPv6 connectivity or the node isn't: + * zero its IPv6 information. */ tor_addr_make_null(&rs->ipv6_addr, AF_INET6); rs->ipv6_orport = 0; } @@ -617,9 +620,9 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs) { - const or_options_t *options = get_options(); + const dirauth_options_t *options = dirauth_get_options(); - tor_assert(options->TestingTorNetwork); + tor_assert(get_options()->TestingTorNetwork); if (routerset_contains_routerstatus(options->TestingDirAuthVoteExit, rs, 0)) { @@ -642,3 +645,20 @@ dirserv_set_routerstatus_testing(routerstatus_t *rs) rs->is_hs_dir = 0; } } + +/** Use dirserv_set_router_is_running() to set bridges as running if they're + * reachable. + * + * This function is called from set_bridge_running_callback() when running as + * a bridge authority. + */ +void +dirserv_set_bridges_running(time_t now) +{ + routerlist_t *rl = router_get_routerlist(); + + SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, ri) { + if (ri->purpose == ROUTER_PURPOSE_BRIDGE) + dirserv_set_router_is_running(ri, now); + } SMARTLIST_FOREACH_END(ri); +} diff --git a/src/feature/dirauth/voteflags.h b/src/feature/dirauth/voteflags.h index aa7b6ed082..91f3854573 100644 --- a/src/feature/dirauth/voteflags.h +++ b/src/feature/dirauth/voteflags.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,20 +12,28 @@ #ifndef TOR_VOTEFLAGS_H #define TOR_VOTEFLAGS_H +#ifdef HAVE_MODULE_DIRAUTH void dirserv_set_router_is_running(routerinfo_t *router, time_t now); char *dirserv_get_flag_thresholds_line(void); void dirserv_compute_bridge_flag_thresholds(void); int running_long_enough_to_decide_unreachable(void); -void set_routerstatus_from_routerinfo(routerstatus_t *rs, - node_t *node, - routerinfo_t *ri, time_t now, - int listbadexits); +void dirauth_set_routerstatus_from_routerinfo(routerstatus_t *rs, + node_t *node, + const routerinfo_t *ri, + time_t now, + int listbadexits); void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil); +#endif /* defined(HAVE_MODULE_DIRAUTH) */ + +void dirserv_set_bridges_running(time_t now); #ifdef VOTEFLAGS_PRIVATE +/** Any descriptor older than this age causes the authorities to set the + * StaleDesc flag. */ +#define DESC_IS_STALE_INTERVAL (18*60*60) STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs); -#endif +#endif /* defined(VOTEFLAGS_PRIVATE) */ -#endif +#endif /* !defined(TOR_VOTEFLAGS_H) */ diff --git a/src/feature/dircache/.may_include b/src/feature/dircache/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/dircache/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/dircache/cached_dir_st.h b/src/feature/dircache/cached_dir_st.h index 71dca8c3a2..ede1d028da 100644 --- a/src/feature/dircache/cached_dir_st.h +++ b/src/feature/dircache/cached_dir_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file cached_dir_st.h + * @brief Cached large directory object structure. + **/ + #ifndef CACHED_DIR_ST_H #define CACHED_DIR_ST_H @@ -21,5 +26,4 @@ struct cached_dir_t { int refcnt; /**< Reference count for this cached_dir_t. */ }; -#endif - +#endif /* !defined(CACHED_DIR_ST_H) */ diff --git a/src/feature/dircache/conscache.c b/src/feature/dircache/conscache.c index cf4fe8701d..ceba410a5f 100644 --- a/src/feature/dircache/conscache.c +++ b/src/feature/dircache/conscache.c @@ -1,6 +1,11 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file conscache.c + * @brief Consensus and diff on-disk cache. + **/ + #include "core/or/or.h" #include "app/config/config.h" @@ -92,7 +97,7 @@ consensus_cache_open(const char *subdir, int max_entries) */ #define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; -#else /* !(defined(MUST_UNMAP_TO_UNLINK)) */ +#else /* !defined(MUST_UNMAP_TO_UNLINK) */ /* Otherwise, we can just tell the storagedir to use the same limits * as this cache. */ storagedir_max_entries = max_entries; @@ -133,7 +138,7 @@ consensus_cache_may_overallocate(consensus_cache_t *cache) */ int consensus_cache_register_with_sandbox(consensus_cache_t *cache, - struct sandbox_cfg_elem **cfg) + struct sandbox_cfg_elem_t **cfg) { #ifdef MUST_UNMAP_TO_UNLINK /* Our Linux sandbox doesn't support huge file lists like the one that would @@ -246,7 +251,7 @@ consensus_cache_find_first(consensus_cache_t *cache, } /** - * Given a <b>cache</b>, add every entry to <b>out<b> for which + * Given a <b>cache</b>, add every entry to <b>out</b> for which * <b>key</b>=<b>value</b>. If <b>key</b> is NULL, add every entry. * * Do not add any entry that has been marked for removal. diff --git a/src/feature/dircache/conscache.h b/src/feature/dircache/conscache.h index d848e57617..ace5908e40 100644 --- a/src/feature/dircache/conscache.h +++ b/src/feature/dircache/conscache.h @@ -1,6 +1,11 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file conscache.h + * @brief Header for conscache.c + **/ + #ifndef TOR_CONSCACHE_H #define TOR_CONSCACHE_H @@ -9,6 +14,8 @@ typedef struct consensus_cache_entry_t consensus_cache_entry_t; typedef struct consensus_cache_t consensus_cache_t; +struct config_line_t; + HANDLE_DECL(consensus_cache_entry, consensus_cache_entry_t, ) #define consensus_cache_entry_handle_free(h) \ FREE_AND_NULL(consensus_cache_entry_handle_t, \ @@ -18,10 +25,10 @@ consensus_cache_t *consensus_cache_open(const char *subdir, int max_entries); void consensus_cache_free_(consensus_cache_t *cache); #define consensus_cache_free(cache) \ FREE_AND_NULL(consensus_cache_t, consensus_cache_free_, (cache)) -struct sandbox_cfg_elem; +struct sandbox_cfg_elem_t; int consensus_cache_may_overallocate(consensus_cache_t *cache); int consensus_cache_register_with_sandbox(consensus_cache_t *cache, - struct sandbox_cfg_elem **cfg); + struct sandbox_cfg_elem_t **cfg); void consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff); void consensus_cache_delete_pending(consensus_cache_t *cache, int force); diff --git a/src/feature/dircache/consdiffmgr.c b/src/feature/dircache/consdiffmgr.c index 025361fa60..8445b8f986 100644 --- a/src/feature/dircache/consdiffmgr.c +++ b/src/feature/dircache/consdiffmgr.c @@ -1,8 +1,8 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** - * \file consdiffmsr.c + * \file consdiffmgr.c * * \brief consensus diff manager functions * @@ -189,6 +189,7 @@ static consdiff_cfg_t consdiff_cfg = { static int consdiffmgr_ensure_space_for_files(int n); static int consensus_queue_compression_work(const char *consensus, + size_t consensus_len, const networkstatus_t *as_parsed); static int consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from, consensus_cache_entry_t *diff_to); @@ -509,8 +510,25 @@ get_max_age_to_cache(void) MAX_MAX_AGE_TO_CACHE); } +#ifdef TOR_UNIT_TESTS +/** As consdiffmgr_add_consensus, but requires a nul-terminated input. For + * testing. */ +int +consdiffmgr_add_consensus_nulterm(const char *consensus, + const networkstatus_t *as_parsed) +{ + size_t len = strlen(consensus); + /* make a non-nul-terminated copy so that we can have a better chance + * of catching errors. */ + char *ctmp = tor_memdup(consensus, len); + int r = consdiffmgr_add_consensus(ctmp, len, as_parsed); + tor_free(ctmp); + return r; +} +#endif /* defined(TOR_UNIT_TESTS) */ + /** - * Given a string containing a networkstatus consensus, and the results of + * Given a buffer containing a networkstatus consensus, and the results of * having parsed that consensus, add that consensus to the cache if it is not * already present and not too old. Create new consensus diffs from or to * that consensus as appropriate. @@ -519,6 +537,7 @@ get_max_age_to_cache(void) */ int consdiffmgr_add_consensus(const char *consensus, + size_t consensus_len, const networkstatus_t *as_parsed) { if (BUG(consensus == NULL) || BUG(as_parsed == NULL)) @@ -544,7 +563,7 @@ consdiffmgr_add_consensus(const char *consensus, } /* We don't have it. Add it to the cache. */ - return consensus_queue_compression_work(consensus, as_parsed); + return consensus_queue_compression_work(consensus, consensus_len, as_parsed); } /** @@ -825,7 +844,7 @@ consdiffmgr_configure(const consdiff_cfg_t *cfg) * operations that the consensus diff manager will need. */ int -consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg) +consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem_t **cfg) { return consensus_cache_register_with_sandbox(cdm_cache_get(), cfg); } @@ -1274,7 +1293,7 @@ typedef struct compressed_result_t { /** * Compress the bytestring <b>input</b> of length <b>len</b> using the - * <n>n_methods</b> compression methods listed in the array <b>methods</b>. + * <b>n_methods</b> compression methods listed in the array <b>methods</b>. * * For each successful compression, set the fields in the <b>results_out</b> * array in the position corresponding to the compression method. Use @@ -1387,19 +1406,21 @@ typedef struct consensus_diff_worker_job_t { } consensus_diff_worker_job_t; /** Given a consensus_cache_entry_t, check whether it has a label claiming - * that it was compressed. If so, uncompress its contents into <b>out</b> and - * set <b>outlen</b> to hold their size. If not, just copy the body into - * <b>out</b> and set <b>outlen</b> to its length. Return 0 on success, - * -1 on failure. - * - * In all cases, the output is nul-terminated. */ + * that it was compressed. If so, uncompress its contents into *<b>out</b> and + * set <b>outlen</b> to hold their size, and set *<b>owned_out</b> to a pointer + * that the caller will need to free. If not, just set *<b>out</b> and + * <b>outlen</b> to its extent in memory. Return 0 on success, -1 on failure. + **/ STATIC int -uncompress_or_copy(char **out, size_t *outlen, - consensus_cache_entry_t *ent) +uncompress_or_set_ptr(const char **out, size_t *outlen, + char **owned_out, + consensus_cache_entry_t *ent) { const uint8_t *body; size_t bodylen; + *owned_out = NULL; + if (consensus_cache_entry_get_body(ent, &body, &bodylen) < 0) return -1; @@ -1410,8 +1431,17 @@ uncompress_or_copy(char **out, size_t *outlen, if (lv_compression) method = compression_method_get_by_name(lv_compression); - return tor_uncompress(out, outlen, (const char *)body, bodylen, + int rv; + if (method == NO_METHOD) { + *out = (const char *)body; + *outlen = bodylen; + rv = 0; + } else { + rv = tor_uncompress(owned_out, outlen, (const char *)body, bodylen, method, 1, LOG_WARN); + *out = *owned_out; + } + return rv; } /** @@ -1478,16 +1508,17 @@ consensus_diff_worker_threadfn(void *state_, void *work_) char *consensus_diff; { - char *diff_from_nt = NULL, *diff_to_nt = NULL; + const char *diff_from_nt = NULL, *diff_to_nt = NULL; + char *owned1 = NULL, *owned2 = NULL; size_t diff_from_nt_len, diff_to_nt_len; - if (uncompress_or_copy(&diff_from_nt, &diff_from_nt_len, - job->diff_from) < 0) { + if (uncompress_or_set_ptr(&diff_from_nt, &diff_from_nt_len, &owned1, + job->diff_from) < 0) { return WQ_RPL_REPLY; } - if (uncompress_or_copy(&diff_to_nt, &diff_to_nt_len, - job->diff_to) < 0) { - tor_free(diff_from_nt); + if (uncompress_or_set_ptr(&diff_to_nt, &diff_to_nt_len, &owned2, + job->diff_to) < 0) { + tor_free(owned1); return WQ_RPL_REPLY; } tor_assert(diff_from_nt); @@ -1496,9 +1527,12 @@ consensus_diff_worker_threadfn(void *state_, void *work_) // XXXX ugh; this is going to calculate the SHA3 of both its // XXXX inputs again, even though we already have that. Maybe it's time // XXXX to change the API here? - consensus_diff = consensus_diff_generate(diff_from_nt, diff_to_nt); - tor_free(diff_from_nt); - tor_free(diff_to_nt); + consensus_diff = consensus_diff_generate(diff_from_nt, + diff_from_nt_len, + diff_to_nt, + diff_to_nt_len); + tor_free(owned1); + tor_free(owned2); } if (!consensus_diff) { /* Couldn't generate consensus; we'll leave the reply blank. */ @@ -1746,8 +1780,8 @@ consensus_compress_worker_threadfn(void *state_, void *work_) (const uint8_t *)consensus, bodylen); { const char *start, *end; - if (router_get_networkstatus_v3_signed_boundaries(consensus, - &start, &end) < 0) { + if (router_get_networkstatus_v3_signed_boundaries(consensus, bodylen, + &start, &end) < 0) { start = consensus; end = consensus+bodylen; } @@ -1811,14 +1845,15 @@ static int background_compression = 0; */ static int consensus_queue_compression_work(const char *consensus, + size_t consensus_len, const networkstatus_t *as_parsed) { tor_assert(consensus); tor_assert(as_parsed); consensus_compress_worker_job_t *job = tor_malloc_zero(sizeof(*job)); - job->consensus = tor_strdup(consensus); - job->consensus_len = strlen(consensus); + job->consensus = tor_memdup_nulterm(consensus, consensus_len); + job->consensus_len = strlen(job->consensus); job->flavor = as_parsed->flavor; char va_str[ISO_TIME_LEN+1]; diff --git a/src/feature/dircache/consdiffmgr.h b/src/feature/dircache/consdiffmgr.h index 39e8fa31cb..27b8165e94 100644 --- a/src/feature/dircache/consdiffmgr.h +++ b/src/feature/dircache/consdiffmgr.h @@ -1,6 +1,11 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file consdiffmgr.h + * @brief Header for consdiffmgr.c + **/ + #ifndef TOR_CONSDIFFMGR_H #define TOR_CONSDIFFMGR_H @@ -22,6 +27,7 @@ typedef struct consdiff_cfg_t { struct consensus_cache_entry_t; // from conscache.h int consdiffmgr_add_consensus(const char *consensus, + size_t consensus_len, const networkstatus_t *as_parsed); consdiff_status_t consdiffmgr_find_consensus( @@ -54,22 +60,30 @@ void consdiffmgr_rescan(void); int consdiffmgr_cleanup(void); void consdiffmgr_enable_background_compression(void); void consdiffmgr_configure(const consdiff_cfg_t *cfg); -struct sandbox_cfg_elem; -int consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg); +struct sandbox_cfg_elem_t; +int consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem_t **cfg); void consdiffmgr_free_all(void); int consdiffmgr_validate(void); #ifdef CONSDIFFMGR_PRIVATE +struct consensus_cache_t; +struct consensus_cache_entry_t; STATIC unsigned n_diff_compression_methods(void); STATIC unsigned n_consensus_compression_methods(void); -STATIC consensus_cache_t *cdm_cache_get(void); -STATIC consensus_cache_entry_t *cdm_cache_lookup_consensus( +STATIC struct consensus_cache_t *cdm_cache_get(void); +STATIC struct consensus_cache_entry_t *cdm_cache_lookup_consensus( consensus_flavor_t flavor, time_t valid_after); STATIC int cdm_entry_get_sha3_value(uint8_t *digest_out, - consensus_cache_entry_t *ent, + struct consensus_cache_entry_t *ent, const char *label); -STATIC int uncompress_or_copy(char **out, size_t *outlen, - consensus_cache_entry_t *ent); +STATIC int uncompress_or_set_ptr(const char **out, size_t *outlen, + char **owned_out, + struct consensus_cache_entry_t *ent); #endif /* defined(CONSDIFFMGR_PRIVATE) */ +#ifdef TOR_UNIT_TESTS +int consdiffmgr_add_consensus_nulterm(const char *consensus, + const networkstatus_t *as_parsed); +#endif + #endif /* !defined(TOR_CONSDIFFMGR_H) */ diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index e8cb284165..3b8775968a 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -1,8 +1,13 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file dircache.c + * @brief Cache directories and serve them to clients. + **/ + #define DIRCACHE_PRIVATE #include "core/or/or.h" @@ -23,6 +28,7 @@ #include "feature/nodelist/authcert.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/routerlist.h" +#include "feature/relay/relay_config.h" #include "feature/relay/routermode.h" #include "feature/rend/rendcache.h" #include "feature/stats/geoip_stats.h" @@ -49,7 +55,8 @@ #define ROUTERDESC_BY_DIGEST_CACHE_LIFETIME (48*60*60) #define ROBOTS_CACHE_LIFETIME (24*60*60) #define MICRODESC_CACHE_LIFETIME (48*60*60) - +/* Bandwidth files change every hour. */ +#define BANDWIDTH_CACHE_LIFETIME (30*60) /** Parse an HTTP request string <b>headers</b> of the form * \verbatim * "\%s [http[s]://]\%s HTTP/1..." @@ -123,7 +130,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, long cache_lifetime) { char date[RFC1123_TIME_LEN+1]; - time_t now = time(NULL); + time_t now = approx_time(); buf_t *buf = buf_new_with_capacity(1024); tor_assert(conn); @@ -166,22 +173,16 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, buf_free(buf); } -/** As write_http_response_header_impl, but sets encoding and content-typed - * based on whether the response will be <b>compressed</b> or not. */ +/** As write_http_response_header_impl, but translates method into + * encoding */ static void write_http_response_headers(dir_connection_t *conn, ssize_t length, compress_method_t method, const char *extra_headers, long cache_lifetime) { - const char *methodname = compression_method_get_name(method); - const char *doctype; - if (method == NO_METHOD) - doctype = "text/plain"; - else - doctype = "application/octet-stream"; write_http_response_header_impl(conn, length, - doctype, - methodname, + "text/plain", + compression_method_get_name(method), extra_headers, cache_lifetime); } @@ -333,7 +334,7 @@ typedef struct get_handler_args_t { * an arguments structure, and must return 0 on success or -1 if we should * close the connection. **/ -typedef struct url_table_ent_s { +typedef struct url_table_ent_t { const char *string; int is_prefix; int (*handler)(dir_connection_t *conn, const get_handler_args_t *args); @@ -357,12 +358,15 @@ static int handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args); static int handle_get_networkstatus_bridges(dir_connection_t *conn, const get_handler_args_t *args); +static int handle_get_next_bandwidth(dir_connection_t *conn, + const get_handler_args_t *args); /** Table for handling GET requests. */ static const url_table_ent_t url_table[] = { { "/tor/", 0, handle_get_frontpage }, { "/tor/status-vote/current/consensus", 1, handle_get_current_consensus }, { "/tor/status-vote/current/", 1, handle_get_status_vote }, + { "/tor/status-vote/next/bandwidth", 0, handle_get_next_bandwidth }, { "/tor/status-vote/next/", 1, handle_get_status_vote }, { "/tor/micro/d/", 1, handle_get_microdesc }, { "/tor/server/", 1, handle_get_descriptor }, @@ -475,7 +479,7 @@ static int handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) { (void) args; /* unused */ - const char *frontpage = get_dirportfrontpage(); + const char *frontpage = relay_get_dirportfrontpage(); if (frontpage) { size_t dlen; @@ -495,28 +499,47 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) } /** Warn that the cached consensus <b>consensus</b> of type - * <b>flavor</b> is too old and will not be served to clients. Rate-limit the - * warning to avoid logging an entry on every request. + * <b>flavor</b> too new or too old, based on <b>is_too_new</b>, + * and will not be served to clients. Rate-limit the warning to avoid logging + * an entry on every request. */ static void -warn_consensus_is_too_old(const struct consensus_cache_entry_t *consensus, - const char *flavor, time_t now) +warn_consensus_is_not_reasonably_live( + const struct consensus_cache_entry_t *consensus, + const char *flavor, time_t now, bool is_too_new) { -#define TOO_OLD_WARNING_INTERVAL (60*60) - static ratelim_t warned = RATELIM_INIT(TOO_OLD_WARNING_INTERVAL); +#define NOT_REASONABLY_LIVE_WARNING_INTERVAL (60*60) + static ratelim_t warned[2] = { RATELIM_INIT( + NOT_REASONABLY_LIVE_WARNING_INTERVAL), + RATELIM_INIT( + NOT_REASONABLY_LIVE_WARNING_INTERVAL) }; char timestamp[ISO_TIME_LEN+1]; - time_t valid_until; - char *dupes; + /* valid_after if is_too_new, valid_until if !is_too_new */ + time_t valid_time = 0; + char *dupes = NULL; - if (consensus_cache_entry_get_valid_until(consensus, &valid_until)) - return; - - if ((dupes = rate_limit_log(&warned, now))) { - format_local_iso_time(timestamp, valid_until); - log_warn(LD_DIRSERV, "Our %s%sconsensus is too old, so we will not " - "serve it to clients. It was valid until %s local time and we " - "continued to serve it for up to 24 hours after it expired.%s", - flavor ? flavor : "", flavor ? " " : "", timestamp, dupes); + if (is_too_new) { + if (consensus_cache_entry_get_valid_after(consensus, &valid_time)) + return; + dupes = rate_limit_log(&warned[1], now); + } else { + if (consensus_cache_entry_get_valid_until(consensus, &valid_time)) + return; + dupes = rate_limit_log(&warned[0], now); + } + + if (dupes) { + format_local_iso_time(timestamp, valid_time); + log_warn(LD_DIRSERV, "Our %s%sconsensus is too %s, so we will not " + "serve it to clients. It was valid %s %s local time and we " + "continued to serve it for up to 24 hours %s.%s", + flavor ? flavor : "", + flavor ? " " : "", + is_too_new ? "new" : "old", + is_too_new ? "after" : "until", + timestamp, + is_too_new ? "before it was valid" : "after it expired", + dupes); tor_free(dupes); } } @@ -543,7 +566,7 @@ parse_one_diff_hash(uint8_t *digest, const char *hex, const char *location, } /** If there is an X-Or-Diff-From-Consensus header included in <b>headers</b>, - * set <b>digest_out<b> to a new smartlist containing every 256-bit + * set <b>digest_out</b> to a new smartlist containing every 256-bit * hex-encoded digest listed in that header and return 0. Otherwise return * -1. */ static int @@ -859,7 +882,6 @@ handle_get_current_consensus(dir_connection_t *conn, if (req.diff_only && !cached_consensus) { write_short_http_response(conn, 404, "No such diff available"); - // XXXX warn_consensus_is_too_old(v, req.flavor, now); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; } @@ -870,19 +892,30 @@ handle_get_current_consensus(dir_connection_t *conn, &compression_used); } - time_t fresh_until, valid_until; - int have_fresh_until = 0, have_valid_until = 0; + time_t valid_after, fresh_until, valid_until; + int have_valid_after = 0, have_fresh_until = 0, have_valid_until = 0; if (cached_consensus) { + have_valid_after = + !consensus_cache_entry_get_valid_after(cached_consensus, &valid_after); have_fresh_until = !consensus_cache_entry_get_fresh_until(cached_consensus, &fresh_until); have_valid_until = !consensus_cache_entry_get_valid_until(cached_consensus, &valid_until); } - if (cached_consensus && have_valid_until && + if (cached_consensus && have_valid_after && + !networkstatus_valid_after_is_reasonably_live(valid_after, now)) { + write_short_http_response(conn, 404, "Consensus is too new"); + warn_consensus_is_not_reasonably_live(cached_consensus, req.flavor, now, + 1); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } else if ( + cached_consensus && have_valid_until && !networkstatus_valid_until_is_reasonably_live(valid_until, now)) { write_short_http_response(conn, 404, "Consensus is too old"); - warn_consensus_is_too_old(cached_consensus, req.flavor, now); + warn_consensus_is_not_reasonably_live(cached_consensus, req.flavor, now, + 0); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; } @@ -924,7 +957,7 @@ handle_get_current_consensus(dir_connection_t *conn, goto done; } - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_debug(LD_DIRSERV, "Client asked for network status lists, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1033,7 +1066,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) } }); - if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), estimated_len)) { write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } @@ -1045,13 +1078,11 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) if (compress_method != NO_METHOD) { conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(estimated_len)); - SMARTLIST_FOREACH(items, const char *, c, - connection_buf_add_compress(c, strlen(c), conn, 0)); - connection_buf_add_compress("", 0, conn, 1); - } else { - SMARTLIST_FOREACH(items, const char *, c, - connection_buf_add(c, strlen(c), TO_CONN(conn))); } + + SMARTLIST_FOREACH(items, const char *, c, + connection_dir_buf_add(c, strlen(c), conn, + c_sl_idx == c_sl_len - 1)); } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, connection_buf_add(compress_method != NO_METHOD ? @@ -1094,7 +1125,7 @@ handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) write_short_http_response(conn, 404, "Not found"); goto done; } - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1192,7 +1223,7 @@ handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) msg = "Not found"; write_short_http_response(conn, 404, msg); } else { - if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), size_guess)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); @@ -1288,9 +1319,8 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) SMARTLIST_FOREACH(certs, authority_cert_t *, c, len += c->cache_info.signed_descriptor_len); - if (global_write_bucket_low(TO_CONN(conn), - compress_method != NO_METHOD ? len/2 : len, - 2)) { + if (connection_dir_is_global_write_low(TO_CONN(conn), + compress_method != NO_METHOD ? len/2 : len)) { write_short_http_response(conn, 503, "Directory busy, try again later"); goto keys_done; } @@ -1302,19 +1332,13 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) if (compress_method != NO_METHOD) { conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(len)); - SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_buf_add_compress( - c->cache_info.signed_descriptor_body, - c->cache_info.signed_descriptor_len, - conn, 0)); - connection_buf_add_compress("", 0, conn, 1); - } else { - SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_buf_add(c->cache_info.signed_descriptor_body, - c->cache_info.signed_descriptor_len, - TO_CONN(conn))); } - keys_done: + + SMARTLIST_FOREACH(certs, authority_cert_t *, c, + connection_dir_buf_add(c->cache_info.signed_descriptor_body, + c->cache_info.signed_descriptor_len, + conn, c_sl_idx == c_sl_len - 1)); + keys_done: smartlist_free(certs); goto done; } @@ -1360,7 +1384,7 @@ handle_get_hs_descriptor_v2(dir_connection_t *conn, return 0; } -/** Helper function for GET /tor/hs/3/<z>. Only for version 3. +/** Helper function for GET `/tor/hs/3/...`. Only for version 3. */ STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, @@ -1371,9 +1395,11 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, const char *pubkey_str = NULL; const char *url = args->url; - /* Reject unencrypted dir connections */ - if (!connection_dir_is_encrypted(conn)) { - write_short_http_response(conn, 404, "Not found"); + /* Reject non anonymous dir connections (which also tests if encrypted). We + * do not allow single hop clients to query an HSDir. */ + if (!connection_dir_is_anonymous(conn)) { + write_short_http_response(conn, 503, + "Rejecting single hop HS v3 descriptor request"); goto done; } @@ -1438,6 +1464,39 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, return 0; } +/** Helper function for GET the bandwidth file used for the next vote */ +static int +handle_get_next_bandwidth(dir_connection_t *conn, + const get_handler_args_t *args) +{ + log_debug(LD_DIR, "Getting next bandwidth."); + const or_options_t *options = get_options(); + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 1); + + if (options->V3BandwidthsFile) { + char *bandwidth = read_file_to_str(options->V3BandwidthsFile, + RFTS_IGNORE_MISSING, NULL); + if (bandwidth != NULL) { + ssize_t len = strlen(bandwidth); + write_http_response_header(conn, compress_method != NO_METHOD ? -1 : len, + compress_method, BANDWIDTH_CACHE_LIFETIME); + if (compress_method != NO_METHOD) { + conn->compress_state = tor_compress_new(1, compress_method, + choose_compression_level(len/2)); + log_debug(LD_DIR, "Compressing bandwidth file."); + } else { + log_debug(LD_DIR, "Not compressing bandwidth file."); + } + connection_dir_buf_add((const char*)bandwidth, len, conn, 1); + tor_free(bandwidth); + return 0; + } + } + write_short_http_response(conn, 404, "Not found"); + return 0; +} + /** Helper function for GET robots.txt or /tor/robots.txt */ static int handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) @@ -1580,10 +1639,15 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, goto done; } - /* Handle HS descriptor publish request. */ - /* XXX: This should be disabled with a consensus param until we want to - * the prop224 be deployed and thus use. */ - if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) { + /* Handle HS descriptor publish request. We force an anonymous connection + * (which also tests for encrypted). We do not allow single-hop client to + * post a descriptor onto an HSDir. */ + if (!strcmpstart(url, "/tor/hs/")) { + if (!connection_dir_is_anonymous(conn)) { + write_short_http_response(conn, 503, + "Rejecting single hop HS descriptor post"); + goto done; + } const char *msg = "HS descriptor stored successfully."; /* We most probably have a publish request for an HS descriptor. */ @@ -1608,8 +1672,8 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, const char *msg = "[None]"; uint8_t purpose = authdir_mode_bridge(options) ? ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL; - was_router_added_t r = dirserv_add_multiple_descriptors(body, purpose, - conn->base_.address, &msg); + was_router_added_t r = dirserv_add_multiple_descriptors(body, body_len, + purpose, conn->base_.address, &msg); tor_assert(msg); if (r == ROUTER_ADDED_SUCCESSFULLY) { diff --git a/src/feature/dircache/dircache.h b/src/feature/dircache/dircache.h index 236ea649ef..d6392e2d42 100644 --- a/src/feature/dircache/dircache.h +++ b/src/feature/dircache/dircache.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -38,6 +38,6 @@ STATIC int parse_hs_version_from_post(const char *url, const char *prefix, const char **end_pos); STATIC unsigned parse_accept_encoding_header(const char *h); -#endif +#endif /* defined(DIRCACHE_PRIVATE) */ #endif /* !defined(TOR_DIRCACHE_H) */ diff --git a/src/feature/dircache/dircache_stub.c b/src/feature/dircache/dircache_stub.c new file mode 100644 index 0000000000..87811597d1 --- /dev/null +++ b/src/feature/dircache/dircache_stub.c @@ -0,0 +1,78 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dircache_stub.c + * @brief Stub declarations for use when dircache module is disabled. + **/ + +#include "core/or/or.h" +#include "feature/dircache/consdiffmgr.h" +#include "feature/dircache/dircache.h" +#include "feature/dircache/dirserv.h" +#include "feature/dircommon/dir_connection_st.h" + +int +directory_handle_command(dir_connection_t *conn) +{ + (void) conn; + tor_assert_nonfatal_unreached_once(); + return -1; +} + +int +connection_dirserv_flushed_some(dir_connection_t *conn) +{ + (void) conn; + tor_assert_nonfatal_unreached_once(); + return -1; +} + +void +dir_conn_clear_spool(dir_connection_t *conn) +{ + if (!conn) + return; + tor_assert_nonfatal_once(conn->spool == NULL); +} + +void +consdiffmgr_enable_background_compression(void) +{ +} + +int +consdiffmgr_add_consensus(const char *consensus, + size_t consensus_len, + const networkstatus_t *as_parsed) +{ + (void)consensus; + (void)consensus_len; + (void)as_parsed; + return 0; +} + +int +consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem_t **cfg) +{ + (void)cfg; + return 0; +} + +int +consdiffmgr_cleanup(void) +{ + return 0; +} + +void +consdiffmgr_free_all(void) +{ +} + +void +dirserv_free_all(void) +{ +} diff --git a/src/feature/dircache/dirserv.c b/src/feature/dircache/dirserv.c index 213c490314..fb8db879a4 100644 --- a/src/feature/dircache/dirserv.c +++ b/src/feature/dircache/dirserv.c @@ -1,9 +1,8 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define DIRSERV_PRIVATE #include "core/or/or.h" #include "app/config/config.h" @@ -69,55 +68,7 @@ static cached_dir_t *lookup_cached_dir_by_fp(const uint8_t *fp); /********************************************************************/ /* A set of functions to answer questions about how we'd like to behave - * as a directory mirror/client. */ - -/** Return 1 if we fetch our directory material directly from the - * authorities, rather than from a mirror. */ -int -directory_fetches_from_authorities(const or_options_t *options) -{ - const routerinfo_t *me; - uint32_t addr; - int refuseunknown; - if (options->FetchDirInfoEarly) - return 1; - if (options->BridgeRelay == 1) - return 0; - if (server_mode(options) && - router_pick_published_address(options, &addr, 1) < 0) - return 1; /* we don't know our IP address; ask an authority. */ - refuseunknown = ! router_my_exit_policy_is_reject_star() && - should_refuse_unknown_exits(options); - if (!dir_server_mode(options) && !refuseunknown) - return 0; - if (!server_mode(options) || !advertised_server_mode()) - return 0; - me = router_get_my_routerinfo(); - if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown)) - return 0; /* if we don't service directory requests, return 0 too */ - return 1; -} - -/** Return 1 if we should fetch new networkstatuses, descriptors, etc - * on the "mirror" schedule rather than the "client" schedule. - */ -int -directory_fetches_dir_info_early(const or_options_t *options) -{ - return directory_fetches_from_authorities(options); -} - -/** Return 1 if we should fetch new networkstatuses, descriptors, etc - * on a very passive schedule -- waiting long enough for ordinary clients - * to probably have the info we want. These would include bridge users, - * and maybe others in the future e.g. if a Tor client uses another Tor - * client as a directory guard. - */ -int -directory_fetches_dir_info_later(const or_options_t *options) -{ - return options->UseBridges != 0; -} + * as a directory mirror */ /** Return true iff we want to serve certificates for authorities * that we don't acknowledge as authorities ourself. @@ -161,19 +112,6 @@ directory_permits_begindir_requests(const or_options_t *options) return options->BridgeRelay != 0 || dir_server_mode(options); } -/** Return 1 if we have no need to fetch new descriptors. This generally - * happens when we're not a dir cache and we haven't built any circuits - * lately. - */ -int -directory_too_idle_to_fetch_descriptors(const or_options_t *options, - time_t now) -{ - return !directory_caches_dir_info(options) && - !options->FetchUselessDescriptors && - rep_hist_circbuilding_dormant(now); -} - /********************************************************************/ /** Map from flavor name to the cached_dir_t for the v3 consensuses that we're @@ -234,6 +172,7 @@ free_cached_dir_(void *_d) * validation is performed. */ void dirserv_set_cached_consensus_networkstatus(const char *networkstatus, + size_t networkstatus_len, const char *flavor_name, const common_digests_t *digests, const uint8_t *sha3_as_signed, @@ -244,7 +183,9 @@ dirserv_set_cached_consensus_networkstatus(const char *networkstatus, if (!cached_consensuses) cached_consensuses = strmap_new(); - new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published); + new_networkstatus = + new_cached_dir(tor_memdup_nulterm(networkstatus, networkstatus_len), + published); memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t)); memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed, DIGEST256_LEN); @@ -256,14 +197,45 @@ dirserv_set_cached_consensus_networkstatus(const char *networkstatus, /** Return the latest downloaded consensus networkstatus in encoded, signed, * optionally compressed format, suitable for sending to clients. */ -cached_dir_t * -dirserv_get_consensus(const char *flavor_name) +MOCK_IMPL(cached_dir_t *, +dirserv_get_consensus,(const char *flavor_name)) { if (!cached_consensuses) return NULL; return strmap_get(cached_consensuses, flavor_name); } +/** As dir_split_resource_into_fingerprints, but instead fills + * <b>spool_out</b> with a list of spoolable_resource_t for the resource + * identified through <b>source</b>. */ +int +dir_split_resource_into_spoolable(const char *resource, + dir_spool_source_t source, + smartlist_t *spool_out, + int *compressed_out, + int flags) +{ + smartlist_t *fingerprints = smartlist_new(); + + tor_assert(flags & (DSR_HEX|DSR_BASE64)); + const size_t digest_len = + (flags & DSR_DIGEST256) ? DIGEST256_LEN : DIGEST_LEN; + + int r = dir_split_resource_into_fingerprints(resource, fingerprints, + compressed_out, flags); + /* This is not a very efficient implementation XXXX */ + SMARTLIST_FOREACH_BEGIN(fingerprints, uint8_t *, digest) { + spooled_resource_t *spooled = + spooled_resource_new(source, digest, digest_len); + if (spooled) + smartlist_add(spool_out, spooled); + tor_free(digest); + } SMARTLIST_FOREACH_END(digest); + + smartlist_free(fingerprints); + return r; +} + /** As dirserv_get_routerdescs(), but instead of getting signed_descriptor_t * pointers, adds copies of digests to fps_out, and doesn't use the * /tor/server/ prefix. For a /d/ request, adds descriptor digests; for other @@ -330,87 +302,6 @@ dirserv_get_routerdesc_spool(smartlist_t *spool_out, return 0; } -/** Add a signed_descriptor_t to <b>descs_out</b> for each router matching - * <b>key</b>. The key should be either - * - "/tor/server/authority" for our own routerinfo; - * - "/tor/server/all" for all the routerinfos we have, concatenated; - * - "/tor/server/fp/FP" where FP is a plus-separated sequence of - * hex identity digests; or - * - "/tor/server/d/D" where D is a plus-separated sequence - * of server descriptor digests, in hex. - * - * Return 0 if we found some matching descriptors, or -1 if we do not - * have any descriptors, no matching descriptors, or if we did not - * recognize the key (URL). - * If -1 is returned *<b>msg</b> will be set to an appropriate error - * message. - * - * XXXX rename this function. It's only called from the controller. - * XXXX in fact, refactor this function, merging as much as possible. - */ -int -dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, - const char **msg) -{ - *msg = NULL; - - if (!strcmp(key, "/tor/server/all")) { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, - smartlist_add(descs_out, &(r->cache_info))); - } else if (!strcmp(key, "/tor/server/authority")) { - const routerinfo_t *ri = router_get_my_routerinfo(); - if (ri) - smartlist_add(descs_out, (void*) &(ri->cache_info)); - } else if (!strcmpstart(key, "/tor/server/d/")) { - smartlist_t *digests = smartlist_new(); - key += strlen("/tor/server/d/"); - dir_split_resource_into_fingerprints(key, digests, NULL, - DSR_HEX|DSR_SORT_UNIQ); - SMARTLIST_FOREACH(digests, const char *, d, - { - signed_descriptor_t *sd = router_get_by_descriptor_digest(d); - if (sd) - smartlist_add(descs_out,sd); - }); - SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); - smartlist_free(digests); - } else if (!strcmpstart(key, "/tor/server/fp/")) { - smartlist_t *digests = smartlist_new(); - time_t cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH; - key += strlen("/tor/server/fp/"); - dir_split_resource_into_fingerprints(key, digests, NULL, - DSR_HEX|DSR_SORT_UNIQ); - SMARTLIST_FOREACH_BEGIN(digests, const char *, d) { - if (router_digest_is_me(d)) { - /* calling router_get_my_routerinfo() to make sure it exists */ - const routerinfo_t *ri = router_get_my_routerinfo(); - if (ri) - smartlist_add(descs_out, (void*) &(ri->cache_info)); - } else { - const routerinfo_t *ri = router_get_by_id_digest(d); - /* Don't actually serve a descriptor that everyone will think is - * expired. This is an (ugly) workaround to keep buggy 0.1.1.10 - * Tors from downloading descriptors that they will throw away. - */ - if (ri && ri->cache_info.published_on > cutoff) - smartlist_add(descs_out, (void*) &(ri->cache_info)); - } - } SMARTLIST_FOREACH_END(d); - SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); - smartlist_free(digests); - } else { - *msg = "Key not recognized"; - return -1; - } - - if (!smartlist_len(descs_out)) { - *msg = "Servers unavailable"; - return -1; - } - return 0; -} - /* ========== * Spooling code. * ========== */ @@ -580,11 +471,9 @@ spooled_resource_flush_some(spooled_resource_t *spooled, /* Absent objects count as "done". */ return SRFS_DONE; } - if (conn->compress_state) { - connection_buf_add_compress((const char*)body, bodylen, conn, 0); - } else { - connection_buf_add((const char*)body, bodylen, TO_CONN(conn)); - } + + connection_dir_buf_add((const char*)body, bodylen, conn, 0); + return SRFS_DONE; } else { cached_dir_t *cached = spooled->cached_dir_ref; @@ -619,14 +508,10 @@ spooled_resource_flush_some(spooled_resource_t *spooled, if (BUG(remaining < 0)) return SRFS_ERR; ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining); - if (conn->compress_state) { - connection_buf_add_compress( - ptr + spooled->cached_dir_offset, - bytes, conn, 0); - } else { - connection_buf_add(ptr + spooled->cached_dir_offset, - bytes, TO_CONN(conn)); - } + + connection_dir_buf_add(ptr + spooled->cached_dir_offset, + bytes, conn, 0); + spooled->cached_dir_offset += bytes; if (spooled->cached_dir_offset >= (off_t)total_len) { return SRFS_DONE; diff --git a/src/feature/dircache/dirserv.h b/src/feature/dircache/dirserv.h index 890b10fd80..73a64b1b7e 100644 --- a/src/feature/dircache/dirserv.h +++ b/src/feature/dircache/dirserv.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -73,28 +73,52 @@ typedef struct spooled_resource_t { int connection_dirserv_flushed_some(dir_connection_t *conn); -int directory_fetches_from_authorities(const or_options_t *options); -int directory_fetches_dir_info_early(const or_options_t *options); -int directory_fetches_dir_info_later(const or_options_t *options); +enum dir_spool_source_t; +int dir_split_resource_into_spoolable(const char *resource, + enum dir_spool_source_t source, + smartlist_t *spool_out, + int *compressed_out, + int flags); + +#ifdef HAVE_MODULE_DIRCACHE +/** Is the dircache module enabled? */ +#define have_module_dircache() (1) int directory_caches_unknown_auth_certs(const or_options_t *options); int directory_caches_dir_info(const or_options_t *options); int directory_permits_begindir_requests(const or_options_t *options); -int directory_too_idle_to_fetch_descriptors(const or_options_t *options, - time_t now); - -cached_dir_t *dirserv_get_consensus(const char *flavor_name); +MOCK_DECL(cached_dir_t *, dirserv_get_consensus, (const char *flavor_name)); void dirserv_set_cached_consensus_networkstatus(const char *consensus, + size_t consensus_len, const char *flavor_name, const common_digests_t *digests, const uint8_t *sha3_as_signed, time_t published); +#else /* !defined(HAVE_MODULE_DIRCACHE) */ +#define have_module_dircache() (0) +#define directory_caches_unknown_auth_certs(opt) \ + ((void)(opt), 0) +#define directory_caches_dir_info(opt) \ + ((void)(opt), 0) +#define directory_permits_begindir_requests(opt) \ + ((void)(opt), 0) +#define dirserv_get_consensus(flav) \ + ((void)(flav), NULL) +#define dirserv_set_cached_consensus_networkstatus(a,b,c,d,e,f) \ + STMT_BEGIN { \ + (void)(a); \ + (void)(b); \ + (void)(c); \ + (void)(d); \ + (void)(e); \ + (void)(f); \ + } STMT_END +#endif /* defined(HAVE_MODULE_DIRCACHE) */ + void dirserv_clear_old_networkstatuses(time_t cutoff); int dirserv_get_routerdesc_spool(smartlist_t *spools_out, const char *key, dir_spool_source_t source, int conn_is_encrypted, const char **msg_out); -int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, - const char **msg); void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); diff --git a/src/feature/dircache/feature_dircache.md b/src/feature/dircache/feature_dircache.md new file mode 100644 index 0000000000..97734f2a34 --- /dev/null +++ b/src/feature/dircache/feature_dircache.md @@ -0,0 +1,6 @@ +@dir /feature/dircache +@brief feature/dircache: Run as a directory cache server + +This module handles the directory caching functionality that all relays may +provide, for serving cached directory objects to objects. + diff --git a/src/feature/dircache/include.am b/src/feature/dircache/include.am new file mode 100644 index 0000000000..ab162565f7 --- /dev/null +++ b/src/feature/dircache/include.am @@ -0,0 +1,21 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +MODULE_DIRCACHE_SOURCES = \ + src/feature/dircache/conscache.c \ + src/feature/dircache/consdiffmgr.c \ + src/feature/dircache/dircache.c \ + src/feature/dircache/dirserv.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/dircache/cached_dir_st.h \ + src/feature/dircache/conscache.h \ + src/feature/dircache/consdiffmgr.h \ + src/feature/dircache/dircache.h \ + src/feature/dircache/dirserv.h + +if BUILD_MODULE_DIRCACHE +LIBTOR_APP_A_SOURCES += $(MODULE_DIRCACHE_SOURCES) +else +LIBTOR_APP_A_STUB_SOURCES += src/feature/dircache/dircache_stub.c +endif diff --git a/src/feature/dirclient/.may_include b/src/feature/dirclient/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/dirclient/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/dirclient/dir_server_st.h b/src/feature/dirclient/dir_server_st.h index 2f5706cdd9..37fa3148a7 100644 --- a/src/feature/dirclient/dir_server_st.h +++ b/src/feature/dirclient/dir_server_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file dir_server_st.h + * @brief Trusted/fallback directory server structure. + **/ + #ifndef DIR_SERVER_ST_H #define DIR_SERVER_ST_H @@ -51,4 +56,4 @@ struct dir_server_t { **/ }; -#endif +#endif /* !defined(DIR_SERVER_ST_H) */ diff --git a/src/feature/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c index 6725fc3369..1200c3c562 100644 --- a/src/feature/dirclient/dirclient.c +++ b/src/feature/dirclient/dirclient.c @@ -1,8 +1,13 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file dirclient.c + * @brief Download directory information + **/ + #define DIRCLIENT_PRIVATE #include "core/or/or.h" @@ -14,12 +19,13 @@ #include "core/or/policies.h" #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/shared_random.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/consdiff.h" #include "feature/dircommon/directory.h" @@ -448,7 +454,7 @@ directory_get_from_dirserver,( { const routerstatus_t *rs = NULL; const or_options_t *options = get_options(); - int prefer_authority = (directory_fetches_from_authorities(options) + int prefer_authority = (dirclient_fetches_from_authorities(options) || want_authority == DL_WANT_AUTHORITY); int require_authority = 0; int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose, @@ -667,7 +673,7 @@ directory_choose_address_routerstatus(const routerstatus_t *status, if (indirection == DIRIND_DIRECT_CONN || indirection == DIRIND_ANON_DIRPORT || (indirection == DIRIND_ONEHOP - && !directory_must_use_begindir(options))) { + && !dirclient_must_use_begindir(options))) { fascist_firewall_choose_address_rs(status, FIREWALL_DIR_CONNECTION, 0, use_dir_ap); have_dir = tor_addr_port_is_valid_ap(use_dir_ap, 0); @@ -866,16 +872,6 @@ connection_dir_download_cert_failed(dir_connection_t *conn, int status) update_certificate_downloads(time(NULL)); } -/* Should this tor instance only use begindir for all its directory requests? - */ -int -directory_must_use_begindir(const or_options_t *options) -{ - /* Clients, onion services, and bridges must use begindir, - * relays and authorities do not have to */ - return !public_server_mode(options); -} - /** Evaluate the situation and decide if we should use an encrypted * "begindir-style" connection for this directory request. * 0) If there is no DirPort, yes. @@ -927,7 +923,7 @@ directory_command_should_use_begindir(const or_options_t *options, } /* Reasons why we want to avoid using begindir */ if (indirection == DIRIND_ONEHOP) { - if (!directory_must_use_begindir(options)) { + if (!dirclient_must_use_begindir(options)) { *reason = "in relay mode"; return 0; } @@ -1289,7 +1285,7 @@ directory_initiate_request,(directory_request_t *request)) /* use encrypted begindir connections for everything except relays * this provides better protection for directory fetches */ - if (!use_begindir && directory_must_use_begindir(options)) { + if (!use_begindir && dirclient_must_use_begindir(options)) { log_warn(LD_BUG, "Client could not use begindir connection: %s", begindir_reason ? begindir_reason : "(NULL)"); return; @@ -2205,24 +2201,31 @@ handle_response_fetch_consensus(dir_connection_t *conn, if (looks_like_a_consensus_diff(body, body_len)) { /* First find our previous consensus. Maybe it's in ram, maybe not. */ cached_dir_t *cd = dirserv_get_consensus(flavname); - const char *consensus_body; - char *owned_consensus = NULL; + const char *consensus_body = NULL; + size_t consensus_body_len; + tor_mmap_t *mapped_consensus = NULL; if (cd) { consensus_body = cd->dir; + consensus_body_len = cd->dir_len; } else { - owned_consensus = networkstatus_read_cached_consensus(flavname); - consensus_body = owned_consensus; + mapped_consensus = networkstatus_map_cached_consensus(flavname); + if (mapped_consensus) { + consensus_body = mapped_consensus->data; + consensus_body_len = mapped_consensus->size; + } } if (!consensus_body) { log_warn(LD_DIR, "Received a consensus diff, but we can't find " "any %s-flavored consensus in our current cache.",flavname); + tor_munmap_file(mapped_consensus); networkstatus_consensus_download_failed(0, flavname); // XXXX if this happens too much, see below return -1; } - new_consensus = consensus_diff_apply(consensus_body, body); - tor_free(owned_consensus); + new_consensus = consensus_diff_apply(consensus_body, consensus_body_len, + body, body_len); + tor_munmap_file(mapped_consensus); if (new_consensus == NULL) { log_warn(LD_DIR, "Could not apply consensus diff received from server " "'%s:%d'", conn->base_.address, conn->base_.port); @@ -2244,7 +2247,9 @@ handle_response_fetch_consensus(dir_connection_t *conn, sourcename = "downloaded"; } - if ((r=networkstatus_set_current_consensus(consensus, flavname, 0, + if ((r=networkstatus_set_current_consensus(consensus, + strlen(consensus), + flavname, 0, conn->identity_digest))<0) { log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, "Unable to load %s consensus directory %s from " @@ -2522,7 +2527,7 @@ handle_response_fetch_microdesc(dir_connection_t *conn, conn->base_.port); tor_assert(conn->requested_resource && !strcmpstart(conn->requested_resource, "d/")); - tor_assert_nonfatal(!tor_mem_is_zero(conn->identity_digest, DIGEST_LEN)); + tor_assert_nonfatal(!fast_mem_is_zero(conn->identity_digest, DIGEST_LEN)); which = smartlist_new(); dir_split_resource_into_fingerprints(conn->requested_resource+2, which, NULL, @@ -2719,62 +2724,7 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))", (int)body_len, status_code, escaped(reason)); - switch (status_code) { - case 200: - /* We got something: Try storing it in the cache. */ - if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { - log_info(LD_REND, "Failed to store hidden service descriptor"); - /* Fire control port FAILED event. */ - hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, - "BAD_DESC"); - hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, - NULL); - } else { - log_info(LD_REND, "Stored hidden service descriptor successfully."); - TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; - hs_client_desc_has_arrived(conn->hs_ident); - /* Fire control port RECEIVED event. */ - hs_control_desc_event_received(conn->hs_ident, conn->identity_digest); - hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, - body); - } - break; - case 404: - /* Not there. We'll retry when connection_about_to_close_connection() - * tries to clean this conn up. */ - log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " - "Retrying at another directory."); - /* Fire control port FAILED event. */ - hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, - "NOT_FOUND"); - hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, - NULL); - break; - case 400: - log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " - "http status 400 (%s). Dirserver didn't like our " - "query? Retrying at another directory.", - escaped(reason)); - /* Fire control port FAILED event. */ - hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, - "QUERY_REJECTED"); - hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, - NULL); - break; - default: - log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " - "http status %d (%s) response unexpected from HSDir server " - "'%s:%d'. Retrying at another directory.", - status_code, escaped(reason), TO_CONN(conn)->address, - TO_CONN(conn)->port); - /* Fire control port FAILED event. */ - hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest, - "UNEXPECTED"); - hs_control_desc_event_content(conn->hs_ident, conn->identity_digest, - NULL); - break; - } - + hs_client_dir_fetch_done(conn, reason, body, status_code); return 0; } @@ -3134,7 +3084,7 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, { char digest[DIGEST_LEN]; time_t now = time(NULL); - int server = directory_fetches_from_authorities(get_options()); + int server = dirclient_fetches_from_authorities(get_options()); if (!was_descriptor_digests) { if (router_purpose == ROUTER_PURPOSE_BRIDGE) { tor_assert(!was_extrainfo); @@ -3179,7 +3129,7 @@ dir_microdesc_download_failed(smartlist_t *failed, routerstatus_t *rs; download_status_t *dls; time_t now = time(NULL); - int server = directory_fetches_from_authorities(get_options()); + int server = dirclient_fetches_from_authorities(get_options()); if (! consensus) return; diff --git a/src/feature/dirclient/dirclient.h b/src/feature/dirclient/dirclient.h index 1a93265dc3..08209721bb 100644 --- a/src/feature/dirclient/dirclient.h +++ b/src/feature/dirclient/dirclient.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -41,8 +41,6 @@ typedef enum { DIRIND_ANON_DIRPORT, } dir_indirection_t; -int directory_must_use_begindir(const or_options_t *options); - /** * A directory_request_t describes the information about a directory request * at the client side. It describes what we're going to ask for, which @@ -167,6 +165,6 @@ STATIC int handle_response_fetch_consensus(dir_connection_t *conn, STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, const char *resource); -#endif +#endif /* defined(DIRCLIENT_PRIVATE) */ #endif /* !defined(TOR_DIRCLIENT_H) */ diff --git a/src/feature/dirclient/dirclient_modes.c b/src/feature/dirclient/dirclient_modes.c new file mode 100644 index 0000000000..23fd1a2f6e --- /dev/null +++ b/src/feature/dirclient/dirclient_modes.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirclient_modes.c + * @brief Functions to answer questions about how we'd like to behave + * as a directory client + **/ + +#include "orconfig.h" + +#include "core/or/or.h" + +#include "feature/dirclient/dirclient_modes.h" +#include "feature/dircache/dirserv.h" +#include "feature/relay/router.h" +#include "feature/relay/routermode.h" +#include "feature/stats/predict_ports.h" + +#include "app/config/or_options_st.h" +#include "feature/nodelist/routerinfo_st.h" + +/* Should this tor instance only use begindir for all its directory requests? + */ +int +dirclient_must_use_begindir(const or_options_t *options) +{ + /* Clients, onion services, and bridges must use begindir, + * relays and authorities do not have to */ + return !public_server_mode(options); +} + +/** Return 1 if we fetch our directory material directly from the + * authorities, rather than from a mirror. */ +int +dirclient_fetches_from_authorities(const or_options_t *options) +{ + const routerinfo_t *me; + uint32_t addr; + int refuseunknown; + if (options->FetchDirInfoEarly) + return 1; + if (options->BridgeRelay == 1) + return 0; + if (server_mode(options) && + router_pick_published_address(options, &addr, 1) < 0) + return 1; /* we don't know our IP address; ask an authority. */ + refuseunknown = ! router_my_exit_policy_is_reject_star() && + should_refuse_unknown_exits(options); + if (!dir_server_mode(options) && !refuseunknown) + return 0; + if (!server_mode(options) || !advertised_server_mode()) + return 0; + me = router_get_my_routerinfo(); + if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown)) + return 0; /* if we don't service directory requests, return 0 too */ + return 1; +} + +/** Return 1 if we should fetch new networkstatuses, descriptors, etc + * on the "mirror" schedule rather than the "client" schedule. + */ +int +dirclient_fetches_dir_info_early(const or_options_t *options) +{ + return dirclient_fetches_from_authorities(options); +} + +/** Return 1 if we should fetch new networkstatuses, descriptors, etc + * on a very passive schedule -- waiting long enough for ordinary clients + * to probably have the info we want. These would include bridge users, + * and maybe others in the future e.g. if a Tor client uses another Tor + * client as a directory guard. + */ +int +dirclient_fetches_dir_info_later(const or_options_t *options) +{ + return options->UseBridges != 0; +} + +/** Return 1 if we have no need to fetch new descriptors. This generally + * happens when we're not a dir cache and we haven't built any circuits + * lately. + */ +int +dirclient_too_idle_to_fetch_descriptors(const or_options_t *options, + time_t now) +{ + return !directory_caches_dir_info(options) && + !options->FetchUselessDescriptors && + rep_hist_circbuilding_dormant(now); +} diff --git a/src/feature/dirclient/dirclient_modes.h b/src/feature/dirclient/dirclient_modes.h new file mode 100644 index 0000000000..c402207724 --- /dev/null +++ b/src/feature/dirclient/dirclient_modes.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file dirclient_modes.h + * @brief Header for feature/dirclient/dirclient_modes.c + **/ + +#ifndef TOR_FEATURE_DIRCLIENT_DIRCLIENT_MODES_H +#define TOR_FEATURE_DIRCLIENT_DIRCLIENT_MODES_H + +struct or_options_t; + +int dirclient_must_use_begindir(const or_options_t *options); +int dirclient_fetches_from_authorities(const struct or_options_t *options); +int dirclient_fetches_dir_info_early(const struct or_options_t *options); +int dirclient_fetches_dir_info_later(const struct or_options_t *options); +int dirclient_too_idle_to_fetch_descriptors(const struct or_options_t *options, + time_t now); + +#endif /* !defined(TOR_FEATURE_DIRCLIENT_DIRCLIENT_MODES_H) */ diff --git a/src/feature/dirclient/dlstatus.c b/src/feature/dirclient/dlstatus.c index 0842a2c676..ab3fbb8577 100644 --- a/src/feature/dirclient/dlstatus.c +++ b/src/feature/dirclient/dlstatus.c @@ -1,8 +1,13 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file dlstatus.c + * @brief Track status and retry schedule of a downloadable object. + **/ + #define DLSTATUS_PRIVATE #include "core/or/or.h" diff --git a/src/feature/dirclient/dlstatus.h b/src/feature/dirclient/dlstatus.h index 99e0d0225b..e5c8b756c4 100644 --- a/src/feature/dirclient/dlstatus.h +++ b/src/feature/dirclient/dlstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -53,6 +53,6 @@ STATIC void next_random_exponential_delay_range(int *low_bound_out, /* no more than triple the previous delay */ #define DIR_TEST_NET_RANDOM_MULTIPLIER (2) -#endif +#endif /* defined(DLSTATUS_PRIVATE) */ #endif /* !defined(TOR_DLSTATUS_H) */ diff --git a/src/feature/dirclient/download_status_st.h b/src/feature/dirclient/download_status_st.h index 11555a1dcc..92efcb44d0 100644 --- a/src/feature/dirclient/download_status_st.h +++ b/src/feature/dirclient/download_status_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file download_status_st.h + * @brief Directory download status/schedule structure. + **/ + #ifndef DOWNLOAD_STATUS_ST_H #define DOWNLOAD_STATUS_ST_H @@ -61,5 +66,4 @@ struct download_status_t { * only updated if backoff == 1 */ }; -#endif - +#endif /* !defined(DOWNLOAD_STATUS_ST_H) */ diff --git a/src/feature/dirclient/feature_dirclient.md b/src/feature/dirclient/feature_dirclient.md new file mode 100644 index 0000000000..5c7ee964d3 --- /dev/null +++ b/src/feature/dirclient/feature_dirclient.md @@ -0,0 +1,7 @@ +@dir /feature/dirclient +@brief feature/dirclient: Directory client implementation. + +The code here is used by all Tor instances that need to download directory +information. Currently, that is all of them, since even authorities need to +launch downloads to learn about relays that other authorities have listed. + diff --git a/src/feature/dirclient/include.am b/src/feature/dirclient/include.am new file mode 100644 index 0000000000..24cae9eedd --- /dev/null +++ b/src/feature/dirclient/include.am @@ -0,0 +1,14 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/dirclient/dirclient.c \ + src/feature/dirclient/dirclient_modes.c \ + src/feature/dirclient/dlstatus.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/dirclient/dir_server_st.h \ + src/feature/dirclient/dirclient.h \ + src/feature/dirclient/dirclient_modes.h \ + src/feature/dirclient/dlstatus.h \ + src/feature/dirclient/download_status_st.h diff --git a/src/feature/dircommon/.may_include b/src/feature/dircommon/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/dircommon/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/dircommon/consdiff.c b/src/feature/dircommon/consdiff.c index d0f7594ce3..e42378c44c 100644 --- a/src/feature/dircommon/consdiff.c +++ b/src/feature/dircommon/consdiff.c @@ -1,5 +1,5 @@ /* Copyright (c) 2014, Daniel Martà - * Copyright (c) 2014-2019, The Tor Project, Inc. */ + * Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -101,11 +101,11 @@ smartlist_add_linecpy(smartlist_t *lst, memarea_t *area, const char *s) /* This is a separate, mockable function so that we can override it when * fuzzing. */ MOCK_IMPL(STATIC int, -consensus_compute_digest,(const char *cons, +consensus_compute_digest,(const char *cons, size_t len, consensus_digest_t *digest_out)) { int r = crypto_digest256((char*)digest_out->sha3_256, - cons, strlen(cons), DIGEST_SHA3_256); + cons, len, DIGEST_SHA3_256); return r; } @@ -114,11 +114,11 @@ consensus_compute_digest,(const char *cons, /* This is a separate, mockable function so that we can override it when * fuzzing. */ MOCK_IMPL(STATIC int, -consensus_compute_digest_as_signed,(const char *cons, +consensus_compute_digest_as_signed,(const char *cons, size_t len, consensus_digest_t *digest_out)) { return router_get_networkstatus_v3_sha3_as_signed(digest_out->sha3_256, - cons); + cons, len); } /** Return true iff <b>d1</b> and <b>d2</b> contain the same digest */ @@ -530,10 +530,12 @@ typedef struct router_id_iterator_t { cdline_t hash; } router_id_iterator_t; +#ifndef COCCI /** * Initializer for a router_id_iterator_t. */ #define ROUTER_ID_ITERATOR_INIT { { NULL, 0 }, { NULL, 0 } } +#endif /* !defined(COCCI) */ /** Given an index *<b>idxp</b> into the consensus at <b>cons</b>, advance * the index to the next router line ("r ...") in the consensus, or to @@ -570,7 +572,7 @@ find_next_router_line(const smartlist_t *cons, /** Pre-process a consensus in <b>cons</b> (represented as a list of cdline_t) * to remove the signatures from it. If the footer is removed, return a * cdline_t containing a delete command to delete the footer, allocated in - * <b>area</>. If no footer is removed, return NULL. + * <b>area</b>. If no footer is removed, return NULL. * * We remove the signatures here because they are not themselves signed, and * as such there might be different encodings for them. @@ -1048,7 +1050,7 @@ consdiff_gen_diff(const smartlist_t *cons1, if (smartlist_len(cons2) == smartlist_len(ed_cons2)) { SMARTLIST_FOREACH_BEGIN(cons2, const cdline_t *, line1) { const cdline_t *line2 = smartlist_get(ed_cons2, line1_sl_idx); - if (! lines_eq(line1, line2) ) { + if (!lines_eq(line1, line2)) { cons2_eq = 0; break; } @@ -1229,7 +1231,8 @@ consdiff_apply_diff(const smartlist_t *cons1, cons2_str = consensus_join_lines(cons2); consensus_digest_t cons2_digests; - if (consensus_compute_digest(cons2_str, &cons2_digests) < 0) { + if (consensus_compute_digest(cons2_str, strlen(cons2_str), + &cons2_digests) < 0) { /* LCOV_EXCL_START -- digest can't fail */ log_warn(LD_CONSDIFF, "Could not compute digests of the consensus " "resulting from applying a consensus diff."); @@ -1283,12 +1286,13 @@ consdiff_apply_diff(const smartlist_t *cons1, * generated cdlines will become invalid. */ STATIC int -consensus_split_lines(smartlist_t *out, const char *s, memarea_t *area) +consensus_split_lines(smartlist_t *out, + const char *s, size_t len, + memarea_t *area) { - const char *end_of_str = s + strlen(s); - tor_assert(*end_of_str == '\0'); + const char *end_of_str = s + len; - while (*s) { + while (s < end_of_str) { const char *eol = memchr(s, '\n', end_of_str - s); if (!eol) { /* File doesn't end with newline. */ @@ -1334,25 +1338,25 @@ consensus_join_lines(const smartlist_t *inp) * success, retun a newly allocated string containing that diff. On failure, * return NULL. */ char * -consensus_diff_generate(const char *cons1, - const char *cons2) +consensus_diff_generate(const char *cons1, size_t cons1len, + const char *cons2, size_t cons2len) { consensus_digest_t d1, d2; smartlist_t *lines1 = NULL, *lines2 = NULL, *result_lines = NULL; int r1, r2; char *result = NULL; - r1 = consensus_compute_digest_as_signed(cons1, &d1); - r2 = consensus_compute_digest(cons2, &d2); + r1 = consensus_compute_digest_as_signed(cons1, cons1len, &d1); + r2 = consensus_compute_digest(cons2, cons2len, &d2); if (BUG(r1 < 0 || r2 < 0)) return NULL; // LCOV_EXCL_LINE memarea_t *area = memarea_new(); lines1 = smartlist_new(); lines2 = smartlist_new(); - if (consensus_split_lines(lines1, cons1, area) < 0) + if (consensus_split_lines(lines1, cons1, cons1len, area) < 0) goto done; - if (consensus_split_lines(lines2, cons2, area) < 0) + if (consensus_split_lines(lines2, cons2, cons2len, area) < 0) goto done; result_lines = consdiff_gen_diff(lines1, lines2, &d1, &d2, area); @@ -1375,7 +1379,9 @@ consensus_diff_generate(const char *cons1, * consensus. On failure, return NULL. */ char * consensus_diff_apply(const char *consensus, - const char *diff) + size_t consensus_len, + const char *diff, + size_t diff_len) { consensus_digest_t d1; smartlist_t *lines1 = NULL, *lines2 = NULL; @@ -1383,15 +1389,15 @@ consensus_diff_apply(const char *consensus, char *result = NULL; memarea_t *area = memarea_new(); - r1 = consensus_compute_digest_as_signed(consensus, &d1); + r1 = consensus_compute_digest_as_signed(consensus, consensus_len, &d1); if (BUG(r1 < 0)) goto done; lines1 = smartlist_new(); lines2 = smartlist_new(); - if (consensus_split_lines(lines1, consensus, area) < 0) + if (consensus_split_lines(lines1, consensus, consensus_len, area) < 0) goto done; - if (consensus_split_lines(lines2, diff, area) < 0) + if (consensus_split_lines(lines2, diff, diff_len, area) < 0) goto done; result = consdiff_apply_diff(lines1, lines2, &d1); diff --git a/src/feature/dircommon/consdiff.h b/src/feature/dircommon/consdiff.h index 98217e6d46..c2dcb6da24 100644 --- a/src/feature/dircommon/consdiff.h +++ b/src/feature/dircommon/consdiff.h @@ -1,16 +1,21 @@ /* Copyright (c) 2014, Daniel Martà - * Copyright (c) 2014-2019, The Tor Project, Inc. */ + * Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file consdiff.h + * @brief Header for consdiff.c + **/ + #ifndef TOR_CONSDIFF_H #define TOR_CONSDIFF_H #include "core/or/or.h" -char *consensus_diff_generate(const char *cons1, - const char *cons2); -char *consensus_diff_apply(const char *consensus, - const char *diff); +char *consensus_diff_generate(const char *cons1, size_t cons1len, + const char *cons2, size_t cons2len); +char *consensus_diff_apply(const char *consensus, size_t consensus_len, + const char *diff, size_t diff_len); int looks_like_a_consensus_diff(const char *document, size_t len); @@ -78,7 +83,8 @@ STATIC int smartlist_slice_string_pos(const smartlist_slice_t *slice, STATIC void set_changed(bitarray_t *changed1, bitarray_t *changed2, const smartlist_slice_t *slice1, const smartlist_slice_t *slice2); -STATIC int consensus_split_lines(smartlist_t *out, const char *s, +STATIC int consensus_split_lines(smartlist_t *out, + const char *s, size_t len, struct memarea_t *area); STATIC void smartlist_add_linecpy(smartlist_t *lst, struct memarea_t *area, const char *s); @@ -86,10 +92,10 @@ STATIC int lines_eq(const cdline_t *a, const cdline_t *b); STATIC int line_str_eq(const cdline_t *a, const char *b); MOCK_DECL(STATIC int, - consensus_compute_digest,(const char *cons, + consensus_compute_digest,(const char *cons, size_t len, consensus_digest_t *digest_out)); MOCK_DECL(STATIC int, - consensus_compute_digest_as_signed,(const char *cons, + consensus_compute_digest_as_signed,(const char *cons, size_t len, consensus_digest_t *digest_out)); MOCK_DECL(STATIC int, consensus_digest_eq,(const uint8_t *d1, diff --git a/src/feature/dircommon/dir_connection_st.h b/src/feature/dircommon/dir_connection_st.h index 8c59cc7a46..12230e6741 100644 --- a/src/feature/dircommon/dir_connection_st.h +++ b/src/feature/dircommon/dir_connection_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file dir_connection_st.h + * @brief Client/server directory connection structure. + **/ + #ifndef DIR_CONNECTION_ST_H #define DIR_CONNECTION_ST_H @@ -23,7 +28,9 @@ struct dir_connection_t { * fingerprints. **/ char *requested_resource; - unsigned int dirconn_direct:1; /**< Is this dirconn direct, or via Tor? */ + /** Is this dirconn direct, or via a multi-hop Tor circuit? + * Direct connections can use the DirPort, or BEGINDIR over the ORPort. */ + unsigned int dirconn_direct:1; /** If we're fetching descriptors, what router purpose shall we assign * to them? */ @@ -64,4 +71,4 @@ struct dir_connection_t { #endif /* defined(MEASUREMENTS_21206) */ }; -#endif +#endif /* !defined(DIR_CONNECTION_ST_H) */ diff --git a/src/feature/dircommon/directory.c b/src/feature/dircommon/directory.c index 9e6f72e9ac..b177fe5201 100644 --- a/src/feature/dircommon/directory.c +++ b/src/feature/dircommon/directory.c @@ -1,12 +1,16 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "core/or/or.h" #include "app/config/config.h" #include "core/mainloop/connection.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/channeltls.h" #include "feature/dircache/dircache.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" @@ -15,6 +19,10 @@ #include "feature/stats/geoip_stats.h" #include "lib/compress/compress.h" +#include "core/or/circuit_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/edge_connection_st.h" +#include "core/or/or_connection_st.h" #include "feature/dircommon/dir_connection_st.h" #include "feature/nodelist/routerinfo_st.h" @@ -167,6 +175,82 @@ connection_dir_is_encrypted(const dir_connection_t *conn) return TO_CONN(conn)->linked; } +/** Return true iff the given directory connection <b>dir_conn</b> is + * anonymous, that is, it is on a circuit via a public relay and not directly + * from a client or bridge. + * + * For client circuits via relays: true for 2-hop+ paths. + * For client circuits via bridges: true for 3-hop+ paths. + * + * This first test if the connection is encrypted since it is a strong + * requirement for anonymity. */ +bool +connection_dir_is_anonymous(const dir_connection_t *dir_conn) +{ + const connection_t *conn, *linked_conn; + const edge_connection_t *edge_conn; + const circuit_t *circ; + + tor_assert(dir_conn); + + if (!connection_dir_is_encrypted(dir_conn)) { + return false; + } + + /* + * Buckle up, we'll do a deep dive into the connection in order to get the + * final connection channel of that connection in order to figure out if + * this is a client or relay link. + * + * We go: dir_conn -> linked_conn -> edge_conn -> on_circuit -> p_chan. + */ + + conn = TO_CONN(dir_conn); + linked_conn = conn->linked_conn; + + /* The dir connection should be connected to an edge connection. It can not + * be closed or marked for close. */ + if (linked_conn == NULL || linked_conn->magic != EDGE_CONNECTION_MAGIC || + conn->linked_conn_is_closed || conn->linked_conn->marked_for_close) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "not linked to edge"); + return false; + } + + edge_conn = TO_EDGE_CONN((connection_t *) linked_conn); + circ = edge_conn->on_circuit; + + /* Can't be a circuit we initiated and without a circuit, no channel. */ + if (circ == NULL || CIRCUIT_IS_ORIGIN(circ)) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "not on OR circuit"); + return false; + } + + /* It is possible that the circuit was closed because one of the channel was + * closed or a DESTROY cell was received. Either way, this connection can + * not continue so return that it is not anonymous since we can not know for + * sure if it is. */ + if (circ->marked_for_close) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "circuit marked for close"); + return false; + } + + /* Get the previous channel to learn if it is a client or relay link. We + * BUG() because if the circuit is not mark for close, we ought to have a + * p_chan else we have a code flow issue. */ + if (BUG(CONST_TO_OR_CIRCUIT(circ)->p_chan == NULL)) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "no p_chan on circuit"); + return false; + } + + /* Will be true if the channel is an unauthenticated peer which is only true + * for clients and bridges. */ + return !channel_is_client(CONST_TO_OR_CIRCUIT(circ)->p_chan); +} + /** Parse an HTTP request line at the start of a headers string. On failure, * return -1. On success, set *<b>command_out</b> to a copy of the HTTP * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and @@ -618,34 +702,3 @@ dir_split_resource_into_fingerprints(const char *resource, smartlist_free(fp_tmp); return 0; } - -/** As dir_split_resource_into_fingerprints, but instead fills - * <b>spool_out</b> with a list of spoolable_resource_t for the resource - * identified through <b>source</b>. */ -int -dir_split_resource_into_spoolable(const char *resource, - dir_spool_source_t source, - smartlist_t *spool_out, - int *compressed_out, - int flags) -{ - smartlist_t *fingerprints = smartlist_new(); - - tor_assert(flags & (DSR_HEX|DSR_BASE64)); - const size_t digest_len = - (flags & DSR_DIGEST256) ? DIGEST256_LEN : DIGEST_LEN; - - int r = dir_split_resource_into_fingerprints(resource, fingerprints, - compressed_out, flags); - /* This is not a very efficient implementation XXXX */ - SMARTLIST_FOREACH_BEGIN(fingerprints, uint8_t *, digest) { - spooled_resource_t *spooled = - spooled_resource_new(source, digest, digest_len); - if (spooled) - smartlist_add(spool_out, spooled); - tor_free(digest); - } SMARTLIST_FOREACH_END(digest); - - smartlist_free(fingerprints); - return r; -} diff --git a/src/feature/dircommon/directory.h b/src/feature/dircommon/directory.h index ba3f8c1b0e..0f26cdeff9 100644 --- a/src/feature/dircommon/directory.h +++ b/src/feature/dircommon/directory.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -94,6 +94,7 @@ int parse_http_command(const char *headers, char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); +bool connection_dir_is_anonymous(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); @@ -107,12 +108,6 @@ void connection_dir_about_to_close(dir_connection_t *dir_conn); int dir_split_resource_into_fingerprints(const char *resource, smartlist_t *fp_out, int *compressed_out, int flags); -enum dir_spool_source_t; -int dir_split_resource_into_spoolable(const char *resource, - enum dir_spool_source_t source, - smartlist_t *spool_out, - int *compressed_out, - int flags); int dir_split_resource_into_fingerprint_pairs(const char *res, smartlist_t *pairs_out); char *directory_dump_request_log(void); diff --git a/src/feature/dircommon/feature_dircommon.md b/src/feature/dircommon/feature_dircommon.md new file mode 100644 index 0000000000..359049ecd8 --- /dev/null +++ b/src/feature/dircommon/feature_dircommon.md @@ -0,0 +1,7 @@ +@dir /feature/dircommon +@brief feature/dircommon: Directory client and server shared code + +This module has the code that directory clients (anybody who download +information about relays) and directory servers (anybody who serves such +information) share in common. + diff --git a/src/feature/dircommon/fp_pair.c b/src/feature/dircommon/fp_pair.c index 284600df77..8b55896ba8 100644 --- a/src/feature/dircommon/fp_pair.c +++ b/src/feature/dircommon/fp_pair.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* Copyright (c) 2013-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,14 +22,14 @@ /* Define fp_pair_map_t structures */ -struct fp_pair_map_entry_s { - HT_ENTRY(fp_pair_map_entry_s) node; +struct fp_pair_map_entry_t { + HT_ENTRY(fp_pair_map_entry_t) node; void *val; fp_pair_t key; }; -struct fp_pair_map_s { - HT_HEAD(fp_pair_map_impl, fp_pair_map_entry_s) head; +struct fp_pair_map_t { + HT_HEAD(fp_pair_map_impl, fp_pair_map_entry_t) head; }; /* @@ -56,9 +56,9 @@ fp_pair_map_entry_hash(const fp_pair_map_entry_t *a) * Hash table functions for fp_pair_map_t */ -HT_PROTOTYPE(fp_pair_map_impl, fp_pair_map_entry_s, node, +HT_PROTOTYPE(fp_pair_map_impl, fp_pair_map_entry_t, node, fp_pair_map_entry_hash, fp_pair_map_entries_eq) -HT_GENERATE2(fp_pair_map_impl, fp_pair_map_entry_s, node, +HT_GENERATE2(fp_pair_map_impl, fp_pair_map_entry_t, node, fp_pair_map_entry_hash, fp_pair_map_entries_eq, 0.6, tor_reallocarray_, tor_free_) diff --git a/src/feature/dircommon/fp_pair.h b/src/feature/dircommon/fp_pair.h index 5041583e88..ae71ea7b71 100644 --- a/src/feature/dircommon/fp_pair.h +++ b/src/feature/dircommon/fp_pair.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* Copyright (c) 2013-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,8 +19,8 @@ typedef struct { * Declare fp_pair_map_t functions and structs */ -typedef struct fp_pair_map_entry_s fp_pair_map_entry_t; -typedef struct fp_pair_map_s fp_pair_map_t; +typedef struct fp_pair_map_entry_t fp_pair_map_entry_t; +typedef struct fp_pair_map_t fp_pair_map_t; typedef fp_pair_map_entry_t *fp_pair_map_iter_t; fp_pair_map_t * fp_pair_map_new(void); diff --git a/src/feature/dircommon/include.am b/src/feature/dircommon/include.am new file mode 100644 index 0000000000..f0f0323d12 --- /dev/null +++ b/src/feature/dircommon/include.am @@ -0,0 +1,16 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/dircommon/consdiff.c \ + src/feature/dircommon/directory.c \ + src/feature/dircommon/fp_pair.c \ + src/feature/dircommon/voting_schedule.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/dircommon/consdiff.h \ + src/feature/dircommon/dir_connection_st.h \ + src/feature/dircommon/directory.h \ + src/feature/dircommon/fp_pair.h \ + src/feature/dircommon/vote_timing_st.h \ + src/feature/dircommon/voting_schedule.h diff --git a/src/feature/dircommon/vote_timing_st.h b/src/feature/dircommon/vote_timing_st.h index 47b90ab009..103d950f86 100644 --- a/src/feature/dircommon/vote_timing_st.h +++ b/src/feature/dircommon/vote_timing_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file vote_timing_st.h + * @brief Directory voting schedule structure. + **/ + #ifndef VOTE_TIMING_ST_H #define VOTE_TIMING_ST_H @@ -20,5 +25,4 @@ struct vote_timing_t { int dist_delay; }; -#endif - +#endif /* !defined(VOTE_TIMING_ST_H) */ diff --git a/src/feature/dircommon/voting_schedule.c b/src/feature/dircommon/voting_schedule.c index 0a7476eda7..389f7f6b5d 100644 --- a/src/feature/dircommon/voting_schedule.c +++ b/src/feature/dircommon/voting_schedule.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -8,7 +8,6 @@ * tor. The full feature is built as part of the dirauth module. **/ -#define VOTING_SCHEDULE_PRIVATE #include "feature/dircommon/voting_schedule.h" #include "core/or/or.h" @@ -150,7 +149,7 @@ voting_schedule_get_next_valid_after_time(void) /* This is a safe guard in order to make sure that the voting schedule * static object is at least initialized. Using this function with a zeroed * voting schedule can lead to bugs. */ - if (tor_mem_is_zero((const char *) &voting_schedule, + if (fast_mem_is_zero((const char *) &voting_schedule, sizeof(voting_schedule))) { need_to_recalculate_voting_schedule = true; goto done; /* no need for next check if we have to recalculate anyway */ diff --git a/src/feature/dircommon/voting_schedule.h b/src/feature/dircommon/voting_schedule.h index bafd81184e..e4c6210087 100644 --- a/src/feature/dircommon/voting_schedule.h +++ b/src/feature/dircommon/voting_schedule.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -26,15 +26,15 @@ typedef struct { /** When do we publish the consensus? */ time_t interval_starts; - /* True iff we have generated and distributed our vote. */ + /** True iff we have generated and distributed our vote. */ int have_voted; - /* True iff we've requested missing votes. */ + /** True iff we've requested missing votes. */ int have_fetched_missing_votes; - /* True iff we have built a consensus and sent the signatures around. */ + /** True iff we have built a consensus and sent the signatures around. */ int have_built_consensus; - /* True iff we've fetched missing signatures. */ + /** True iff we've fetched missing signatures. */ int have_fetched_missing_signatures; - /* True iff we have published our consensus. */ + /** True iff we have published our consensus. */ int have_published_consensus; /* True iff this voting schedule was set on demand meaning not through the @@ -61,5 +61,4 @@ time_t voting_schedule_get_start_of_next_interval(time_t now, int offset); time_t voting_schedule_get_next_valid_after_time(void); -#endif /* TOR_VOTING_SCHEDULE_H */ - +#endif /* !defined(TOR_VOTING_SCHEDULE_H) */ diff --git a/src/feature/dirparse/.may_include b/src/feature/dirparse/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/dirparse/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/dirparse/authcert_members.h b/src/feature/dirparse/authcert_members.h new file mode 100644 index 0000000000..c6755bb629 --- /dev/null +++ b/src/feature/dirparse/authcert_members.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file authcert_members.h + * + * @brief List of tokens common to V3 authority certificates and V3 + * consensuses. + **/ + +#ifndef TOR_AUTHCERT_MEMBERS_H +#define TOR_AUTHCERT_MEMBERS_H + +#define AUTHCERT_MEMBERS \ + T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, \ + GE(1), NO_OBJ ), \ + T1("dir-identity-key", K_DIR_IDENTITY_KEY, NO_ARGS, NEED_KEY ),\ + T1("dir-key-published",K_DIR_KEY_PUBLISHED, CONCAT_ARGS, NO_OBJ),\ + T1("dir-key-expires", K_DIR_KEY_EXPIRES, CONCAT_ARGS, NO_OBJ),\ + T1("dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY ),\ + T1("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ),\ + T1("dir-key-certification", K_DIR_KEY_CERTIFICATION,\ + NO_ARGS, NEED_OBJ),\ + T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ) + +#endif /* !defined(TOR_AUTHCERT_MEMBERS_H) */ diff --git a/src/feature/dirparse/authcert_members.i b/src/feature/dirparse/authcert_members.i deleted file mode 100644 index 08cffca97a..0000000000 --- a/src/feature/dirparse/authcert_members.i +++ /dev/null @@ -1,13 +0,0 @@ -/* - * List of tokens common to V3 authority certificates and V3 consensuses. - */ - T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, - GE(1), NO_OBJ ), - T1("dir-identity-key", K_DIR_IDENTITY_KEY, NO_ARGS, NEED_KEY ), - T1("dir-key-published",K_DIR_KEY_PUBLISHED, CONCAT_ARGS, NO_OBJ), - T1("dir-key-expires", K_DIR_KEY_EXPIRES, CONCAT_ARGS, NO_OBJ), - T1("dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY ), - T1("dir-key-crosscert", K_DIR_KEY_CROSSCERT, NO_ARGS, NEED_OBJ ), - T1("dir-key-certification", K_DIR_KEY_CERTIFICATION, - NO_ARGS, NEED_OBJ), - T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ), diff --git a/src/feature/dirparse/authcert_parse.c b/src/feature/dirparse/authcert_parse.c index 1680bdbf30..3d42119b94 100644 --- a/src/feature/dirparse/authcert_parse.c +++ b/src/feature/dirparse/authcert_parse.c @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file authcert_parse.c + * @brief Authority certificate parsing. + **/ + #include "core/or/or.h" #include "feature/dirparse/authcert_parse.h" #include "feature/dirparse/parsecommon.h" @@ -13,10 +18,11 @@ #include "lib/memarea/memarea.h" #include "feature/nodelist/authority_cert_st.h" +#include "feature/dirparse/authcert_members.h" /** List of tokens recognized in V3 authority certificates. */ static token_rule_t dir_key_certificate_table[] = { -#include "feature/dirparse/authcert_members.i" + AUTHCERT_MEMBERS, T1("fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), END_OF_TABLE }; @@ -24,7 +30,8 @@ static token_rule_t dir_key_certificate_table[] = { /** Parse a key certificate from <b>s</b>; point <b>end-of-string</b> to * the first character after the certificate. */ authority_cert_t * -authority_cert_parse_from_string(const char *s, const char **end_of_string) +authority_cert_parse_from_string(const char *s, size_t maxlen, + const char **end_of_string) { /** Reject any certificate at least this big; it is probably an overflow, an * attack, a bug, or some other nonsense. */ @@ -35,24 +42,25 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) char digest[DIGEST_LEN]; directory_token_t *tok; char fp_declared[DIGEST_LEN]; - char *eos; + const char *eos; size_t len; int found; memarea_t *area = NULL; + const char *end_of_s = s + maxlen; const char *s_dup = s; - s = eat_whitespace(s); - eos = strstr(s, "\ndir-key-certification"); + s = eat_whitespace_eos(s, end_of_s); + eos = tor_memstr(s, end_of_s - s, "\ndir-key-certification"); if (! eos) { log_warn(LD_DIR, "No signature found on key certificate"); return NULL; } - eos = strstr(eos, "\n-----END SIGNATURE-----\n"); + eos = tor_memstr(eos, end_of_s - eos, "\n-----END SIGNATURE-----\n"); if (! eos) { log_warn(LD_DIR, "No end-of-signature found on key certificate"); return NULL; } - eos = strchr(eos+2, '\n'); + eos = memchr(eos+2, '\n', end_of_s - (eos+2)); tor_assert(eos); ++eos; len = eos - s; @@ -69,7 +77,7 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) log_warn(LD_DIR, "Error tokenizing key certificate"); goto err; } - if (router_get_hash_impl(s, strlen(s), digest, "dir-key-certificate-version", + if (router_get_hash_impl(s, eos - s, digest, "dir-key-certificate-version", "\ndir-key-certification", '\n', DIGEST_SHA1) < 0) goto err; tok = smartlist_get(tokens, 0); diff --git a/src/feature/dirparse/authcert_parse.h b/src/feature/dirparse/authcert_parse.h index ca475ad0e3..7f6dd1c02f 100644 --- a/src/feature/dirparse/authcert_parse.h +++ b/src/feature/dirparse/authcert_parse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,6 +13,7 @@ #define TOR_AUTHCERT_PARSE_H authority_cert_t *authority_cert_parse_from_string(const char *s, + size_t maxlen, const char **end_of_string); #endif /* !defined(TOR_AUTHCERT_PARSE_H) */ diff --git a/src/feature/dirparse/feature_dirparse.md b/src/feature/dirparse/feature_dirparse.md new file mode 100644 index 0000000000..e4b34668ba --- /dev/null +++ b/src/feature/dirparse/feature_dirparse.md @@ -0,0 +1,8 @@ +@dir /feature/dirparse +@brief feature/dirparse: Parsing Tor directory objects + +We define a number of "directory objects" in +[dir-spec.txt](https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt), +all of them using a common line-oriented meta-format. This module is used by +other parts of Tor to parse them. + diff --git a/src/feature/dirparse/include.am b/src/feature/dirparse/include.am new file mode 100644 index 0000000000..edca04f6f7 --- /dev/null +++ b/src/feature/dirparse/include.am @@ -0,0 +1,25 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/dirparse/authcert_parse.c \ + src/feature/dirparse/microdesc_parse.c \ + src/feature/dirparse/ns_parse.c \ + src/feature/dirparse/parsecommon.c \ + src/feature/dirparse/policy_parse.c \ + src/feature/dirparse/routerparse.c \ + src/feature/dirparse/sigcommon.c \ + src/feature/dirparse/signing.c \ + src/feature/dirparse/unparseable.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/dirparse/authcert_members.h \ + src/feature/dirparse/authcert_parse.h \ + src/feature/dirparse/microdesc_parse.h \ + src/feature/dirparse/ns_parse.h \ + src/feature/dirparse/parsecommon.h \ + src/feature/dirparse/policy_parse.h \ + src/feature/dirparse/routerparse.h \ + src/feature/dirparse/sigcommon.h \ + src/feature/dirparse/signing.h \ + src/feature/dirparse/unparseable.h diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c index 5a75af3994..c2eabeb404 100644 --- a/src/feature/dirparse/microdesc_parse.c +++ b/src/feature/dirparse/microdesc_parse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,6 +18,7 @@ #include "feature/dirparse/routerparse.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/nickname.h" +#include "feature/nodelist/nodefamily.h" #include "feature/relay/router.h" #include "lib/crypt_ops/crypto_curve25519.h" #include "lib/crypt_ops/crypto_ed25519.h" @@ -32,7 +33,7 @@ static token_rule_t microdesc_token_table[] = { T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), T0N("id", K_ID, GE(2), NO_OBJ ), T0N("a", K_A, GE(1), NO_OBJ ), - T01("family", K_FAMILY, ARGS, NO_OBJ ), + T01("family", K_FAMILY, CONCAT_ARGS, NO_OBJ ), T01("p", K_P, CONCAT_ARGS, NO_OBJ ), T01("p6", K_P6, CONCAT_ARGS, NO_OBJ ), A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), @@ -91,6 +92,190 @@ find_start_of_next_microdesc(const char *s, const char *eos) #undef NEXT_LINE } +static inline int +policy_is_reject_star_or_null(struct short_policy_t *policy) +{ + return !policy || short_policy_is_reject_star(policy); +} + +/** + * Return a human-readable description of a given saved_location_t. + * Never returns NULL. + **/ +static const char * +saved_location_to_string(saved_location_t where) +{ + const char *location; + switch (where) { + case SAVED_NOWHERE: + location = "download or generated string"; + break; + case SAVED_IN_CACHE: + location = "cache"; + break; + case SAVED_IN_JOURNAL: + location = "journal"; + break; + default: + location = "unknown location"; + break; + } + return location; +} + +/** + * Given a microdescriptor stored in <b>where</b> which starts at <b>s</b>, + * which ends at <b>start_of_next_microdescriptor</b>, and which is located + * within a larger document beginning at <b>start</b>: Fill in the body, + * bodylen, bodylen, saved_location, off, and digest fields of <b>md</b> as + * appropriate. + * + * The body field will be an alias within <b>s</b> if <b>saved_location</b> + * is SAVED_IN_CACHE, and will be copied into body and nul-terminated + * otherwise. + **/ +static int +microdesc_extract_body(microdesc_t *md, + const char *start, + const char *s, const char *start_of_next_microdesc, + saved_location_t where) +{ + const bool copy_body = (where != SAVED_IN_CACHE); + + const char *cp = tor_memstr(s, start_of_next_microdesc-s, "onion-key"); + + const bool no_onion_key = (cp == NULL); + if (no_onion_key) { + cp = s; /* So that we have *some* junk to put in the body */ + } + + md->bodylen = start_of_next_microdesc - cp; + md->saved_location = where; + if (copy_body) + md->body = tor_memdup_nulterm(cp, md->bodylen); + else + md->body = (char*)cp; + md->off = cp - start; + + crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); + + return no_onion_key ? -1 : 0; +} + +/** + * Parse a microdescriptor which begins at <b>s</b> and ends at + * <b>start_of_next_microdesc</b>. Store its fields into <b>md</b>. Use + * <b>where</b> for generating log information. If <b>allow_annotations</b> + * is true, then one or more annotations may precede the microdescriptor body + * proper. Use <b>area</b> for memory management, clearing it when done. + * + * On success, return 0; otherwise return -1. + **/ +static int +microdesc_parse_fields(microdesc_t *md, + memarea_t *area, + const char *s, const char *start_of_next_microdesc, + int allow_annotations, + saved_location_t where) +{ + smartlist_t *tokens = smartlist_new(); + int rv = -1; + int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; + directory_token_t *tok; + + if (tokenize_string(area, s, start_of_next_microdesc, tokens, + microdesc_token_table, flags)) { + log_warn(LD_DIR, "Unparseable microdescriptor found in %s", + saved_location_to_string(where)); + goto err; + } + + if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { + if (parse_iso_time(tok->args[0], &md->last_listed)) { + log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); + goto err; + } + } + + tok = find_by_keyword(tokens, K_ONION_KEY); + if (!crypto_pk_public_exponent_ok(tok->key)) { + log_warn(LD_DIR, + "Relay's onion key had invalid exponent."); + goto err; + } + md->onion_pkey = tor_memdup(tok->object_body, tok->object_size); + md->onion_pkey_len = tok->object_size; + crypto_pk_free(tok->key); + + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + curve25519_public_key_t k; + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); + goto err; + } + md->onion_curve25519_pkey = + tor_memdup(&k, sizeof(curve25519_public_key_t)); + } + + smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); + if (id_lines) { + SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { + tor_assert(t->n_args >= 2); + if (!strcmp(t->args[0], "ed25519")) { + if (md->ed25519_identity_pkey) { + log_warn(LD_DIR, "Extra ed25519 key in microdesc"); + smartlist_free(id_lines); + goto err; + } + ed25519_public_key_t k; + if (ed25519_public_from_base64(&k, t->args[1])<0) { + log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); + smartlist_free(id_lines); + goto err; + } + md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); + } + } SMARTLIST_FOREACH_END(t); + smartlist_free(id_lines); + } + + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); + smartlist_free(a_lines); + } + } + + if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { + md->family = nodefamily_parse(tok->args[0], + NULL, + NF_WARN_MALFORMED); + } + + if ((tok = find_opt_by_keyword(tokens, K_P))) { + md->exit_policy = parse_short_policy(tok->args[0]); + } + if ((tok = find_opt_by_keyword(tokens, K_P6))) { + md->ipv6_exit_policy = parse_short_policy(tok->args[0]); + } + + if (policy_is_reject_star_or_null(md->exit_policy) && + policy_is_reject_star_or_null(md->ipv6_exit_policy)) { + md->policy_is_reject_star = 1; + } + + rv = 0; + err: + + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + memarea_clear(area); + smartlist_free(tokens); + + return rv; +} + /** Parse as many microdescriptors as are found from the string starting at * <b>s</b> and ending at <b>eos</b>. If allow_annotations is set, read any * annotations we recognize and ignore ones we don't. @@ -108,16 +293,11 @@ microdescs_parse_from_string(const char *s, const char *eos, saved_location_t where, smartlist_t *invalid_digests_out) { - smartlist_t *tokens; smartlist_t *result; microdesc_t *md = NULL; memarea_t *area; const char *start = s; const char *start_of_next_microdesc; - int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; - const int copy_body = (where != SAVED_IN_CACHE); - - directory_token_t *tok; if (!eos) eos = s + strlen(s); @@ -125,143 +305,47 @@ microdescs_parse_from_string(const char *s, const char *eos, s = eat_whitespace_eos(s, eos); area = memarea_new(); result = smartlist_new(); - tokens = smartlist_new(); while (s < eos) { - int okay = 0; + bool okay = false; start_of_next_microdesc = find_start_of_next_microdesc(s, eos); if (!start_of_next_microdesc) start_of_next_microdesc = eos; md = tor_malloc_zero(sizeof(microdesc_t)); + uint8_t md_digest[DIGEST256_LEN]; { - const char *cp = tor_memstr(s, start_of_next_microdesc-s, - "onion-key"); - const int no_onion_key = (cp == NULL); - if (no_onion_key) { - cp = s; /* So that we have *some* junk to put in the body */ - } + const bool body_not_found = + microdesc_extract_body(md, start, s, + start_of_next_microdesc, + where) < 0; - md->bodylen = start_of_next_microdesc - cp; - md->saved_location = where; - if (copy_body) - md->body = tor_memdup_nulterm(cp, md->bodylen); - else - md->body = (char*)cp; - md->off = cp - start; - crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); - if (no_onion_key) { + memcpy(md_digest, md->digest, DIGEST256_LEN); + if (body_not_found) { log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor"); goto next; } } - if (tokenize_string(area, s, start_of_next_microdesc, tokens, - microdesc_token_table, flags)) { - log_warn(LD_DIR, "Unparseable microdescriptor"); - goto next; + if (microdesc_parse_fields(md, area, s, start_of_next_microdesc, + allow_annotations, where) == 0) { + smartlist_add(result, md); + md = NULL; // prevent free + okay = true; } - if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { - if (parse_iso_time(tok->args[0], &md->last_listed)) { - log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); - goto next; - } - } - - tok = find_by_keyword(tokens, K_ONION_KEY); - if (!crypto_pk_public_exponent_ok(tok->key)) { - log_warn(LD_DIR, - "Relay's onion key had invalid exponent."); - goto next; - } - router_set_rsa_onion_pkey(tok->key, &md->onion_pkey, - &md->onion_pkey_len); - crypto_pk_free(tok->key); - - if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { - curve25519_public_key_t k; - tor_assert(tok->n_args >= 1); - if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { - log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); - goto next; - } - md->onion_curve25519_pkey = - tor_memdup(&k, sizeof(curve25519_public_key_t)); - } - - smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); - if (id_lines) { - SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { - tor_assert(t->n_args >= 2); - if (!strcmp(t->args[0], "ed25519")) { - if (md->ed25519_identity_pkey) { - log_warn(LD_DIR, "Extra ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - ed25519_public_key_t k; - if (ed25519_public_from_base64(&k, t->args[1])<0) { - log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); - } - } SMARTLIST_FOREACH_END(t); - smartlist_free(id_lines); - } - - { - smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); - if (a_lines) { - find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); - smartlist_free(a_lines); - } - } - - if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { - int i; - md->family = smartlist_new(); - for (i=0;i<tok->n_args;++i) { - if (!is_legal_nickname_or_hexdigest(tok->args[i])) { - log_warn(LD_DIR, "Illegal nickname %s in family line", - escaped(tok->args[i])); - goto next; - } - smartlist_add_strdup(md->family, tok->args[i]); - } - } - - if ((tok = find_opt_by_keyword(tokens, K_P))) { - md->exit_policy = parse_short_policy(tok->args[0]); - } - if ((tok = find_opt_by_keyword(tokens, K_P6))) { - md->ipv6_exit_policy = parse_short_policy(tok->args[0]); - } - - smartlist_add(result, md); - okay = 1; - - md = NULL; next: if (! okay && invalid_digests_out) { smartlist_add(invalid_digests_out, - tor_memdup(md->digest, DIGEST256_LEN)); + tor_memdup(md_digest, DIGEST256_LEN)); } microdesc_free(md); md = NULL; - - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - memarea_clear(area); - smartlist_clear(tokens); s = start_of_next_microdesc; } - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); memarea_drop_all(area); - smartlist_free(tokens); return result; } diff --git a/src/feature/dirparse/microdesc_parse.h b/src/feature/dirparse/microdesc_parse.h index 23a90084b1..e81126b8cd 100644 --- a/src/feature/dirparse/microdesc_parse.h +++ b/src/feature/dirparse/microdesc_parse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,4 +17,4 @@ smartlist_t *microdescs_parse_from_string(const char *s, const char *eos, saved_location_t where, smartlist_t *invalid_digests_out); -#endif +#endif /* !defined(TOR_MICRODESC_PARSE_H) */ diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c index 109eebeb66..4d9b6e6e73 100644 --- a/src/feature/dirparse/ns_parse.c +++ b/src/feature/dirparse/ns_parse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -36,6 +36,7 @@ #include "feature/nodelist/networkstatus_st.h" #include "feature/nodelist/networkstatus_voter_info_st.h" #include "feature/nodelist/vote_routerstatus_st.h" +#include "feature/dirparse/authcert_members.h" #undef log #include <math.h> @@ -84,7 +85,7 @@ static token_rule_t networkstatus_token_table[] = { T01("required-relay-protocols", K_REQUIRED_RELAY_PROTOCOLS, CONCAT_ARGS, NO_OBJ ), -#include "feature/dirparse/authcert_members.i" + AUTHCERT_MEMBERS, T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), T1( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), @@ -151,10 +152,11 @@ static token_rule_t networkstatus_vote_footer_token_table[] = { * -1. */ int router_get_networkstatus_v3_signed_boundaries(const char *s, + size_t len, const char **start_out, const char **end_out) { - return router_get_hash_impl_helper(s, strlen(s), + return router_get_hash_impl_helper(s, len, "network-status-version", "\ndirectory-signature", ' ', LOG_INFO, @@ -166,12 +168,13 @@ router_get_networkstatus_v3_signed_boundaries(const char *s, * signed portion can be identified. Return 0 on success, -1 on failure. */ int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, - const char *s) + const char *s, size_t len) { const char *start, *end; - if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) { + if (router_get_networkstatus_v3_signed_boundaries(s, len, + &start, &end) < 0) { start = s; - end = s + strlen(s); + end = s + len; } tor_assert(start); tor_assert(end); @@ -182,9 +185,10 @@ router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, /** Set <b>digests</b> to all the digests of the consensus document in * <b>s</b> */ int -router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests) +router_get_networkstatus_v3_hashes(const char *s, size_t len, + common_digests_t *digests) { - return router_get_hashes_impl(s,strlen(s),digests, + return router_get_hashes_impl(s, len, digests, "network-status-version", "\ndirectory-signature", ' '); @@ -195,13 +199,13 @@ router_get_networkstatus_v3_hashes(const char *s, common_digests_t *digests) * return the start of the directory footer, or the next directory signature. * If none is found, return the end of the string. */ static inline const char * -find_start_of_next_routerstatus(const char *s) +find_start_of_next_routerstatus(const char *s, const char *s_eos) { const char *eos, *footer, *sig; - if ((eos = strstr(s, "\nr "))) + if ((eos = tor_memstr(s, s_eos - s, "\nr "))) ++eos; else - eos = s + strlen(s); + eos = s_eos; footer = tor_memstr(s, eos-s, "\ndirectory-footer"); sig = tor_memstr(s, eos-s, "\ndirectory-signature"); @@ -289,7 +293,8 @@ routerstatus_parse_guardfraction(const char *guardfraction_str, **/ STATIC routerstatus_t * routerstatus_parse_entry_from_string(memarea_t *area, - const char **s, smartlist_t *tokens, + const char **s, const char *s_eos, + smartlist_t *tokens, networkstatus_t *vote, vote_routerstatus_t *vote_rs, int consensus_method, @@ -308,7 +313,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, flav = FLAV_NS; tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC); - eos = find_start_of_next_routerstatus(*s); + eos = find_start_of_next_routerstatus(*s, s_eos); if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) { log_warn(LD_DIR, "Error tokenizing router status"); @@ -430,6 +435,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->is_hs_dir = 1; } else if (!strcmp(tok->args[i], "V2Dir")) { rs->is_v2_dir = 1; + } else if (!strcmp(tok->args[i], "StaleDesc")) { + rs->is_staledesc = 1; } } /* These are implied true by having been included in a consensus made @@ -1051,7 +1058,9 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) /** Parse a v3 networkstatus vote, opinion, or consensus (depending on * ns_type), from <b>s</b>, and return the result. Return NULL on failure. */ networkstatus_t * -networkstatus_parse_vote_from_string(const char *s, const char **eos_out, +networkstatus_parse_vote_from_string(const char *s, + size_t s_len, + const char **eos_out, networkstatus_type_t ns_type) { smartlist_t *tokens = smartlist_new(); @@ -1067,20 +1076,22 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, memarea_t *area = NULL, *rs_area = NULL; consensus_flavor_t flav = FLAV_NS; char *last_kwd=NULL; + const char *eos = s + s_len; tor_assert(s); if (eos_out) *eos_out = NULL; - if (router_get_networkstatus_v3_hashes(s, &ns_digests) || - router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) { + if (router_get_networkstatus_v3_hashes(s, s_len, &ns_digests) || + router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, + s, s_len)<0) { log_warn(LD_DIR, "Unable to compute digest of network-status"); goto err; } area = memarea_new(); - end_of_header = find_start_of_next_routerstatus(s); + end_of_header = find_start_of_next_routerstatus(s, eos); if (tokenize_string(area, s, end_of_header, tokens, (ns_type == NS_TYPE_CONSENSUS) ? networkstatus_consensus_token_table : @@ -1111,10 +1122,12 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, if (ns_type != NS_TYPE_CONSENSUS) { const char *end_of_cert = NULL; - if (!(cert = strstr(s, "\ndir-key-certificate-version"))) + if (!(cert = tor_memstr(s, end_of_header - s, + "\ndir-key-certificate-version"))) goto err; ++cert; - ns->cert = authority_cert_parse_from_string(cert, &end_of_cert); + ns->cert = authority_cert_parse_from_string(cert, end_of_header - cert, + &end_of_cert); if (!ns->cert || !end_of_cert || end_of_cert > end_of_header) goto err; } @@ -1424,10 +1437,10 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, s = end_of_header; ns->routerstatus_list = smartlist_new(); - while (!strcmpstart(s, "r ")) { + while (eos - s >= 2 && fast_memeq(s, "r ", 2)) { if (ns->type != NS_TYPE_CONSENSUS) { vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns, + if (routerstatus_parse_entry_from_string(rs_area, &s, eos, rs_tokens, ns, rs, 0, 0)) { smartlist_add(ns->routerstatus_list, rs); } else { @@ -1435,7 +1448,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, } } else { routerstatus_t *rs; - if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, + if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, eos, + rs_tokens, NULL, NULL, ns->consensus_method, flav))) { @@ -1465,7 +1479,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, vote_routerstatus_t *, vrs) { if (! vrs->has_ed25519_listing || - tor_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN)) + fast_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN)) continue; if (digest256map_get(ed_id_map, vrs->ed25519_id) != NULL) { log_warn(LD_DIR, "Vote networkstatus ed25519 identities were not " @@ -1480,10 +1494,10 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, /* Parse footer; check signature. */ footer_tokens = smartlist_new(); - if ((end_of_footer = strstr(s, "\nnetwork-status-version "))) + if ((end_of_footer = tor_memstr(s, eos-s, "\nnetwork-status-version "))) ++end_of_footer; else - end_of_footer = s + strlen(s); + end_of_footer = eos; if (tokenize_string(area,s, end_of_footer, footer_tokens, networkstatus_vote_footer_token_table, 0)) { log_warn(LD_DIR, "Error tokenizing network-status vote footer."); diff --git a/src/feature/dirparse/ns_parse.h b/src/feature/dirparse/ns_parse.h index 10a6f9cefc..6a1ea85c92 100644 --- a/src/feature/dirparse/ns_parse.h +++ b/src/feature/dirparse/ns_parse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,18 +12,19 @@ #ifndef TOR_NS_PARSE_H #define TOR_NS_PARSE_H -int router_get_networkstatus_v3_hashes(const char *s, +int router_get_networkstatus_v3_hashes(const char *s, size_t len, common_digests_t *digests); -int router_get_networkstatus_v3_signed_boundaries(const char *s, +int router_get_networkstatus_v3_signed_boundaries(const char *s, size_t len, const char **start_out, const char **end_out); int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out, - const char *s); + const char *s, size_t len); int compare_vote_routerstatus_entries(const void **_a, const void **_b); int networkstatus_verify_bw_weights(networkstatus_t *ns, int); enum networkstatus_type_t; networkstatus_t *networkstatus_parse_vote_from_string(const char *s, + size_t len, const char **eos_out, enum networkstatus_type_t ns_type); @@ -35,11 +36,12 @@ STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str, struct memarea_t; STATIC routerstatus_t *routerstatus_parse_entry_from_string( struct memarea_t *area, - const char **s, smartlist_t *tokens, + const char **s, const char *eos, + smartlist_t *tokens, networkstatus_t *vote, vote_routerstatus_t *vote_rs, int consensus_method, consensus_flavor_t flav); -#endif +#endif /* defined(NS_PARSE_PRIVATE) */ -#endif +#endif /* !defined(TOR_NS_PARSE_H) */ diff --git a/src/feature/dirparse/parsecommon.c b/src/feature/dirparse/parsecommon.c index 1664a77bbe..ab465c4d7f 100644 --- a/src/feature/dirparse/parsecommon.c +++ b/src/feature/dirparse/parsecommon.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,6 +15,7 @@ #include "lib/string/printf.h" #include "lib/memarea/memarea.h" #include "lib/crypt_ops/crypto_rsa.h" +#include "lib/ctime/di_ops.h" #include <string.h> @@ -169,7 +170,6 @@ get_token_arguments(memarea_t *area, directory_token_t *tok, char *cp = mem; int j = 0; char *args[MAX_ARGS]; - memset(args, 0, sizeof(args)); while (*cp) { if (j == MAX_ARGS) return -1; @@ -251,6 +251,16 @@ token_check_object(memarea_t *area, const char *kwd, return tok; } +/** Return true iff the <b>memlen</b>-byte chunk of memory at + * <b>memlen</b> is the same length as <b>token</b>, and their + * contents are equal. */ +static bool +mem_eq_token(const void *mem, size_t memlen, const char *token) +{ + size_t len = strlen(token); + return memlen == len && fast_memeq(mem, token, len); +} + /** Helper function: read the next token from *s, advance *s to the end of the * token, and return the parsed token. Parse *<b>s</b> according to the list * of tokens in <b>table</b>. @@ -266,7 +276,7 @@ get_next_token(memarea_t *area, * attack, a bug, or some other nonsense. */ #define MAX_LINE_LENGTH (128*1024) - const char *next, *eol, *obstart; + const char *next, *eol; size_t obname_len; int i; directory_token_t *tok; @@ -290,7 +300,7 @@ get_next_token(memarea_t *area, next = find_whitespace_eos(*s, eol); - if (!strcmp_len(*s, "opt", next-*s)) { + if (mem_eq_token(*s, next-*s, "opt")) { /* Skip past an "opt" at the start of the line. */ *s = eat_whitespace_eos_no_nl(next, eol); next = find_whitespace_eos(*s, eol); @@ -301,7 +311,7 @@ get_next_token(memarea_t *area, /* Search the table for the appropriate entry. (I tried a binary search * instead, but it wasn't any faster.) */ for (i = 0; table[i].t ; ++i) { - if (!strcmp_len(*s, table[i].t, next-*s)) { + if (mem_eq_token(*s, next-*s, table[i].t)) { /* We've found the keyword. */ kwd = table[i].t; tok->tp = table[i].v; @@ -352,9 +362,8 @@ get_next_token(memarea_t *area, if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */ goto check_object; - obstart = *s; /* Set obstart to start of object spec */ if (eol - *s <= 16 || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */ - strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */ + !mem_eq_token(eol-5, 5, "-----") || /* nuls or invalid endings */ (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */ RET_ERR("Malformed object: bad begin line"); } @@ -373,8 +382,8 @@ get_next_token(memarea_t *area, eol = eos; /* Validate the ending tag, which should be 9 + NAME + 5 + eol */ if ((size_t)(eol-next) != 9+obname_len+5 || - strcmp_len(next+9, tok->object_type, obname_len) || - strcmp_len(eol-5, "-----", 5)) { + !mem_eq_token(next+9, obname_len, tok->object_type) || + !mem_eq_token(eol-5, 5, "-----")) { tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s", tok->object_type); ebuf[sizeof(ebuf)-1] = '\0'; @@ -383,28 +392,32 @@ get_next_token(memarea_t *area, if (next - *s > MAX_UNPARSED_OBJECT_SIZE) RET_ERR("Couldn't parse object: missing footer or object much too big."); + { + int r; + size_t maxsize = base64_decode_maxsize(next-*s); + tok->object_body = ALLOC(maxsize); + r = base64_decode(tok->object_body, maxsize, *s, next-*s); + if (r<0) + RET_ERR("Malformed object: bad base64-encoded data"); + tok->object_size = r; + } + if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */ if (o_syn != NEED_KEY && o_syn != NEED_KEY_1024 && o_syn != OBJ_OK) { RET_ERR("Unexpected public key."); } - tok->key = crypto_pk_new(); - if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart)) + tok->key = crypto_pk_asn1_decode(tok->object_body, tok->object_size); + if (! tok->key) RET_ERR("Couldn't parse public key."); } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */ if (o_syn != NEED_SKEY_1024 && o_syn != OBJ_OK) { RET_ERR("Unexpected private key."); } - tok->key = crypto_pk_new(); - if (crypto_pk_read_private_key1024_from_string(tok->key, - obstart, eol-obstart)) + tok->key = crypto_pk_asn1_decode_private(tok->object_body, + tok->object_size, + 1024); + if (! tok->key) RET_ERR("Couldn't parse private key."); - } else { /* If it's something else, try to base64-decode it */ - int r; - tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */ - r = base64_decode(tok->object_body, next-*s, *s, next-*s); - if (r<0) - RET_ERR("Malformed object: bad base64-encoded data"); - tok->object_size = r; } *s = eol; diff --git a/src/feature/dirparse/parsecommon.h b/src/feature/dirparse/parsecommon.h index ef74925b26..4db9a89f13 100644 --- a/src/feature/dirparse/parsecommon.h +++ b/src/feature/dirparse/parsecommon.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirparse/policy_parse.c b/src/feature/dirparse/policy_parse.c index 7562ae409b..28cd174686 100644 --- a/src/feature/dirparse/policy_parse.c +++ b/src/feature/dirparse/policy_parse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,7 +9,7 @@ * \brief Code to parse address policies. **/ -#define EXPOSE_ROUTERDESC_TOKEN_TABLE +#define ROUTERDESC_TOKEN_TABLE_PRIVATE #include "core/or/or.h" diff --git a/src/feature/dirparse/policy_parse.h b/src/feature/dirparse/policy_parse.h index e09ee5559f..7764069e66 100644 --- a/src/feature/dirparse/policy_parse.h +++ b/src/feature/dirparse/policy_parse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c index e44fbf77f9..f476beec66 100644 --- a/src/feature/dirparse/routerparse.c +++ b/src/feature/dirparse/routerparse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -50,7 +50,7 @@ * </ul> **/ -#define EXPOSE_ROUTERDESC_TOKEN_TABLE +#define ROUTERDESC_TOKEN_TABLE_PRIVATE #include "core/or/or.h" #include "app/config/config.h" @@ -591,8 +591,8 @@ router_parse_entry_from_string(const char *s, const char *end, "Relay's onion key had invalid exponent."); goto err; } - router_set_rsa_onion_pkey(tok->key, &router->onion_pkey, - &router->onion_pkey_len); + router->onion_pkey = tor_memdup(tok->object_body, tok->object_size); + router->onion_pkey_len = tok->object_size; crypto_pk_free(tok->key); if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { diff --git a/src/feature/dirparse/routerparse.h b/src/feature/dirparse/routerparse.h index f9a13f2168..519044e9b0 100644 --- a/src/feature/dirparse/routerparse.h +++ b/src/feature/dirparse/routerparse.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -40,7 +40,8 @@ int find_single_ipv6_orport(const smartlist_t *list, void routerparse_init(void); void routerparse_free_all(void); -#ifdef EXPOSE_ROUTERDESC_TOKEN_TABLE +#ifdef ROUTERDESC_TOKEN_TABLE_PRIVATE +#include "feature/dirparse/parsecommon.h" extern const struct token_rule_t routerdesc_token_table[]; #endif diff --git a/src/feature/dirparse/sigcommon.c b/src/feature/dirparse/sigcommon.c index 2019e09918..8b970d7d1f 100644 --- a/src/feature/dirparse/sigcommon.c +++ b/src/feature/dirparse/sigcommon.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirparse/sigcommon.h b/src/feature/dirparse/sigcommon.h index fdd8e839a9..c2ed9df494 100644 --- a/src/feature/dirparse/sigcommon.h +++ b/src/feature/dirparse/sigcommon.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -43,6 +43,6 @@ MOCK_DECL(STATIC int, signed_digest_equals, MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest, const char *start, size_t len, digest_algorithm_t alg)); -#endif +#endif /* defined(SIGCOMMON_PRIVATE) */ #endif /* !defined(TOR_SIGCOMMON_H) */ diff --git a/src/feature/dirparse/signing.c b/src/feature/dirparse/signing.c index 3ab40c3807..e420e5b6b9 100644 --- a/src/feature/dirparse/signing.c +++ b/src/feature/dirparse/signing.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/dirparse/signing.h b/src/feature/dirparse/signing.h index 2e3699baf8..7ca34bb14a 100644 --- a/src/feature/dirparse/signing.h +++ b/src/feature/dirparse/signing.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -20,4 +20,4 @@ int router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest, size_t digest_len, crypto_pk_t *private_key); -#endif +#endif /* !defined(TOR_SIGNING_H) */ diff --git a/src/feature/dirparse/unparseable.c b/src/feature/dirparse/unparseable.c index 941b5a1f6d..98b68f90bb 100644 --- a/src/feature/dirparse/unparseable.c +++ b/src/feature/dirparse/unparseable.c @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file unparseable.c + * @brief Dump unparseable objects to disk. + **/ + #define UNPARSEABLE_PRIVATE #include "core/or/or.h" diff --git a/src/feature/dirparse/unparseable.h b/src/feature/dirparse/unparseable.h index 853fe8cb0f..cff91c82cc 100644 --- a/src/feature/dirparse/unparseable.h +++ b/src/feature/dirparse/unparseable.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -26,7 +26,7 @@ void dump_desc_init(void); log_debug(LD_MM, "Area for %s has %lu allocated; using %lu.", \ name, (unsigned long)alloc, (unsigned long)used); \ STMT_END -#else /* !(defined(DEBUG_AREA_ALLOC)) */ +#else /* !defined(DEBUG_AREA_ALLOC) */ #define DUMP_AREA(a,name) STMT_NIL #endif /* defined(DEBUG_AREA_ALLOC) */ @@ -51,6 +51,6 @@ EXTERN(struct smartlist_t *, descs_dumped) MOCK_DECL(STATIC dumped_desc_t *, dump_desc_populate_one_file, (const char *dirname, const char *f)); STATIC void dump_desc_populate_fifo_from_directory(const char *dirname); -#endif +#endif /* defined(UNPARSEABLE_PRIVATE) */ #endif /* !defined(TOR_UNPARSEABLE_H) */ diff --git a/src/feature/feature.md b/src/feature/feature.md new file mode 100644 index 0000000000..acc3487e55 --- /dev/null +++ b/src/feature/feature.md @@ -0,0 +1,7 @@ +@dir /feature +@brief feature: domain-specific modules + +The "feature" directory has modules that Tor uses only for a particular +role or service, such as maintaining/using an onion service, operating as a +relay or a client, or being a directory authority. + diff --git a/src/feature/hibernate/.may_include b/src/feature/hibernate/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/hibernate/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/hibernate/feature_hibernate.md b/src/feature/hibernate/feature_hibernate.md new file mode 100644 index 0000000000..0eb5ffea0d --- /dev/null +++ b/src/feature/hibernate/feature_hibernate.md @@ -0,0 +1,14 @@ +@dir /feature/hibernate +@brief feature/hibernate: Bandwidth accounting and hibernation (!) + +This module implements two features that are only somewhat related, and +should probably be separated in the future. One feature is bandwidth +accounting (making sure we use no more than so many gigabytes in a day) and +hibernation (avoiding network activity while we have used up all/most of our +configured gigabytes). The other feature is clean shutdown, where we stop +accepting new connections for a while and give the old ones time to close. + +The two features are related only in the sense that "soft hibernation" (being +almost out of ) is very close to the "shutting down" state. But it would be +better in the long run to make the two completely separate. + diff --git a/src/feature/hibernate/hibernate.c b/src/feature/hibernate/hibernate.c index 09932c97ac..82c33659aa 100644 --- a/src/feature/hibernate/hibernate.c +++ b/src/feature/hibernate/hibernate.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -35,8 +35,9 @@ hibernating, phase 2: #include "core/mainloop/connection.h" #include "core/or/connection_edge.h" #include "core/or/connection_or.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "lib/crypt_ops/crypto_rand.h" +#include "lib/defs/time.h" #include "feature/hibernate/hibernate.h" #include "core/mainloop/mainloop.h" #include "feature/relay/router.h" @@ -56,7 +57,7 @@ hibernating, phase 2: * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> #endif /* defined(HAVE_SYSTEMD) */ @@ -66,8 +67,9 @@ static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL; /** If are hibernating, when do we plan to wake up? Set to 0 if we * aren't hibernating. */ static time_t hibernate_end_time = 0; -/** If we are shutting down, when do we plan finally exit? Set to 0 if - * we aren't shutting down. */ +/** If we are shutting down, when do we plan to finally exit? Set to 0 if we + * aren't shutting down. (This is obsolete; scheduled shutdowns are supposed + * to happen from mainloop_schedule_shutdown() now.) */ static time_t shutdown_time = 0; /** A timed event that we'll use when it's time to wake up from @@ -560,7 +562,7 @@ time_to_record_bandwidth_usage(time_t now) /* Note every 600 sec */ #define NOTE_INTERVAL (600) /* Or every 20 megabytes */ -#define NOTE_BYTES 20*(1024*1024) +#define NOTE_BYTES (20*1024*1024) static uint64_t last_read_bytes_noted = 0; static uint64_t last_written_bytes_noted = 0; static time_t last_time_noted = 0; @@ -813,7 +815,7 @@ hibernate_soft_limit_reached(void) * We want to stop accepting connections when ALL of the following are true: * - We expect to use up the remaining bytes in under 3 hours * - We have used up 95% of our bytes. - * - We have less than 500MB of bytes left. + * - We have less than 500MBytes left. */ uint64_t soft_limit = (uint64_t) (acct_max * SOFT_LIM_PCT); if (acct_max > SOFT_LIM_BYTES && acct_max - SOFT_LIM_BYTES > soft_limit) { @@ -831,8 +833,6 @@ hibernate_soft_limit_reached(void) return get_accounting_bytes() >= soft_limit; } -#define TOR_USEC_PER_SEC (1000000) - /** Called when we get a SIGINT, or when bandwidth soft limit is * reached. Puts us into "loose hibernation": we don't accept new * connections, but we continue handling old ones. */ @@ -867,7 +867,13 @@ hibernate_begin(hibernate_state_t new_state, time_t now) log_notice(LD_GENERAL,"Interrupt: we have stopped accepting new " "connections, and will shut down in %d seconds. Interrupt " "again to exit now.", options->ShutdownWaitLength); - shutdown_time = time(NULL) + options->ShutdownWaitLength; + /* We add an arbitrary delay here so that even if something goes wrong + * with the mainloop shutdown code, we can still shutdown from + * consider_hibernation() if we call it... but so that the + * mainloop_schedule_shutdown() mechanism will be the first one called. + */ + shutdown_time = time(NULL) + options->ShutdownWaitLength + 5; + mainloop_schedule_shutdown(options->ShutdownWaitLength); #ifdef HAVE_SYSTEMD /* tell systemd that we may need more than the default 90 seconds to shut * down so they don't kill us. add some extra time to actually finish @@ -887,7 +893,7 @@ hibernate_begin(hibernate_state_t new_state, time_t now) */ sd_notifyf(0, "EXTEND_TIMEOUT_USEC=%" PRIu64, ((uint64_t)(options->ShutdownWaitLength) + 30) * TOR_USEC_PER_SEC); -#endif +#endif /* defined(HAVE_SYSTEMD) */ } else { /* soft limit reached */ hibernate_end_time = interval_end_time; } @@ -1096,11 +1102,12 @@ consider_hibernation(time_t now) hibernate_state_t prev_state = hibernate_state; /* If we're in 'exiting' mode, then we just shut down after the interval - * elapses. */ + * elapses. The mainloop was supposed to catch this via + * mainloop_schedule_shutdown(), but apparently it didn't. */ if (hibernate_state == HIBERNATE_STATE_EXITING) { tor_assert(shutdown_time); if (shutdown_time <= now) { - log_notice(LD_GENERAL, "Clean shutdown finished. Exiting."); + log_notice(LD_BUG, "Mainloop did not catch shutdown event; exiting."); tor_shutdown_event_loop_and_exit(0); } return; /* if exiting soon, don't worry about bandwidth limits */ @@ -1112,7 +1119,7 @@ consider_hibernation(time_t now) if (hibernate_end_time > now && accounting_enabled) { /* If we're hibernating, don't wake up until it's time, regardless of * whether we're in a new interval. */ - return ; + return; } else { hibernate_end_time_elapsed(now); } @@ -1240,8 +1247,6 @@ on_hibernate_state_change(hibernate_state_t prev_state) if (prev_state != HIBERNATE_STATE_INITIAL) { rescan_periodic_events(get_options()); } - - reschedule_per_second_timer(); } /** Free all resources held by the accounting module */ diff --git a/src/feature/hibernate/hibernate.h b/src/feature/hibernate/hibernate.h index 3309ef0ce3..2383658b20 100644 --- a/src/feature/hibernate/hibernate.h +++ b/src/feature/hibernate/hibernate.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -32,6 +32,7 @@ int getinfo_helper_accounting(control_connection_t *conn, const char **errmsg); uint64_t get_accounting_max_total(void); void accounting_free_all(void); +bool accounting_tor_is_dormant(void); #ifdef HIBERNATE_PRIVATE /** Possible values of hibernate_state */ diff --git a/src/feature/hibernate/include.am b/src/feature/hibernate/include.am new file mode 100644 index 0000000000..355e591392 --- /dev/null +++ b/src/feature/hibernate/include.am @@ -0,0 +1,8 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/hibernate/hibernate.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/hibernate/hibernate.h diff --git a/src/feature/hs/.may_include b/src/feature/hs/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/hs/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/hs/feature_hs.md b/src/feature/hs/feature_hs.md new file mode 100644 index 0000000000..299d07e014 --- /dev/null +++ b/src/feature/hs/feature_hs.md @@ -0,0 +1,8 @@ +@dir /feature/hs +@brief feature/hs: v3 (current) onion service protocol + +This directory implements the v3 onion service protocol, +as specified in +[rend-spec-v3.txt](https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt). + + diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c index 042ec55fa4..ef5e88e947 100644 --- a/src/feature/hs/hs_cache.c +++ b/src/feature/hs/hs_cache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -28,12 +28,27 @@ static int cached_client_descriptor_has_expired(time_t now, const hs_cache_client_descriptor_t *cached_desc); +/** Helper function: Return true iff the cache entry has a decrypted + * descriptor. + * + * A NULL desc object in the entry means that we were not able to decrypt the + * descriptor because we are likely lacking client authorization. It is still + * a valid entry but some operations can't be done without the decrypted + * descriptor thus this function MUST be used to safe guard access to the + * decrypted desc object. */ +static inline bool +entry_has_decrypted_descriptor(const hs_cache_client_descriptor_t *entry) +{ + tor_assert(entry); + return (entry->desc != NULL); +} + /********************** Directory HS cache ******************/ -/* Directory descriptor cache. Map indexed by blinded key. */ +/** Directory descriptor cache. Map indexed by blinded key. */ static digest256map_t *hs_cache_v3_dir; -/* Remove a given descriptor from our cache. */ +/** Remove a given descriptor from our cache. */ static void remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc) { @@ -41,7 +56,7 @@ remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc) digest256map_remove(hs_cache_v3_dir, desc->key); } -/* Store a given descriptor in our cache. */ +/** Store a given descriptor in our cache. */ static void store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc) { @@ -49,7 +64,7 @@ store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc) digest256map_set(hs_cache_v3_dir, desc->key, desc); } -/* Query our cache and return the entry or NULL if not found. */ +/** Query our cache and return the entry or NULL if not found. */ static hs_cache_dir_descriptor_t * lookup_v3_desc_as_dir(const uint8_t *key) { @@ -60,7 +75,7 @@ lookup_v3_desc_as_dir(const uint8_t *key) #define cache_dir_desc_free(val) \ FREE_AND_NULL(hs_cache_dir_descriptor_t, cache_dir_desc_free_, (val)) -/* Free a directory descriptor object. */ +/** Free a directory descriptor object. */ static void cache_dir_desc_free_(hs_cache_dir_descriptor_t *desc) { @@ -72,7 +87,7 @@ cache_dir_desc_free_(hs_cache_dir_descriptor_t *desc) tor_free(desc); } -/* Helper function: Use by the free all function using the digest256map +/** Helper function: Use by the free all function using the digest256map * interface to cache entries. */ static void cache_dir_desc_free_void(void *ptr) @@ -80,7 +95,7 @@ cache_dir_desc_free_void(void *ptr) cache_dir_desc_free_(ptr); } -/* Create a new directory cache descriptor object from a encoded descriptor. +/** Create a new directory cache descriptor object from a encoded descriptor. * On success, return the heap-allocated cache object, otherwise return NULL if * we can't decode the descriptor. */ static hs_cache_dir_descriptor_t * @@ -110,7 +125,7 @@ cache_dir_desc_new(const char *desc) return NULL; } -/* Return the size of a cache entry in bytes. */ +/** Return the size of a cache entry in bytes. */ static size_t cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry) { @@ -118,7 +133,7 @@ cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry) + strlen(entry->encoded_desc)); } -/* Try to store a valid version 3 descriptor in the directory cache. Return 0 +/** Try to store a valid version 3 descriptor in the directory cache. Return 0 * on success else a negative value is returned indicating that we have a * newer version in our cache. On error, caller is responsible to free the * given descriptor desc. */ @@ -168,7 +183,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) return -1; } -/* Using the query which is the base64 encoded blinded key of a version 3 +/** Using the query which is the base64 encoded blinded key of a version 3 * descriptor, lookup in our directory cache the entry. If found, 1 is * returned and desc_out is populated with a newly allocated string being the * encoded descriptor. If not found, 0 is returned and desc_out is untouched. @@ -203,7 +218,7 @@ cache_lookup_v3_as_dir(const char *query, const char **desc_out) return -1; } -/* Clean the v3 cache by removing any entry that has expired using the +/** Clean the v3 cache by removing any entry that has expired using the * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning * process will use the lifetime found in the plaintext data section. Return * the number of bytes cleaned. */ @@ -253,7 +268,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) return bytes_removed; } -/* Given an encoded descriptor, store it in the directory cache depending on +/** Given an encoded descriptor, store it in the directory cache depending on * which version it is. Return a negative value on error. On success, 0 is * returned. */ int @@ -288,7 +303,7 @@ hs_cache_store_as_dir(const char *desc) return -1; } -/* Using the query, lookup in our directory cache the entry. If found, 1 is +/** Using the query, lookup in our directory cache the entry. If found, 1 is * returned and desc_out is populated with a newly allocated string being * the encoded descriptor. If not found, 0 is returned and desc_out is * untouched. On error, a negative value is returned and desc_out is @@ -313,7 +328,7 @@ hs_cache_lookup_as_dir(uint32_t version, const char *query, return found; } -/* Clean all directory caches using the current time now. */ +/** Clean all directory caches using the current time now. */ void hs_cache_clean_as_dir(time_t now) { @@ -330,23 +345,38 @@ hs_cache_clean_as_dir(time_t now) /********************** Client-side HS cache ******************/ -/* Client-side HS descriptor cache. Map indexed by service identity key. */ +/** Client-side HS descriptor cache. Map indexed by service identity key. */ static digest256map_t *hs_cache_v3_client; -/* Client-side introduction point state cache. Map indexed by service public +/** Client-side introduction point state cache. Map indexed by service public * identity key (onion address). It contains hs_cache_client_intro_state_t * objects all related to a specific service. */ static digest256map_t *hs_cache_client_intro_state; -/* Return the size of a client cache entry in bytes. */ +/** Return the size of a client cache entry in bytes. */ static size_t cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) { - return sizeof(*entry) + - strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); + size_t size = 0; + + if (entry == NULL) { + goto end; + } + size += sizeof(*entry); + + if (entry->encoded_desc) { + size += strlen(entry->encoded_desc); + } + + if (entry_has_decrypted_descriptor(entry)) { + size += hs_desc_obj_size(entry->desc); + } + + end: + return size; } -/* Remove a given descriptor from our cache. */ +/** Remove a given descriptor from our cache. */ static void remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) { @@ -356,7 +386,7 @@ remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) rend_cache_decrement_allocation(cache_get_client_entry_size(desc)); } -/* Store a given descriptor in our cache. */ +/** Store a given descriptor in our cache. */ static void store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) { @@ -366,7 +396,7 @@ store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) rend_cache_increment_allocation(cache_get_client_entry_size(desc)); } -/* Query our cache and return the entry or NULL if not found or if expired. */ +/** Query our cache and return the entry or NULL if not found or if expired. */ STATIC hs_cache_client_descriptor_t * lookup_v3_desc_as_client(const uint8_t *key) { @@ -389,15 +419,17 @@ lookup_v3_desc_as_client(const uint8_t *key) return cached_desc; } -/* Parse the encoded descriptor in <b>desc_str</b> using - * <b>service_identity_pk<b> to decrypt it first. +/** Parse the encoded descriptor in <b>desc_str</b> using + * <b>service_identity_pk</b> to decrypt it first. * * If everything goes well, allocate and return a new * hs_cache_client_descriptor_t object. In case of error, return NULL. */ static hs_cache_client_descriptor_t * cache_client_desc_new(const char *desc_str, - const ed25519_public_key_t *service_identity_pk) + const ed25519_public_key_t *service_identity_pk, + hs_desc_decode_status_t *decode_status_out) { + hs_desc_decode_status_t ret; hs_descriptor_t *desc = NULL; hs_cache_client_descriptor_t *client_desc = NULL; @@ -405,10 +437,24 @@ cache_client_desc_new(const char *desc_str, tor_assert(service_identity_pk); /* Decode the descriptor we just fetched. */ - if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) { + ret = hs_client_decode_descriptor(desc_str, service_identity_pk, &desc); + if (ret != HS_DESC_DECODE_OK && + ret != HS_DESC_DECODE_NEED_CLIENT_AUTH && + ret != HS_DESC_DECODE_BAD_CLIENT_AUTH) { + /* In the case of a missing or bad client authorization, we'll keep the + * descriptor in the cache because those credentials can arrive later. */ goto end; } - tor_assert(desc); + /* Make sure we do have a descriptor if decoding was successful. */ + if (ret == HS_DESC_DECODE_OK) { + tor_assert(desc); + } else { + if (BUG(desc != NULL)) { + /* We are not suppose to have a descriptor if the decoding code is not + * indicating success. Just in case, bail early to recover. */ + goto end; + } + } /* All is good: make a cache object for this descriptor */ client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t)); @@ -421,6 +467,9 @@ cache_client_desc_new(const char *desc_str, client_desc->encoded_desc = tor_strdup(desc_str); end: + if (decode_status_out) { + *decode_status_out = ret; + } return client_desc; } @@ -449,7 +498,7 @@ cache_client_desc_free_void(void *ptr) cache_client_desc_free(desc); } -/* Return a newly allocated and initialized hs_cache_intro_state_t object. */ +/** Return a newly allocated and initialized hs_cache_intro_state_t object. */ static hs_cache_intro_state_t * cache_intro_state_new(void) { @@ -461,21 +510,21 @@ cache_intro_state_new(void) #define cache_intro_state_free(val) \ FREE_AND_NULL(hs_cache_intro_state_t, cache_intro_state_free_, (val)) -/* Free an hs_cache_intro_state_t object. */ +/** Free an hs_cache_intro_state_t object. */ static void cache_intro_state_free_(hs_cache_intro_state_t *state) { tor_free(state); } -/* Helper function: used by the free all function. */ +/** Helper function: used by the free all function. */ static void cache_intro_state_free_void(void *state) { cache_intro_state_free_(state); } -/* Return a newly allocated and initialized hs_cache_client_intro_state_t +/** Return a newly allocated and initialized hs_cache_client_intro_state_t * object. */ static hs_cache_client_intro_state_t * cache_client_intro_state_new(void) @@ -489,7 +538,7 @@ cache_client_intro_state_new(void) FREE_AND_NULL(hs_cache_client_intro_state_t, \ cache_client_intro_state_free_, (val)) -/* Free a cache_client_intro_state object. */ +/** Free a cache_client_intro_state object. */ static void cache_client_intro_state_free_(hs_cache_client_intro_state_t *cache) { @@ -500,14 +549,14 @@ cache_client_intro_state_free_(hs_cache_client_intro_state_t *cache) tor_free(cache); } -/* Helper function: used by the free all function. */ +/** Helper function: used by the free all function. */ static void cache_client_intro_state_free_void(void *entry) { cache_client_intro_state_free_(entry); } -/* For the given service identity key service_pk and an introduction +/** For the given service identity key service_pk and an introduction * authentication key auth_key, lookup the intro state object. Return 1 if * found and put it in entry if not NULL. Return 0 if not found and entry is * untouched. */ @@ -542,7 +591,7 @@ cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk, return 0; } -/* Note the given failure in state. */ +/** Note the given failure in state. */ static void cache_client_intro_state_note(hs_cache_intro_state_t *state, rend_intro_point_failure_t failure) @@ -564,7 +613,7 @@ cache_client_intro_state_note(hs_cache_intro_state_t *state, } } -/* For the given service identity key service_pk and an introduction +/** For the given service identity key service_pk and an introduction * authentication key auth_key, add an entry in the client intro state cache * If no entry exists for the service, it will create one. If state is non * NULL, it will point to the new intro state entry. */ @@ -598,7 +647,7 @@ cache_client_intro_state_add(const ed25519_public_key_t *service_pk, } } -/* Remove every intro point state entry from cache that has been created +/** Remove every intro point state entry from cache that has been created * before or at the cutoff. */ static void cache_client_intro_state_clean(time_t cutoff, @@ -615,7 +664,7 @@ cache_client_intro_state_clean(time_t cutoff, } DIGEST256MAP_FOREACH_END; } -/* Return true iff no intro points are in this cache. */ +/** Return true iff no intro points are in this cache. */ static int cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache) { @@ -636,9 +685,25 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) tor_assert(client_desc); /* Check if we already have a descriptor from this HS in cache. If we do, - * check if this descriptor is newer than the cached one */ + * check if this descriptor is newer than the cached one only if we have a + * decoded descriptor. We do keep non-decoded descriptor that requires + * client authorization. */ cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); if (cache_entry != NULL) { + /* If the current or the new cache entry don't have a decrypted descriptor + * (missing client authorization), we always replace the current one with + * the new one. Reason is that we can't inspect the revision counter + * within the plaintext data so we blindly replace. */ + if (!entry_has_decrypted_descriptor(cache_entry) || + !entry_has_decrypted_descriptor(client_desc)) { + remove_v3_desc_as_client(cache_entry); + cache_client_desc_free(cache_entry); + goto store; + } + + /* From this point on, we know that the decrypted descriptor is in the + * current entry and new object thus safe to access. */ + /* If we have an entry in our cache that has a revision counter greater * than the one we just fetched, discard the one we fetched. */ if (cache_entry->desc->plaintext_data.revision_counter > @@ -658,6 +723,7 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) cache_client_desc_free(cache_entry); } + store: /* Store descriptor in cache */ store_v3_desc_as_client(client_desc); @@ -665,7 +731,7 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) return 0; } -/* Return true iff the cached client descriptor at <b>cached_desc</b has +/** Return true iff the cached client descriptor at <b>cached_desc</b> has * expired. */ static int cached_client_descriptor_has_expired(time_t now, @@ -690,7 +756,7 @@ cached_client_descriptor_has_expired(time_t now, return 0; } -/* clean the client cache using now as the current time. Return the total size +/** clean the client cache using now as the current time. Return the total size * of removed bytes from the cache. */ static size_t cache_clean_v3_as_client(time_t now) @@ -713,6 +779,15 @@ cache_clean_v3_as_client(time_t now) MAP_DEL_CURRENT(key); entry_size = cache_get_client_entry_size(entry); bytes_removed += entry_size; + + /* We just removed an old descriptor. We need to close all intro circuits + * if the descriptor is decrypted so we don't have leftovers that can be + * selected while lacking a descriptor. Circuits are selected by intro + * authentication key thus we need the descriptor. We leave the rendezvous + * circuits opened because they could be in use. */ + if (entry_has_decrypted_descriptor(entry)) { + hs_client_close_intro_circuits_from_desc(entry->desc); + } /* Entry is not in the cache anymore, destroy it. */ cache_client_desc_free(entry); /* Update our OOM. We didn't use the remove() function because we are in @@ -750,7 +825,9 @@ hs_cache_lookup_encoded_as_client(const ed25519_public_key_t *key) } /** Public API: Given the HS ed25519 identity public key in <b>key</b>, return - * its HS descriptor if it's stored in our cache, or NULL if not. */ + * its HS descriptor if it's stored in our cache, or NULL if not or if the + * descriptor was never decrypted. The later can happen if we are waiting for + * client authorization to be added. */ const hs_descriptor_t * hs_cache_lookup_as_client(const ed25519_public_key_t *key) { @@ -759,27 +836,41 @@ hs_cache_lookup_as_client(const ed25519_public_key_t *key) tor_assert(key); cached_desc = lookup_v3_desc_as_client(key->pubkey); - if (cached_desc) { - tor_assert(cached_desc->desc); + if (cached_desc && entry_has_decrypted_descriptor(cached_desc)) { return cached_desc->desc; } return NULL; } -/** Public API: Given an encoded descriptor, store it in the client HS - * cache. Return -1 on error, 0 on success .*/ -int +/** Public API: Given an encoded descriptor, store it in the client HS cache. + * Return a decode status which changes how we handle the SOCKS connection + * depending on its value: + * + * HS_DESC_DECODE_OK: Returned on success. Descriptor was properly decoded + * and is now stored. + * + * HS_DESC_DECODE_NEED_CLIENT_AUTH: Client authorization is needed but the + * descriptor was still stored. + * + * HS_DESC_DECODE_BAD_CLIENT_AUTH: Client authorization for this descriptor + * was not usable but the descriptor was + * still stored. + * + * Any other codes means indicate where the error occured and the descriptor + * was not stored. */ +hs_desc_decode_status_t hs_cache_store_as_client(const char *desc_str, const ed25519_public_key_t *identity_pk) { + hs_desc_decode_status_t ret; hs_cache_client_descriptor_t *client_desc = NULL; tor_assert(desc_str); tor_assert(identity_pk); /* Create client cache descriptor object */ - client_desc = cache_client_desc_new(desc_str, identity_pk); + client_desc = cache_client_desc_new(desc_str, identity_pk, &ret); if (!client_desc) { log_warn(LD_GENERAL, "HSDesc parsing failed!"); log_debug(LD_GENERAL, "Failed to parse HSDesc: %s.", escaped(desc_str)); @@ -788,17 +879,54 @@ hs_cache_store_as_client(const char *desc_str, /* Push it to the cache */ if (cache_store_as_client(client_desc) < 0) { + ret = HS_DESC_DECODE_GENERIC_ERROR; goto err; } - return 0; + return ret; err: cache_client_desc_free(client_desc); - return -1; + return ret; } -/* Clean all client caches using the current time now. */ +/** Remove and free a client cache descriptor entry for the given onion + * service ed25519 public key. If the descriptor is decoded, the intro + * circuits are closed if any. + * + * This does nothing if no descriptor exists for the given key. */ +void +hs_cache_remove_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (!cached_desc) { + return; + } + /* If we have a decrypted/decoded descriptor, attempt to close its + * introduction circuit(s). We shouldn't have circuit(s) without a + * descriptor else it will lead to a failure. */ + if (entry_has_decrypted_descriptor(cached_desc)) { + hs_client_close_intro_circuits_from_desc(cached_desc->desc); + } + /* Remove and free. */ + remove_v3_desc_as_client(cached_desc); + cache_client_desc_free(cached_desc); + + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + digest256_to_base64(key_b64, (const char *) key); + log_info(LD_REND, "Onion service v3 descriptor '%s' removed " + "from client cache", + safe_str_client(key_b64)); + } +} + +/** Clean all client caches using the current time now. */ void hs_cache_clean_as_client(time_t now) { @@ -809,7 +937,7 @@ hs_cache_clean_as_client(time_t now) cache_clean_v3_as_client(now); } -/* Purge the client descriptor cache. */ +/** Purge the client descriptor cache. */ void hs_cache_purge_as_client(void) { @@ -826,7 +954,7 @@ hs_cache_purge_as_client(void) log_info(LD_REND, "Hidden service client descriptor cache purged."); } -/* For a given service identity public key and an introduction authentication +/** For a given service identity public key and an introduction authentication * key, note the given failure in the client intro state cache. */ void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, @@ -848,7 +976,7 @@ hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, cache_client_intro_state_note(entry, failure); } -/* For a given service identity public key and an introduction authentication +/** For a given service identity public key and an introduction authentication * key, return true iff it is present in the failure cache. */ const hs_cache_intro_state_t * hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, @@ -859,7 +987,7 @@ hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, return state; } -/* Cleanup the client introduction state cache. */ +/** Cleanup the client introduction state cache. */ void hs_cache_client_intro_state_clean(time_t now) { @@ -879,7 +1007,7 @@ hs_cache_client_intro_state_clean(time_t now) } DIGEST256MAP_FOREACH_END; } -/* Purge the client introduction state cache. */ +/** Purge the client introduction state cache. */ void hs_cache_client_intro_state_purge(void) { @@ -893,9 +1021,41 @@ hs_cache_client_intro_state_purge(void) "cache purged."); } +/* This is called when new client authorization was added to the global state. + * It attemps to decode the descriptor of the given service identity key. + * + * Return true if decoding was successful else false. */ +bool +hs_cache_client_new_auth_parse(const ed25519_public_key_t *service_pk) +{ + bool ret = false; + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(service_pk); + + if (!hs_cache_v3_client) { + return false; + } + + cached_desc = lookup_v3_desc_as_client(service_pk->pubkey); + if (cached_desc == NULL || entry_has_decrypted_descriptor(cached_desc)) { + /* No entry for that service or the descriptor is already decoded. */ + goto end; + } + + /* Attempt a decode. If we are successful, inform the caller. */ + if (hs_client_decode_descriptor(cached_desc->encoded_desc, service_pk, + &cached_desc->desc) == HS_DESC_DECODE_OK) { + ret = true; + } + + end: + return ret; +} + /**************** Generics *********************************/ -/* Do a round of OOM cleanup on all directory caches. Return the amount of +/** Do a round of OOM cleanup on all directory caches. Return the amount of * removed bytes. It is possible that the returned value is lower than * min_remove_bytes if the caches get emptied out so the caller should be * aware of this. */ @@ -949,7 +1109,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes) return bytes_removed; } -/* Return the maximum size of a v3 HS descriptor. */ +/** Return the maximum size of a v3 HS descriptor. */ unsigned int hs_cache_get_max_descriptor_size(void) { @@ -958,7 +1118,7 @@ hs_cache_get_max_descriptor_size(void) HS_DESC_MAX_LEN, 1, INT32_MAX); } -/* Initialize the hidden service cache subsystem. */ +/** Initialize the hidden service cache subsystem. */ void hs_cache_init(void) { @@ -973,7 +1133,7 @@ hs_cache_init(void) hs_cache_client_intro_state = digest256map_new(); } -/* Cleanup the hidden service cache subsystem. */ +/** Cleanup the hidden service cache subsystem. */ void hs_cache_free_all(void) { diff --git a/src/feature/hs/hs_cache.h b/src/feature/hs/hs_cache.h index 079d31d437..bb3c77f224 100644 --- a/src/feature/hs/hs_cache.h +++ b/src/feature/hs/hs_cache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,47 +18,47 @@ struct ed25519_public_key_t; -/* This is the maximum time an introduction point state object can stay in the +/** This is the maximum time an introduction point state object can stay in the * client cache in seconds (2 mins or 120 seconds). */ #define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60) -/* Introduction point state. */ +/** Introduction point state. */ typedef struct hs_cache_intro_state_t { - /* When this entry was created and put in the cache. */ + /** When this entry was created and put in the cache. */ time_t created_ts; - /* Did it suffered a generic error? */ + /** Did it suffered a generic error? */ unsigned int error : 1; - /* Did it timed out? */ + /** Did it timed out? */ unsigned int timed_out : 1; - /* How many times we tried to reached it and it was unreachable. */ + /** How many times we tried to reached it and it was unreachable. */ uint32_t unreachable_count; } hs_cache_intro_state_t; typedef struct hs_cache_client_intro_state_t { - /* Contains hs_cache_intro_state_t object indexed by introduction point + /** Contains hs_cache_intro_state_t object indexed by introduction point * authentication key. */ digest256map_t *intro_points; } hs_cache_client_intro_state_t; -/* Descriptor representation on the directory side which is a subset of +/** Descriptor representation on the directory side which is a subset of * information that the HSDir can decode and serve it. */ typedef struct hs_cache_dir_descriptor_t { - /* This object is indexed using the blinded pubkey located in the plaintext + /** This object is indexed using the blinded pubkey located in the plaintext * data which is populated only once the descriptor has been successfully * decoded and validated. This simply points to that pubkey. */ const uint8_t *key; - /* When does this entry has been created. Used to expire entries. */ + /** When does this entry has been created. Used to expire entries. */ time_t created_ts; - /* Descriptor plaintext information. Obviously, we can't decrypt the + /** Descriptor plaintext information. Obviously, we can't decrypt the * encrypted part of the descriptor. */ hs_desc_plaintext_data_t *plaintext_data; - /* Encoded descriptor which is basically in text form. It's a NUL terminated + /** Encoded descriptor which is basically in text form. It's a NUL terminated * string thus safe to strlen(). */ char *encoded_desc; } hs_cache_dir_descriptor_t; @@ -83,8 +83,9 @@ const hs_descriptor_t * hs_cache_lookup_as_client(const struct ed25519_public_key_t *key); const char * hs_cache_lookup_encoded_as_client(const struct ed25519_public_key_t *key); -int hs_cache_store_as_client(const char *desc_str, - const struct ed25519_public_key_t *identity_pk); +hs_desc_decode_status_t hs_cache_store_as_client(const char *desc_str, + const struct ed25519_public_key_t *identity_pk); +void hs_cache_remove_as_client(const struct ed25519_public_key_t *key); void hs_cache_clean_as_client(time_t now); void hs_cache_purge_as_client(void); @@ -99,24 +100,28 @@ const hs_cache_intro_state_t *hs_cache_client_intro_state_find( void hs_cache_client_intro_state_clean(time_t now); void hs_cache_client_intro_state_purge(void); +bool hs_cache_client_new_auth_parse(const ed25519_public_key_t *service_pk); + #ifdef HS_CACHE_PRIVATE #include "lib/crypt_ops/crypto_ed25519.h" /** Represents a locally cached HS descriptor on a hidden service client. */ typedef struct hs_cache_client_descriptor_t { - /* This object is indexed using the service identity public key */ + /** This object is indexed using the service identity public key */ struct ed25519_public_key_t key; - /* When will this entry expire? We expire cached client descriptors in the + /** When will this entry expire? We expire cached client descriptors in the * start of the next time period, since that's when clients need to start * using the next blinded key of the service. */ time_t expiration_ts; - /* The cached descriptor, this object is the owner. It can't be NULL. A - * cache object without a valid descriptor is not possible. */ + /** The cached decoded descriptor, this object is the owner. This can be + * NULL if the descriptor couldn't be decoded due to missing or bad client + * authorization. It can be decoded later from the encoded_desc object if + * the proper client authorization is given tor. */ hs_descriptor_t *desc; - /* Encoded descriptor in string form. Can't be NULL. */ + /** Encoded descriptor in string form. Can't be NULL. */ char *encoded_desc; } hs_cache_client_descriptor_t; diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 613ffe7260..52bd663200 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -24,7 +24,7 @@ #include "trunnel/hs/cell_introduce1.h" #include "trunnel/hs/cell_rendezvous.h" -/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is +/** Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is * the cell content up to the ENCRYPTED section of length encoded_cell_len. * The encrypted param is the start of the ENCRYPTED section of length * encrypted_len. The mac_key is the key needed for the computation of the MAC @@ -67,7 +67,7 @@ compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, memwipe(mac_msg, 0, sizeof(mac_msg)); } -/* From a set of keys, subcredential and the ENCRYPTED section of an +/** From a set of keys, subcredential and the ENCRYPTED section of an * INTRODUCE2 cell, return a newly allocated intro cell keys structure. * Finally, the client public key is copied in client_pk. On error, return * NULL. */ @@ -101,7 +101,7 @@ get_introduce2_key_material(const ed25519_public_key_t *auth_key, return keys; } -/* Using the given encryption key, decrypt the encrypted_section of length +/** Using the given encryption key, decrypt the encrypted_section of length * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated * buffer containing the decrypted data. On decryption failure, NULL is * returned. */ @@ -136,7 +136,7 @@ decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, return decrypted; } -/* Given a pointer to the decrypted data of the ENCRYPTED section of an +/** Given a pointer to the decrypted data of the ENCRYPTED section of an * INTRODUCE2 cell of length decrypted_len, parse and validate the cell * content. Return a newly allocated cell structure or NULL on error. The * circuit and service object are only used for logging purposes. */ @@ -188,7 +188,7 @@ parse_introduce2_encrypted(const uint8_t *decrypted_data, return NULL; } -/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA +/** Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA * encryption key. The encoded cell is put in cell_out that MUST at least be * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on * success else a negative value and cell_out is untouched. */ @@ -210,7 +210,7 @@ build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, return cell_len; } -/* Parse an INTRODUCE2 cell from payload of size payload_len for the given +/** Parse an INTRODUCE2 cell from payload of size payload_len for the given * service and circuit which are used only for logging purposes. The resulting * parsed cell is put in cell_ptr_out. * @@ -249,7 +249,7 @@ parse_introduce2_cell(const hs_service_t *service, return -1; } -/* Set the onion public key onion_pk in cell, the encrypted section of an +/** Set the onion public key onion_pk in cell, the encrypted section of an * INTRODUCE1 cell. */ static void introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, @@ -266,7 +266,7 @@ introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, trn_cell_introduce_encrypted_getlen_onion_key(cell)); } -/* Set the link specifiers in lspecs in cell, the encrypted section of an +/** Set the link specifiers in lspecs in cell, the encrypted section of an * INTRODUCE1 cell. */ static void introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, @@ -286,7 +286,7 @@ introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, trn_cell_introduce_encrypted_add_nspecs(cell, ls)); } -/* Set padding in the enc_cell only if needed that is the total length of both +/** Set padding in the enc_cell only if needed that is the total length of both * sections are below the mininum required for an INTRODUCE1 cell. */ static void introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, @@ -306,7 +306,7 @@ introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, } } -/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell +/** Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell * and the INTRODUCE1 data. * * This can't fail but it is very important that the caller sets every field @@ -394,7 +394,7 @@ introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell, tor_free(encrypted); } -/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means +/** Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means * set it, encrypt it and encode it. */ static void introduce1_set_encrypted(trn_cell_introduce1_t *cell, @@ -435,7 +435,7 @@ introduce1_set_encrypted(trn_cell_introduce1_t *cell, trn_cell_introduce_encrypted_free(enc_cell); } -/* Set the authentication key in the INTRODUCE1 cell from the given data. */ +/** Set the authentication key in the INTRODUCE1 cell from the given data. */ static void introduce1_set_auth_key(trn_cell_introduce1_t *cell, const hs_cell_introduce1_data_t *data) @@ -451,7 +451,7 @@ introduce1_set_auth_key(trn_cell_introduce1_t *cell, data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell)); } -/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */ +/** Set the legacy ID field in the INTRODUCE1 cell from the given data. */ static void introduce1_set_legacy_id(trn_cell_introduce1_t *cell, const hs_cell_introduce1_data_t *data) @@ -473,26 +473,150 @@ introduce1_set_legacy_id(trn_cell_introduce1_t *cell, } } +/** Build and add to the given DoS cell extension the given parameter type and + * value. */ +static void +build_establish_intro_dos_param(trn_cell_extension_dos_t *dos_ext, + uint8_t param_type, uint64_t param_value) +{ + trn_cell_extension_dos_param_t *dos_param = + trn_cell_extension_dos_param_new(); + + /* Extra safety. We should never send an unknown parameter type. */ + tor_assert(param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC || + param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC); + + trn_cell_extension_dos_param_set_type(dos_param, param_type); + trn_cell_extension_dos_param_set_value(dos_param, param_value); + trn_cell_extension_dos_add_params(dos_ext, dos_param); + + /* Not freeing the trunnel object because it is now owned by dos_ext. */ +} + +/** Build the DoS defense cell extension and put it in the given extensions + * object. Return 0 on success, -1 on failure. (Right now, failure is only + * possible if there is a bug.) */ +static int +build_establish_intro_dos_extension(const hs_service_config_t *service_config, + trn_cell_extension_t *extensions) +{ + ssize_t ret; + size_t dos_ext_encoded_len; + uint8_t *field_array; + trn_cell_extension_field_t *field = NULL; + trn_cell_extension_dos_t *dos_ext = NULL; + + tor_assert(service_config); + tor_assert(extensions); + + /* We are creating a cell extension field of the type DoS. */ + field = trn_cell_extension_field_new(); + trn_cell_extension_field_set_field_type(field, + TRUNNEL_CELL_EXTENSION_TYPE_DOS); + + /* Build DoS extension field. We will put in two parameters. */ + dos_ext = trn_cell_extension_dos_new(); + trn_cell_extension_dos_set_n_params(dos_ext, 2); + + /* Build DoS parameter INTRO2 rate per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC, + service_config->intro_dos_rate_per_sec); + /* Build DoS parameter INTRO2 burst per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC, + service_config->intro_dos_burst_per_sec); + + /* Set the field with the encoded DoS extension. */ + ret = trn_cell_extension_dos_encoded_len(dos_ext); + if (BUG(ret <= 0)) { + goto err; + } + dos_ext_encoded_len = ret; + /* Set length field and the field array size length. */ + trn_cell_extension_field_set_field_len(field, dos_ext_encoded_len); + trn_cell_extension_field_setlen_field(field, dos_ext_encoded_len); + /* Encode the DoS extension into the cell extension field. */ + field_array = trn_cell_extension_field_getarray_field(field); + ret = trn_cell_extension_dos_encode(field_array, + trn_cell_extension_field_getlen_field(field), dos_ext); + if (BUG(ret <= 0)) { + goto err; + } + tor_assert(ret == (ssize_t) dos_ext_encoded_len); + + /* Finally, encode field into the cell extension. */ + trn_cell_extension_add_fields(extensions, field); + + /* We've just add an extension field to the cell extensions so increment the + * total number. */ + trn_cell_extension_set_num(extensions, + trn_cell_extension_get_num(extensions) + 1); + + /* Cleanup. DoS extension has been encoded at this point. */ + trn_cell_extension_dos_free(dos_ext); + + return 0; + + err: + trn_cell_extension_field_free(field); + trn_cell_extension_dos_free(dos_ext); + return -1; +} + /* ========== */ /* Public API */ /* ========== */ -/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point +/** Allocate and build all the ESTABLISH_INTRO cell extension. The given + * extensions pointer is always set to a valid cell extension object. */ +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip) +{ + int ret; + trn_cell_extension_t *extensions; + + tor_assert(service_config); + tor_assert(ip); + + extensions = trn_cell_extension_new(); + trn_cell_extension_set_num(extensions, 0); + + /* If the defense has been enabled service side (by the operator with a + * torrc option) and the intro point does support it. */ + if (service_config->has_dos_defense_enabled && + ip->support_intro2_dos_defense) { + /* This function takes care to increment the number of extensions. */ + ret = build_establish_intro_dos_extension(service_config, extensions); + if (ret < 0) { + /* Return no extensions on error. */ + goto end; + } + } + + end: + return extensions; +} + +/** Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point * object. The encoded cell is put in cell_out that MUST at least be of the * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else * a negative value and cell_out is untouched. This function also supports * legacy cell creation. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *service_config, const hs_service_intro_point_t *ip, uint8_t *cell_out) { ssize_t cell_len = -1; uint16_t sig_len = ED25519_SIG_LEN; - trn_cell_extension_t *ext; trn_cell_establish_intro_t *cell = NULL; + trn_cell_extension_t *extensions; tor_assert(circ_nonce); + tor_assert(service_config); tor_assert(ip); /* Quickly handle the legacy IP. */ @@ -505,11 +629,12 @@ hs_cell_build_establish_intro(const char *circ_nonce, goto done; } + /* Build the extensions, if any. */ + extensions = build_establish_intro_extensions(service_config, ip); + /* Set extension data. None used here. */ - ext = trn_cell_extension_new(); - trn_cell_extension_set_num(ext, 0); cell = trn_cell_establish_intro_new(); - trn_cell_establish_intro_set_extensions(cell, ext); + trn_cell_establish_intro_set_extensions(cell, extensions); /* Set signature size. Array is then allocated in the cell. We need to do * this early so we can use trunnel API to get the signature length. */ trn_cell_establish_intro_set_sig_len(cell, sig_len); @@ -600,7 +725,7 @@ hs_cell_build_establish_intro(const char *circ_nonce, return cell_len; } -/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we +/** Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we * are successful at parsing it, return the length of the parsed cell else a * negative value on error. */ ssize_t @@ -622,7 +747,7 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) return ret; } -/* Parse the INTRODUCE2 cell using data which contains everything we need to +/** Parse the INTRODUCE2 cell using data which contains everything we need to * do so and contains the destination buffers of information we extract and * compute from the cell. Return 0 on success else a negative value. The * service and circ are only used for logging purposes. */ @@ -758,7 +883,14 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { link_specifier_t *lspec = trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); - smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + if (BUG(!lspec)) { + goto done; + } + link_specifier_t *lspec_dup = link_specifier_dup(lspec); + if (BUG(!lspec_dup)) { + goto done; + } + smartlist_add(data->link_specifiers, lspec_dup); } /* Success. */ @@ -776,7 +908,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, return ret; } -/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake +/** Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake * info. The encoded cell is put in cell_out and the length of the data is * returned. This can't fail. */ ssize_t @@ -810,7 +942,7 @@ hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, return cell_len; } -/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in +/** Build an INTRODUCE1 cell from the given data. The encoded cell is put in * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the * encoded length is returned else a negative value and the content of * cell_out should be ignored. */ @@ -851,7 +983,7 @@ hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, return cell_len; } -/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The +/** Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The * encoded cell is put in cell_out which must be of at least * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the * caller should clear up the content of the cell. @@ -868,7 +1000,7 @@ hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, return HS_REND_COOKIE_LEN; } -/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. +/** Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. * Return the status code on success else a negative value if the cell as not * decodable. */ int @@ -903,7 +1035,7 @@ hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) return ret; } -/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On +/** Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On * success, handshake_info contains the data in the HANDSHAKE_INFO field, and * 0 is returned. On error, a negative value is returned. */ int @@ -935,7 +1067,7 @@ hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, return ret; } -/* Clear the given INTRODUCE1 data structure data. */ +/** Clear the given INTRODUCE1 data structure data. */ void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) { @@ -949,4 +1081,3 @@ hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) /* The data object has no ownership of any members. */ memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); } - diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h index 9569de535e..80f37057d2 100644 --- a/src/feature/hs/hs_cell.h +++ b/src/feature/hs/hs_cell.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,35 +12,35 @@ #include "core/or/or.h" #include "feature/hs/hs_service.h" -/* An INTRODUCE1 cell requires at least this amount of bytes (see section +/** An INTRODUCE1 cell requires at least this amount of bytes (see section * 3.2.2 of the specification). Below this value, the cell must be padded. */ #define HS_CELL_INTRODUCE1_MIN_SIZE 246 -/* This data structure contains data that we need to build an INTRODUCE1 cell +/** This data structure contains data that we need to build an INTRODUCE1 cell * used by the INTRODUCE1 build function. */ typedef struct hs_cell_introduce1_data_t { - /* Is this a legacy introduction point? */ + /** Is this a legacy introduction point? */ unsigned int is_legacy : 1; - /* (Legacy only) The encryption key for a legacy intro point. Only set if + /** (Legacy only) The encryption key for a legacy intro point. Only set if * is_legacy is true. */ const crypto_pk_t *legacy_key; - /* Introduction point authentication public key. */ + /** Introduction point authentication public key. */ const ed25519_public_key_t *auth_pk; - /* Introduction point encryption public key. */ + /** Introduction point encryption public key. */ const curve25519_public_key_t *enc_pk; - /* Subcredentials of the service. */ + /** Subcredentials of the service. */ const uint8_t *subcredential; - /* Onion public key for the ntor handshake. */ + /** Onion public key for the ntor handshake. */ const curve25519_public_key_t *onion_pk; - /* Rendezvous cookie. */ + /** Rendezvous cookie. */ const uint8_t *rendezvous_cookie; - /* Public key put before the encrypted data (CLIENT_PK). */ + /** Public key put before the encrypted data (CLIENT_PK). */ const curve25519_keypair_t *client_kp; - /* Rendezvous point link specifiers. */ + /** Rendezvous point link specifiers. */ smartlist_t *link_specifiers; } hs_cell_introduce1_data_t; -/* This data structure contains data that we need to parse an INTRODUCE2 cell +/** This data structure contains data that we need to parse an INTRODUCE2 cell * which is used by the INTRODUCE2 cell parsing function. On a successful * parsing, the onion_pk and rendezvous_cookie will be populated with the * computed key material from the cell data. This structure is only used during @@ -48,37 +48,38 @@ typedef struct hs_cell_introduce1_data_t { typedef struct hs_cell_introduce2_data_t { /*** Immutable Section: Set on structure init. ***/ - /* Introduction point authentication public key. Pointer owned by the + /** Introduction point authentication public key. Pointer owned by the introduction point object through which we received the INTRO2 cell. */ const ed25519_public_key_t *auth_pk; - /* Introduction point encryption keypair for the ntor handshake. Pointer + /** Introduction point encryption keypair for the ntor handshake. Pointer owned by the introduction point object through which we received the INTRO2 cell*/ const curve25519_keypair_t *enc_kp; - /* Subcredentials of the service. Pointer owned by the descriptor that owns + /** Subcredentials of the service. Pointer owned by the descriptor that owns the introduction point through which we received the INTRO2 cell. */ const uint8_t *subcredential; - /* Payload of the received encoded cell. */ + /** Payload of the received encoded cell. */ const uint8_t *payload; - /* Size of the payload of the received encoded cell. */ + /** Size of the payload of the received encoded cell. */ size_t payload_len; /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ - /* Onion public key computed using the INTRODUCE2 encrypted section. */ + /** Onion public key computed using the INTRODUCE2 encrypted section. */ curve25519_public_key_t onion_pk; - /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + /** Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ uint8_t rendezvous_cookie[REND_COOKIE_LEN]; - /* Client public key from the INTRODUCE2 encrypted section. */ + /** Client public key from the INTRODUCE2 encrypted section. */ curve25519_public_key_t client_pk; - /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + /** Link specifiers of the rendezvous point. Contains link_specifier_t. */ smartlist_t *link_specifiers; - /* Replay cache of the introduction point. */ + /** Replay cache of the introduction point. */ replaycache_t *replay_cache; } hs_cell_introduce2_data_t; /* Build cell API. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *config, const hs_service_intro_point_t *ip, uint8_t *cell_out); ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, @@ -105,5 +106,14 @@ int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, /* Util API. */ void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); -#endif /* !defined(TOR_HS_CELL_H) */ +#ifdef TOR_UNIT_TESTS + +#include "trunnel/hs/cell_common.h" + +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip); +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* !defined(TOR_HS_CELL_H) */ diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index 8acfcbd65b..90805a98b7 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,15 +15,18 @@ #include "core/or/circuituse.h" #include "core/or/policies.h" #include "core/or/relay.h" +#include "core/or/crypt_path.h" #include "feature/client/circpathbias.h" #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" @@ -39,7 +42,7 @@ #include "feature/nodelist/node_st.h" #include "core/or/origin_circuit_st.h" -/* A circuit is about to become an e2e rendezvous circuit. Check +/** A circuit is about to become an e2e rendezvous circuit. Check * <b>circ_purpose</b> and ensure that it's properly set. Return true iff * circuit purpose is properly set, otherwise return false. */ static int @@ -66,7 +69,7 @@ circuit_purpose_is_correct_for_rend(unsigned int circ_purpose, return 1; } -/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous +/** Create and return a crypt path for the final hop of a v3 prop224 rendezvous * circuit. Initialize the crypt path crypto using the output material from the * ntor key exchange at <b>ntor_key_seed</b>. * @@ -89,7 +92,7 @@ create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, cpath = tor_malloc_zero(sizeof(crypt_path_t)); cpath->magic = CRYPT_PATH_MAGIC; - if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys), + if (cpath_init_circuit_crypto(cpath, (char*)keys, sizeof(keys), is_service_side, 1) < 0) { tor_free(cpath); goto err; @@ -100,7 +103,7 @@ create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, return cpath; } -/* We are a v2 legacy HS client: Create and return a crypt path for the hidden +/** We are a v2 legacy HS client: Create and return a crypt path for the hidden * service on the other side of the rendezvous circuit <b>circ</b>. Initialize * the crypt path crypto using the body of the RENDEZVOUS1 cell at * <b>rend_cell_body</b> (which must be at least DH1024_KEY_LEN+DIGEST_LEN @@ -126,7 +129,7 @@ create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) goto err; } /* ... and set up cpath. */ - if (circuit_init_cpath_crypto(hop, + if (cpath_init_circuit_crypto(hop, keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, 0, 0) < 0) goto err; @@ -151,7 +154,7 @@ create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) return hop; } -/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark +/** Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark * <b>circ</b> ready for use to transfer HS relay cells. */ static void finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, @@ -177,7 +180,7 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, circ->hs_circ_has_timed_out = 0; /* Append the hop to the cpath of this circuit */ - onion_append_to_cpath(&circ->cpath, hop); + cpath_extend_linked_list(&circ->cpath, hop); /* In legacy code, 'pending_final_cpath' points to the final hop we just * appended to the cpath. We set the original pointer to NULL so that we @@ -192,7 +195,7 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, } } -/* For a given circuit and a service introduction point object, register the +/** For a given circuit and a service introduction point object, register the * intro circuit to the circuitmap. This supports legacy intro point. */ static void register_intro_circ(const hs_service_intro_point_t *ip, @@ -210,7 +213,7 @@ register_intro_circ(const hs_service_intro_point_t *ip, } } -/* Return the number of opened introduction circuit for the given circuit that +/** Return the number of opened introduction circuit for the given circuit that * is matching its identity key. */ static unsigned int count_opened_desc_intro_point_circuits(const hs_service_t *service, @@ -242,7 +245,7 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, return count; } -/* From a given service, rendezvous cookie and handshake info, create a +/** From a given service, rendezvous cookie and handshake info, create a * rendezvous point circuit identifier. This can't fail. */ STATIC hs_ident_circuit_t * create_rp_circuit_identifier(const hs_service_t *service, @@ -258,8 +261,7 @@ create_rp_circuit_identifier(const hs_service_t *service, tor_assert(server_pk); tor_assert(keys); - ident = hs_ident_circuit_new(&service->keys.identity_pk, - HS_IDENT_CIRCUIT_RENDEZVOUS); + ident = hs_ident_circuit_new(&service->keys.identity_pk); /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ memcpy(ident->rendezvous_cookie, rendezvous_cookie, sizeof(ident->rendezvous_cookie)); @@ -282,7 +284,7 @@ create_rp_circuit_identifier(const hs_service_t *service, return ident; } -/* From a given service and service intro point, create an introduction point +/** From a given service and service intro point, create an introduction point * circuit identifier. This can't fail. */ static hs_ident_circuit_t * create_intro_circuit_identifier(const hs_service_t *service, @@ -293,14 +295,13 @@ create_intro_circuit_identifier(const hs_service_t *service, tor_assert(service); tor_assert(ip); - ident = hs_ident_circuit_new(&service->keys.identity_pk, - HS_IDENT_CIRCUIT_INTRO); + ident = hs_ident_circuit_new(&service->keys.identity_pk); ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); return ident; } -/* For a given introduction point and an introduction circuit, send the +/** For a given introduction point and an introduction circuit, send the * ESTABLISH_INTRO cell. The service object is used for logging. This can fail * and if so, the circuit is closed and the intro point object is flagged * that the circuit is not established anymore which is important for the @@ -318,7 +319,7 @@ send_establish_intro(const hs_service_t *service, /* Encode establish intro cell. */ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, - ip, payload); + &service->config, ip, payload); if (cell_len < 0) { log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " "on circuit %u. Closing circuit.", @@ -350,7 +351,7 @@ send_establish_intro(const hs_service_t *service, memwipe(payload, 0, sizeof(payload)); } -/* Return a string constant describing the anonymity of service. */ +/** Return a string constant describing the anonymity of service. */ static const char * get_service_anonymity_string(const hs_service_t *service) { @@ -361,7 +362,7 @@ get_service_anonymity_string(const hs_service_t *service) } } -/* For a given service, the ntor onion key and a rendezvous cookie, launch a +/** For a given service, the ntor onion key and a rendezvous cookie, launch a * circuit to the rendezvous point specified by the link specifiers. On * success, a circuit identifier is attached to the circuit with the needed * data. This function will try to open a circuit for a maximum value of @@ -388,10 +389,7 @@ launch_rendezvous_point_circuit(const hs_service_t *service, &data->onion_pk, service->config.is_single_onion); if (info == NULL) { - /* We are done here, we can't extend to the rendezvous point. - * If you're running an IPv6-only v3 single onion service on 0.3.2 or with - * 0.3.2 clients, and somehow disable the option check, it will fail here. - */ + /* We are done here, we can't extend to the rendezvous point. */ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Not enough info to open a circuit to a rendezvous point for " "%s service %s.", @@ -473,7 +471,7 @@ launch_rendezvous_point_circuit(const hs_service_t *service, extend_info_free(info); } -/* Return true iff the given service rendezvous circuit circ is allowed for a +/** Return true iff the given service rendezvous circuit circ is allowed for a * relaunch to the rendezvous point. */ static int can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) @@ -520,7 +518,7 @@ can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) return 0; } -/* Retry the rendezvous point of circ by launching a new circuit to it. */ +/** Retry the rendezvous point of circ by launching a new circuit to it. */ static void retry_service_rendezvous_point(const origin_circuit_t *circ) { @@ -569,82 +567,7 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) return; } -/* Add all possible link specifiers in node to lspecs: - * - legacy ID is mandatory thus MUST be present in node; - * - include ed25519 link specifier if present in the node, and the node - * supports ed25519 link authentication, even if its link versions are not - * compatible with us; - * - include IPv4 link specifier, if the primary address is not IPv4, log a - * BUG() warning, and return an empty smartlist; - * - include IPv6 link specifier if present in the node. */ -static void -get_lspecs_from_node(const node_t *node, smartlist_t *lspecs) -{ - link_specifier_t *ls; - tor_addr_port_t ap; - - tor_assert(node); - tor_assert(lspecs); - - /* Get the relay's IPv4 address. */ - node_get_prim_orport(node, &ap); - - /* We expect the node's primary address to be a valid IPv4 address. - * This conforms to the protocol, which requires either an IPv4 or IPv6 - * address (or both). */ - if (BUG(!tor_addr_is_v4(&ap.addr)) || - BUG(!tor_addr_port_is_valid_ap(&ap, 0))) { - return; - } - - ls = link_specifier_new(); - link_specifier_set_ls_type(ls, LS_IPV4); - link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ap.addr)); - link_specifier_set_un_ipv4_port(ls, ap.port); - /* Four bytes IPv4 and two bytes port. */ - link_specifier_set_ls_len(ls, sizeof(ap.addr.addr.in_addr) + - sizeof(ap.port)); - smartlist_add(lspecs, ls); - - /* Legacy ID is mandatory and will always be present in node. */ - ls = link_specifier_new(); - link_specifier_set_ls_type(ls, LS_LEGACY_ID); - memcpy(link_specifier_getarray_un_legacy_id(ls), node->identity, - link_specifier_getlen_un_legacy_id(ls)); - link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); - smartlist_add(lspecs, ls); - - /* ed25519 ID is only included if the node has it, and the node declares a - protocol version that supports ed25519 link authentication, even if that - link version is not compatible with us. (We are sending the ed25519 key - to another tor, which may support different link versions.) */ - if (!ed25519_public_key_is_zero(&node->ed25519_id) && - node_supports_ed25519_link_authentication(node, 0)) { - ls = link_specifier_new(); - link_specifier_set_ls_type(ls, LS_ED25519_ID); - memcpy(link_specifier_getarray_un_ed25519_id(ls), &node->ed25519_id, - link_specifier_getlen_un_ed25519_id(ls)); - link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); - smartlist_add(lspecs, ls); - } - - /* Check for IPv6. If so, include it as well. */ - if (node_has_ipv6_orport(node)) { - ls = link_specifier_new(); - node_get_pref_ipv6_orport(node, &ap); - link_specifier_set_ls_type(ls, LS_IPV6); - size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); - const uint8_t *in6_addr = tor_addr_to_in6_addr8(&ap.addr); - uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); - memcpy(ipv6_array, in6_addr, addr_len); - link_specifier_set_un_ipv6_port(ls, ap.port); - /* Sixteen bytes IPv6 and two bytes port. */ - link_specifier_set_ls_len(ls, addr_len + sizeof(ap.port)); - smartlist_add(lspecs, ls); - } -} - -/* Using the given descriptor intro point ip, the node of the +/** Using the given descriptor intro point ip, the node of the * rendezvous point rp_node and the service's subcredential, populate the * already allocated intro1_data object with the needed key material and link * specifiers. @@ -666,10 +589,9 @@ setup_introduce1_data(const hs_desc_intro_point_t *ip, tor_assert(subcredential); tor_assert(intro1_data); - /* Build the link specifiers from the extend information of the rendezvous - * circuit that we've picked previously. */ - rp_lspecs = smartlist_new(); - get_lspecs_from_node(rp_node, rp_lspecs); + /* Build the link specifiers from the node at the end of the rendezvous + * circuit that we opened for this introduction. */ + rp_lspecs = node_get_link_specifier_smartlist(rp_node, 0); if (smartlist_len(rp_lspecs) == 0) { /* We can't rendezvous without link specifiers. */ smartlist_free(rp_lspecs); @@ -698,11 +620,27 @@ 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 */ /* ========== */ -/* Return an introduction point circuit matching the given intro point object. +/** Return an introduction point circuit matching the given intro point object. * NULL is returned is no such circuit can be found. */ origin_circuit_t * hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) @@ -717,7 +655,29 @@ hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) } } -/* Called when we fail building a rendezvous circuit at some point other than +/** Return an introduction point established circuit matching the given intro + * point object. The circuit purpose has to be CIRCUIT_PURPOSE_S_INTRO. NULL + * is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_established_intro_circ(const hs_service_intro_point_t *ip) +{ + origin_circuit_t *circ; + + tor_assert(ip); + + if (ip->base.is_only_legacy) { + circ = hs_circuitmap_get_intro_circ_v2_service_side(ip->legacy_key_digest); + } else { + circ = hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + } + + /* Only return circuit if it is established. */ + 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 * the last hop: launches a new circuit to the same rendezvous point. This * supports legacy service. * @@ -757,7 +717,7 @@ hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) return; } -/* For a given service and a service intro point, launch a circuit to the +/** For a given service and a service intro point, launch a circuit to the * extend info ei. If the service is a single onion, and direct_conn is true, * a one-hop circuit will be requested. * @@ -818,7 +778,7 @@ hs_circ_launch_intro_point(hs_service_t *service, return ret; } -/* Called when a service introduction point circuit is done building. Given +/** Called when a service introduction point circuit is done building. Given * the service and intro point object, this function will send the * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the * circuit has been repurposed to General because we already have too many @@ -887,7 +847,7 @@ hs_circ_service_intro_has_opened(hs_service_t *service, return ret; } -/* Called when a service rendezvous point circuit is done building. Given the +/** Called when a service rendezvous point circuit is done building. Given the * service and the circuit, this function will send a RENDEZVOUS1 cell on the * circuit using the information in the circuit identifier. If the cell can't * be sent, the circuit is closed. */ @@ -953,7 +913,7 @@ hs_circ_service_rp_has_opened(const hs_service_t *service, memwipe(payload, 0, sizeof(payload)); } -/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle +/** Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the * given introduction circuit circ. The service is only used for logging * purposes. Return 0 on success else a negative value. */ @@ -998,7 +958,7 @@ hs_circ_handle_intro_established(const hs_service_t *service, return ret; } -/* We just received an INTRODUCE2 cell on the established introduction circuit +/** We just received an INTRODUCE2 cell on the established introduction circuit * circ. Handle the INTRODUCE2 payload of size payload_len for the given * circuit and service. This cell is associated with the intro point object ip * and the subcredential. Return 0 on success else a negative value. */ @@ -1060,14 +1020,12 @@ hs_circ_handle_introduce2(const hs_service_t *service, ret = 0; done: - SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, - link_specifier_free(lspec)); - smartlist_free(data.link_specifiers); + link_specifier_smartlist_free(data.link_specifiers); memwipe(&data, 0, sizeof(data)); return ret; } -/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key +/** Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to * serve as a rendezvous end-to-end circuit between the client and the * service. If <b>is_service_side</b> is set, then we are the hidden service @@ -1097,7 +1055,7 @@ hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, return 0; } -/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell +/** We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then * extend the crypt path of <b>circ</b> so that the hidden service is on the * other side. */ @@ -1122,7 +1080,7 @@ hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, return 0; } -/* Given the introduction circuit intro_circ, the rendezvous circuit +/** Given the introduction circuit intro_circ, the rendezvous circuit * rend_circ, a descriptor intro point object ip and the service's * subcredential, send an INTRODUCE1 cell on intro_circ. * @@ -1207,7 +1165,7 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, return ret; } -/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On +/** Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On * success, 0 is returned else -1 and the circuit is marked for close. */ int hs_circ_send_establish_rendezvous(origin_circuit_t *circ) @@ -1258,30 +1216,132 @@ 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); } - /* 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. */ + /* 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. */ + if (circ->hs_token) { hs_circuitmap_remove_circuit(circ); } } + +/** Return true iff the given established client rendezvous circuit was sent + * into the INTRODUCE1 cell. This is called so we can take a decision on + * expiring or not the circuit. + * + * The caller MUST make sure the circuit is an established client rendezvous + * circuit (purpose: CIRCUIT_PURPOSE_C_REND_READY). + * + * This function supports all onion service versions. */ +bool +hs_circ_is_rend_sent_in_intro1(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This can only be called for a rendezvous circuit that is an established + * confirmed rendezsvous circuit but without an introduction ACK. */ + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_REND_READY); + + /* The v2 and v3 circuit are handled differently: + * + * v2: A circ's pending_final_cpath field is non-NULL iff it is a rend circ + * and we have tried to send an INTRODUCE1 cell specifying it. Thus, if the + * pending_final_cpath field *is* NULL, then we want to not spare it. + * + * v3: When the INTRODUCE1 cell is sent, the introduction encryption public + * key is copied in the rendezvous circuit hs identifier. If it is a valid + * key, we know that this circuit is waiting the ACK on the introduction + * circuit. We want to _not_ spare the circuit if the key was never set. */ + + if (circ->rend_data) { + /* v2. */ + if (circ->build_state && circ->build_state->pending_final_cpath != NULL) { + return true; + } + } else if (circ->hs_ident) { + /* v3. */ + if (curve25519_public_key_is_ok(&circ->hs_ident->intro_enc_pk)) { + return true; + } + } else { + /* A circuit with an HS purpose without an hs_ident or rend_data in theory + * can not happen. In case, scream loudly and return false to the caller + * that the rendezvous was not sent in the INTRO1 cell. */ + tor_assert_nonfatal_unreached(); + } + + /* The rendezvous has not been specified in the INTRODUCE1 cell. */ + return false; +} diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h index e168b301f1..92231369c6 100644 --- a/src/feature/hs/hs_circuit.h +++ b/src/feature/hs/hs_circuit.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -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, @@ -35,6 +37,8 @@ void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); origin_circuit_t *hs_circ_service_get_intro_circ( const hs_service_intro_point_t *ip); +origin_circuit_t *hs_circ_service_get_established_intro_circ( + const hs_service_intro_point_t *ip); /* Cell API. */ int hs_circ_handle_intro_established(const hs_service_t *service, @@ -62,15 +66,18 @@ int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, const uint8_t *rend_cell_body); +bool hs_circ_is_rend_sent_in_intro1(const origin_circuit_t *circ); + #ifdef HS_CIRCUIT_PRIVATE +struct hs_ntor_rend_cell_keys_t; + STATIC hs_ident_circuit_t * create_rp_circuit_identifier(const hs_service_t *service, const uint8_t *rendezvous_cookie, const curve25519_public_key_t *server_pk, - const hs_ntor_rend_cell_keys_t *keys); + const struct hs_ntor_rend_cell_keys_t *keys); #endif /* defined(HS_CIRCUIT_PRIVATE) */ #endif /* !defined(TOR_HS_CIRCUIT_H) */ - diff --git a/src/feature/hs/hs_circuitmap.c b/src/feature/hs/hs_circuitmap.c index 5480d5eb84..2343d729dd 100644 --- a/src/feature/hs/hs_circuitmap.c +++ b/src/feature/hs/hs_circuitmap.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -23,13 +23,13 @@ /************************** HS circuitmap code *******************************/ -/* This is the hidden service circuitmap. It's a hash table that maps +/** This is the hidden service circuitmap. It's a hash table that maps introduction and rendezvous tokens to specific circuits such that given a token it's easy to find the corresponding circuit. */ static struct hs_circuitmap_ht *the_hs_circuitmap = NULL; -/* This is a helper function used by the hash table code (HT_). It returns 1 if - * two circuits have the same HS token. */ +/** This is a helper function used by the hash table code (HT_). It returns 1 + * if two circuits have the same HS token. */ static int hs_circuits_have_same_token(const circuit_t *first_circuit, const circuit_t *second_circuit) @@ -60,8 +60,9 @@ hs_circuits_have_same_token(const circuit_t *first_circuit, first_token->token_len); } -/* This is a helper function for the hash table code (HT_). It hashes a circuit - * HS token into an unsigned int for use as a key by the hash table routines.*/ +/** This is a helper function for the hash table code (HT_). It hashes a + * circuit HS token into an unsigned int for use as a key by the hash table + * routines.*/ static inline unsigned int hs_circuit_hash_token(const circuit_t *circuit) { @@ -71,7 +72,7 @@ hs_circuit_hash_token(const circuit_t *circuit) circuit->hs_token->token_len); } -/* Register the circuitmap hash table */ +/** Register the circuitmap hash table */ HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct circuit_t, // The name of the element struct, hs_circuitmap_node, // The name of HT_ENTRY member @@ -83,7 +84,7 @@ HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node, #ifdef TOR_UNIT_TESTS -/* Return the global HS circuitmap. Used by unittests. */ +/** Return the global HS circuitmap. Used by unittests. */ hs_circuitmap_ht * get_hs_circuitmap(void) { @@ -136,7 +137,7 @@ get_circuit_with_token(hs_token_t *search_token) return HT_FIND(hs_circuitmap_ht, the_hs_circuitmap, &search_circ); } -/* Helper function that registers <b>circ</b> with <b>token</b> on the HS +/** Helper function that registers <b>circ</b> with <b>token</b> on the HS circuitmap. This function steals reference of <b>token</b>. */ static void hs_circuitmap_register_impl(circuit_t *circ, hs_token_t *token) @@ -186,7 +187,7 @@ hs_circuitmap_register_circuit(circuit_t *circ, hs_circuitmap_register_impl(circ, hs_token); } -/* Helper function for hs_circuitmap_get_origin_circuit() and +/** Helper function for hs_circuitmap_get_origin_circuit() and * hs_circuitmap_get_or_circuit(). Because only circuit_t are indexed in the * circuitmap, this function returns object type so the specialized functions * using this helper can upcast it to the right type. @@ -220,7 +221,7 @@ hs_circuitmap_get_circuit_impl(hs_token_type_t type, return found_circ; } -/* Helper function: Query circuitmap for origin circuit with <b>token</b> of +/** Helper function: Query circuitmap for origin circuit with <b>token</b> of * size <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose * equal to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked * for close. Return NULL if no such circuit is found. */ @@ -244,7 +245,7 @@ hs_circuitmap_get_origin_circuit(hs_token_type_t type, return TO_ORIGIN_CIRCUIT(circ); } -/* Helper function: Query circuitmap for OR circuit with <b>token</b> of size +/** Helper function: Query circuitmap for OR circuit with <b>token</b> of size * <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose equal * to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked for * close. Return NULL if no such circuit is found. */ @@ -272,7 +273,34 @@ hs_circuitmap_get_or_circuit(hs_token_type_t type, /**** Public relay-side getters: */ -/* Public function: Return a v3 introduction circuit to this relay with +/** Public function: Return v2 and v3 introduction circuit to this relay. + * Always return a newly allocated list for which it is the caller's + * responsability to free it. */ +smartlist_t * +hs_circuitmap_get_all_intro_circ_relay_side(void) +{ + circuit_t **iter; + smartlist_t *circuit_list = smartlist_new(); + + HT_FOREACH(iter, hs_circuitmap_ht, the_hs_circuitmap) { + circuit_t *circ = *iter; + + /* An origin circuit or purpose is wrong or the hs token is not set to be + * a v2 or v3 intro relay side type, we ignore the circuit. Else, we have + * a match so add it to our list. */ + if (CIRCUIT_IS_ORIGIN(circ) || + circ->purpose != CIRCUIT_PURPOSE_INTRO_POINT || + (circ->hs_token->type != HS_TOKEN_INTRO_V3_RELAY_SIDE && + circ->hs_token->type != HS_TOKEN_INTRO_V2_RELAY_SIDE)) { + continue; + } + smartlist_add(circuit_list, circ); + } + + return circuit_list; +} + +/** Public function: Return a v3 introduction circuit to this relay with * <b>auth_key</b>. Return NULL if no such circuit is found in the * circuitmap. */ or_circuit_t * @@ -284,7 +312,7 @@ hs_circuitmap_get_intro_circ_v3_relay_side( CIRCUIT_PURPOSE_INTRO_POINT); } -/* Public function: Return v2 introduction circuit to this relay with +/** Public function: Return v2 introduction circuit to this relay with * <b>digest</b>. Return NULL if no such circuit is found in the circuitmap. */ or_circuit_t * hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest) @@ -294,7 +322,7 @@ hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest) CIRCUIT_PURPOSE_INTRO_POINT); } -/* Public function: Return rendezvous circuit to this relay with rendezvous +/** Public function: Return rendezvous circuit to this relay with rendezvous * <b>cookie</b>. Return NULL if no such circuit is found in the circuitmap. */ or_circuit_t * hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie) @@ -306,7 +334,7 @@ hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie) /** Public relay-side setters: */ -/* Public function: Register rendezvous circuit with key <b>cookie</b> to the +/** Public function: Register rendezvous circuit with key <b>cookie</b> to the * circuitmap. */ void hs_circuitmap_register_rend_circ_relay_side(or_circuit_t *circ, @@ -316,7 +344,7 @@ hs_circuitmap_register_rend_circ_relay_side(or_circuit_t *circ, HS_TOKEN_REND_RELAY_SIDE, REND_TOKEN_LEN, cookie); } -/* Public function: Register v2 intro circuit with key <b>digest</b> to the +/** Public function: Register v2 intro circuit with key <b>digest</b> to the * circuitmap. */ void hs_circuitmap_register_intro_circ_v2_relay_side(or_circuit_t *circ, @@ -327,7 +355,7 @@ hs_circuitmap_register_intro_circ_v2_relay_side(or_circuit_t *circ, REND_TOKEN_LEN, digest); } -/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the +/** Public function: Register v3 intro circuit with key <b>auth_key</b> to the * circuitmap. */ void hs_circuitmap_register_intro_circ_v3_relay_side(or_circuit_t *circ, @@ -340,7 +368,7 @@ hs_circuitmap_register_intro_circ_v3_relay_side(or_circuit_t *circ, /**** Public servide-side getters: */ -/* Public function: Return v3 introduction circuit with <b>auth_key</b> +/** Public function: Return v3 introduction circuit with <b>auth_key</b> * originating from this hidden service. Return NULL if no such circuit is * found in the circuitmap. */ origin_circuit_t * @@ -365,9 +393,9 @@ hs_circuitmap_get_intro_circ_v3_service_side(const return circ; } -/* Public function: Return v2 introduction circuit originating from this hidden - * service with <b>digest</b>. Return NULL if no such circuit is found in the - * circuitmap. */ +/** Public function: Return v2 introduction circuit originating from this + * hidden service with <b>digest</b>. Return NULL if no such circuit is found + * in the circuitmap. */ origin_circuit_t * hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest) { @@ -389,7 +417,7 @@ hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest) return circ; } -/* Public function: Return rendezvous circuit originating from this hidden +/** Public function: Return rendezvous circuit originating from this hidden * service with rendezvous <b>cookie</b>. Return NULL if no such circuit is * found in the circuitmap. */ origin_circuit_t * @@ -412,7 +440,7 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) return circ; } -/* Public function: Return client-side rendezvous circuit with rendezvous +/** Public function: Return client-side rendezvous circuit with rendezvous * <b>cookie</b>. It will look for circuits with the following purposes: * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received @@ -445,7 +473,7 @@ hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) return circ; } -/* Public function: Return client-side established rendezvous circuit with +/** Public function: Return client-side established rendezvous circuit with * rendezvous <b>cookie</b>. It will look for circuits with the following * purposes: * @@ -487,7 +515,7 @@ hs_circuitmap_get_established_rend_circ_client_side(const uint8_t *cookie) /**** Public servide-side setters: */ -/* Public function: Register v2 intro circuit with key <b>digest</b> to the +/** Public function: Register v2 intro circuit with key <b>digest</b> to the * circuitmap. */ void hs_circuitmap_register_intro_circ_v2_service_side(origin_circuit_t *circ, @@ -498,7 +526,7 @@ hs_circuitmap_register_intro_circ_v2_service_side(origin_circuit_t *circ, REND_TOKEN_LEN, digest); } -/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the +/** Public function: Register v3 intro circuit with key <b>auth_key</b> to the * circuitmap. */ void hs_circuitmap_register_intro_circ_v3_service_side(origin_circuit_t *circ, @@ -509,7 +537,7 @@ hs_circuitmap_register_intro_circ_v3_service_side(origin_circuit_t *circ, ED25519_PUBKEY_LEN, auth_key->pubkey); } -/* Public function: Register rendezvous circuit with key <b>cookie</b> to the +/** Public function: Register rendezvous circuit with key <b>cookie</b> to the * circuitmap. */ void hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, @@ -520,7 +548,7 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, REND_TOKEN_LEN, cookie); } -/* Public function: Register rendezvous circuit with key <b>cookie</b> to the +/** Public function: Register rendezvous circuit with key <b>cookie</b> to the * client-side circuitmap. */ void hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, @@ -564,7 +592,7 @@ hs_circuitmap_remove_circuit(circuit_t *circ) circ->hs_token = NULL; } -/* Public function: Initialize the global HS circuitmap. */ +/** Public function: Initialize the global HS circuitmap. */ void hs_circuitmap_init(void) { @@ -574,7 +602,7 @@ hs_circuitmap_init(void) HT_INIT(hs_circuitmap_ht, the_hs_circuitmap); } -/* Public function: Free all memory allocated by the global HS circuitmap. */ +/** Public function: Free all memory allocated by the global HS circuitmap. */ void hs_circuitmap_free_all(void) { diff --git a/src/feature/hs/hs_circuitmap.h b/src/feature/hs/hs_circuitmap.h index c1bbb1ff1c..df3e7a6e7e 100644 --- a/src/feature/hs/hs_circuitmap.h +++ b/src/feature/hs/hs_circuitmap.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,6 +14,7 @@ typedef HT_HEAD(hs_circuitmap_ht, circuit_t) hs_circuitmap_ht; typedef struct hs_token_t hs_token_t; struct or_circuit_t; struct origin_circuit_t; +struct ed25519_public_key_t; /** Public HS circuitmap API: */ @@ -21,7 +22,7 @@ struct origin_circuit_t; struct or_circuit_t * hs_circuitmap_get_intro_circ_v3_relay_side(const - ed25519_public_key_t *auth_key); + struct ed25519_public_key_t *auth_key); struct or_circuit_t * hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest); struct or_circuit_t * @@ -32,13 +33,15 @@ void hs_circuitmap_register_rend_circ_relay_side(struct or_circuit_t *circ, void hs_circuitmap_register_intro_circ_v2_relay_side(struct or_circuit_t *circ, const uint8_t *digest); void hs_circuitmap_register_intro_circ_v3_relay_side(struct or_circuit_t *circ, - const ed25519_public_key_t *auth_key); + const struct ed25519_public_key_t *auth_key); + +smartlist_t *hs_circuitmap_get_all_intro_circ_relay_side(void); /** Public service-side API: */ struct origin_circuit_t * hs_circuitmap_get_intro_circ_v3_service_side(const - ed25519_public_key_t *auth_key); + struct ed25519_public_key_t *auth_key); struct origin_circuit_t * hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest); struct origin_circuit_t * @@ -52,8 +55,8 @@ void hs_circuitmap_register_intro_circ_v2_service_side( struct origin_circuit_t *circ, const uint8_t *digest); void hs_circuitmap_register_intro_circ_v3_service_side( - struct origin_circuit_t *circ, - const ed25519_public_key_t *auth_key); + struct origin_circuit_t *circ, + const struct ed25519_public_key_t *auth_key); void hs_circuitmap_register_rend_circ_service_side( struct origin_circuit_t *circ, const uint8_t *cookie); diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index e25919ecb7..06b7f006ab 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -43,14 +43,15 @@ #include "core/or/entry_connection_st.h" #include "core/or/extend_info_st.h" #include "core/or/origin_circuit_st.h" +#include "core/or/socks_request_st.h" -/* Client-side authorizations for hidden services; map of service identity +/** Client-side authorizations for hidden services; map of service identity * public key to hs_client_service_authorization_t *. */ static digest256map_t *client_auths = NULL; #include "trunnel/hs/cell_introduce1.h" -/* Return a human-readable string for the client fetch status code. */ +/** Return a human-readable string for the client fetch status code. */ static const char * fetch_status_to_string(hs_client_fetch_status_t status) { @@ -74,7 +75,7 @@ fetch_status_to_string(hs_client_fetch_status_t status) } } -/* Return true iff tor should close the SOCKS request(s) for the descriptor +/** Return true iff tor should close the SOCKS request(s) for the descriptor * fetch that ended up with this given status code. */ static int fetch_status_should_close_socks(hs_client_fetch_status_t status) @@ -101,12 +102,51 @@ fetch_status_should_close_socks(hs_client_fetch_status_t status) return 1; } +/* Return a newly allocated list of all the entry connections that matches the + * given service identity pk. If service_identity_pk is NULL, all entry + * connections with an hs_ident are returned. + * + * Caller must free the returned list but does NOT have ownership of the + * object inside thus they have to remain untouched. */ +static smartlist_t * +find_entry_conns(const ed25519_public_key_t *service_identity_pk) +{ + time_t now = time(NULL); + smartlist_t *conns = NULL, *entry_conns = NULL; + + entry_conns = smartlist_new(); + + conns = connection_list_by_type_state(CONN_TYPE_AP, + AP_CONN_STATE_RENDDESC_WAIT); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we just fetched its descriptor. */ + if (!edge_conn->hs_ident || + (service_identity_pk && + !ed25519_pubkey_eq(service_identity_pk, + &edge_conn->hs_ident->identity_pk))) { + continue; + } + assert_connection_ok(base_conn, now); + + /* Validated! Add the entry connection to the list. */ + smartlist_add(entry_conns, entry_conn); + } SMARTLIST_FOREACH_END(base_conn); + + /* We don't have ownership of the objects in this list. */ + smartlist_free(conns); + return entry_conns; +} + /* Cancel all descriptor fetches currently in progress. */ static void cancel_descriptor_fetches(void) { smartlist_t *conns = - connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; if (BUG(ident == NULL)) { @@ -125,7 +165,7 @@ cancel_descriptor_fetches(void) log_info(LD_REND, "Hidden service client descriptor fetches cancelled."); } -/* Get all connections that are waiting on a circuit and flag them back to +/** Get all connections that are waiting on a circuit and flag them back to * waiting for a hidden service descriptor for the given service key * service_identity_pk. */ static void @@ -152,7 +192,7 @@ flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) smartlist_free(conns); } -/* Remove tracked HSDir requests from our history for this hidden service +/** Remove tracked HSDir requests from our history for this hidden service * identity public key. */ static void purge_hid_serv_request(const ed25519_public_key_t *identity_pk) @@ -168,14 +208,12 @@ purge_hid_serv_request(const ed25519_public_key_t *identity_pk) * some point and we don't care about those anymore. */ hs_build_blinded_pubkey(identity_pk, NULL, 0, hs_get_time_period_num(0), &blinded_pk); - if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); /* Purge last hidden service request from cache for this blinded key. */ hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); } -/* Return true iff there is at least one pending directory descriptor request +/** Return true iff there is at least one pending directory descriptor request * for the service identity_pk. */ static int directory_request_is_pending(const ed25519_public_key_t *identity_pk) @@ -203,7 +241,7 @@ directory_request_is_pending(const ed25519_public_key_t *identity_pk) return ret; } -/* Helper function that changes the state of an entry connection to waiting +/** Helper function that changes the state of an entry connection to waiting * for a circuit. For this to work properly, the connection timestamps are set * to now and the connection is then marked as pending for a circuit. */ static void @@ -223,7 +261,7 @@ mark_conn_as_waiting_for_circuit(connection_t *conn, time_t now) connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn)); } -/* We failed to fetch a descriptor for the service with <b>identity_pk</b> +/** We failed to fetch a descriptor for the service with <b>identity_pk</b> * because of <b>status</b>. Find all pending SOCKS connections for this * service that are waiting on the descriptor and close them with * <b>reason</b>. */ @@ -233,26 +271,13 @@ close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk, int reason) { unsigned int count = 0; - time_t now = approx_time(); - smartlist_t *conns = - connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); - - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { - entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); - const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + smartlist_t *entry_conns = find_entry_conns(identity_pk); - /* Only consider the entry connections that matches the service for which - * we tried to get the descriptor */ - if (!edge_conn->hs_ident || - !ed25519_pubkey_eq(identity_pk, - &edge_conn->hs_ident->identity_pk)) { - continue; - } - assert_connection_ok(base_conn, now); + SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) { /* Unattach the entry connection which will close for the reason. */ connection_mark_unattached_ap(entry_conn, reason); count++; - } SMARTLIST_FOREACH_END(base_conn); + } SMARTLIST_FOREACH_END(entry_conn); if (count > 0) { char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; @@ -265,26 +290,26 @@ close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk, } /* No ownership of the object(s) in this list. */ - smartlist_free(conns); + smartlist_free(entry_conns); } -/* Find all pending SOCKS connection waiting for a descriptor and retry them +/** Find all pending SOCKS connection waiting for a descriptor and retry them * all. This is called when the directory information changed. */ STATIC void retry_all_socks_conn_waiting_for_desc(void) { - smartlist_t *conns = - connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + smartlist_t *entry_conns = find_entry_conns(NULL); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) { hs_client_fetch_status_t status; - const edge_connection_t *edge_conn = - ENTRY_TO_EDGE_CONN(TO_ENTRY_CONN(base_conn)); + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + connection_t *base_conn = &edge_conn->base_; /* Ignore non HS or non v3 connection. */ if (edge_conn->hs_ident == NULL) { continue; } + /* In this loop, we will possibly try to fetch a descriptor for the * pending connections because we just got more directory information. * However, the refetch process can cleanup all SOCKS request to the same @@ -318,13 +343,13 @@ retry_all_socks_conn_waiting_for_desc(void) * closed or we are still missing directory information. Leave the * connection in renddesc wait state so when we get more info, we'll be * able to try it again. */ - } SMARTLIST_FOREACH_END(base_conn); + } SMARTLIST_FOREACH_END(entry_conn); /* We don't have ownership of those objects. */ - smartlist_free(conns); + smartlist_free(entry_conns); } -/* A v3 HS circuit successfully connected to the hidden service. Update the +/** A v3 HS circuit successfully connected to the hidden service. Update the * stream state at <b>hs_conn_ident</b> appropriately. */ static void note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) @@ -346,7 +371,7 @@ note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) * will be reset and thus possible to be retried. */ } -/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its +/** Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its * descriptor by launching a dir connection to <b>hsdir</b>. Return a * hs_client_fetch_status_t status code depending on how it went. */ static hs_client_fetch_status_t @@ -357,7 +382,6 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, ed25519_public_key_t blinded_pubkey; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; hs_ident_dir_conn_t hs_conn_dir_ident; - int retval; tor_assert(hsdir); tor_assert(onion_identity_pk); @@ -366,10 +390,7 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); /* ...and base64 it. */ - retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); - if (BUG(retval < 0)) { - return HS_CLIENT_FETCH_ERROR; - } + ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey, @@ -408,7 +429,6 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, STATIC routerstatus_t * pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) { - int retval; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; uint64_t current_time_period = hs_get_time_period_num(0); smartlist_t *responsible_hsdirs = NULL; @@ -421,10 +441,7 @@ pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); /* ...and base64 it. */ - retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); - if (BUG(retval < 0)) { - return NULL; - } + ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); /* Get responsible hsdirs of service for this time period */ responsible_hsdirs = smartlist_new(); @@ -437,7 +454,7 @@ pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) /* Pick an HSDir from the responsible ones. The ownership of * responsible_hsdirs is given to this function so no need to free it. */ - hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey, NULL); return hsdir_rs; } @@ -462,7 +479,25 @@ fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)) return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); } -/* Make sure that the given v3 origin circuit circ is a valid correct +/** With a given <b>onion_identity_pk</b>, fetch its descriptor. If + * <b>hsdirs</b> is specified, use the directory servers specified in the list. + * Else, use a random server. */ +void +hs_client_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, + const smartlist_t *hsdirs) +{ + tor_assert(onion_identity_pk); + + if (hsdirs != NULL) { + SMARTLIST_FOREACH_BEGIN(hsdirs, const routerstatus_t *, hsdir) { + directory_launch_v3_desc_fetch(onion_identity_pk, hsdir); + } SMARTLIST_FOREACH_END(hsdir); + } else { + fetch_v3_desc(onion_identity_pk); + } +} + +/** Make sure that the given v3 origin circuit circ is a valid correct * introduction circuit. This will BUG() on any problems and hard assert if * the anonymity of the circuit is not ok. Return 0 on success else -1 where * the circuit should be mark for closed immediately. */ @@ -491,7 +526,7 @@ intro_circ_is_ok(const origin_circuit_t *circ) return ret; } -/* Find a descriptor intro point object that matches the given ident in the +/** Find a descriptor intro point object that matches the given ident in the * given descriptor desc. Return NULL if not found. */ static const hs_desc_intro_point_t * find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, @@ -514,7 +549,7 @@ find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, return intro_point; } -/* Find a descriptor intro point object from the descriptor object desc that +/** Find a descriptor intro point object from the descriptor object desc that * matches the given legacy identity digest in legacy_id. Return NULL if not * found. */ static hs_desc_intro_point_t * @@ -531,13 +566,15 @@ find_desc_intro_point_by_legacy_id(const char *legacy_id, SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, hs_desc_intro_point_t *, ip) { SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, - const hs_desc_link_specifier_t *, lspec) { + const link_specifier_t *, lspec) { /* Not all tor node have an ed25519 identity key so we still rely on the * legacy identity digest. */ - if (lspec->type != LS_LEGACY_ID) { + if (link_specifier_get_ls_type(lspec) != LS_LEGACY_ID) { continue; } - if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) { + if (fast_memneq(legacy_id, + link_specifier_getconstarray_un_legacy_id(lspec), + DIGEST_LEN)) { break; } /* Found it. */ @@ -550,7 +587,7 @@ find_desc_intro_point_by_legacy_id(const char *legacy_id, return ret_ip; } -/* Send an INTRODUCE1 cell along the intro circuit and populate the rend +/** Send an INTRODUCE1 cell along the intro circuit and populate the rend * circuit identifier with the needed key material for the e2e encryption. * Return 0 on success, -1 if there is a transient error such that an action * has been taken to recover and -2 if there is a permanent error indicating @@ -667,7 +704,7 @@ send_introduce1(origin_circuit_t *intro_circ, return status; } -/* Using the introduction circuit circ, setup the authentication key of the +/** Using the introduction circuit circ, setup the authentication key of the * intro point this circuit has extended to. */ static void setup_intro_circ_auth_key(origin_circuit_t *circ) @@ -699,14 +736,14 @@ setup_intro_circ_auth_key(origin_circuit_t *circ) } /* Reaching this point means we didn't find any intro point for this circuit - * which is not suppose to happen. */ + * which is not supposed to happen. */ tor_assert_nonfatal_unreached(); end: return; } -/* Called when an introduction circuit has opened. */ +/** Called when an introduction circuit has opened. */ static void client_intro_circ_has_opened(origin_circuit_t *circ) { @@ -723,7 +760,7 @@ client_intro_circ_has_opened(origin_circuit_t *circ) connection_ap_attach_pending(1); } -/* Called when a rendezvous circuit has opened. */ +/** Called when a rendezvous circuit has opened. */ static void client_rendezvous_circ_has_opened(origin_circuit_t *circ) { @@ -757,7 +794,7 @@ client_rendezvous_circ_has_opened(origin_circuit_t *circ) } } -/* This is an helper function that convert a descriptor intro point object ip +/** This is an helper function that convert a descriptor intro point object ip * to a newly allocated extend_info_t object fully initialized. Return NULL if * we can't convert it for which chances are that we are missing or malformed * link specifiers. */ @@ -765,28 +802,17 @@ STATIC extend_info_t * desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) { extend_info_t *ei; - smartlist_t *lspecs = smartlist_new(); tor_assert(ip); - /* We first encode the descriptor link specifiers into the binary - * representation which is a trunnel object. */ - SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, - const hs_desc_link_specifier_t *, desc_lspec) { - link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec); - smartlist_add(lspecs, lspec); - } SMARTLIST_FOREACH_END(desc_lspec); - /* Explicitly put the direct connection option to 0 because this is client * side and there is no such thing as a non anonymous client. */ - ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0); + ei = hs_get_extend_info_from_lspecs(ip->link_specifiers, &ip->onion_key, 0); - SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls)); - smartlist_free(lspecs); return ei; } -/* Return true iff the intro point ip for the service service_pk is usable. +/** Return true iff the intro point ip for the service service_pk is usable. * This function checks if the intro point is in the client intro state cache * and checks at the failures. It is considered usable if: * - No error happened (INTRO_POINT_FAILURE_GENERIC) @@ -831,7 +857,7 @@ intro_point_is_usable(const ed25519_public_key_t *service_pk, return 0; } -/* Using a descriptor desc, return a newly allocated extend_info_t object of a +/** Using a descriptor desc, return a newly allocated extend_info_t object of a * randomly picked introduction point from its list. Return NULL if none are * usable. */ STATIC extend_info_t * @@ -936,7 +962,7 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) return ei; } -/* For this introduction circuit, we'll look at if we have any usable +/** For this introduction circuit, we'll look at if we have any usable * introduction point left for this service. If so, we'll use the circuit to * re-extend to a new intro point. Else, we'll close the circuit and its * corresponding rendezvous circuit. Return 0 if we are re-extending else -1 @@ -993,7 +1019,7 @@ close_or_reextend_intro_circ(origin_circuit_t *intro_circ) return ret; } -/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate +/** Called when we get an INTRODUCE_ACK success status code. Do the appropriate * actions for the rendezvous point and finally close intro_circ. */ static void handle_introduce_ack_success(origin_circuit_t *intro_circ) @@ -1039,7 +1065,7 @@ handle_introduce_ack_success(origin_circuit_t *intro_circ) return; } -/* Called when we get an INTRODUCE_ACK failure status code. Depending on our +/** Called when we get an INTRODUCE_ACK failure status code. Depending on our * failure cache status, either close the circuit or re-extend to a new * introduction point. */ static void @@ -1061,7 +1087,7 @@ handle_introduce_ack_bad(origin_circuit_t *circ, int status) INTRO_POINT_FAILURE_GENERIC); } -/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded +/** Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded * cell is in payload of length payload_len. Return 0 on success else a * negative value. The circuit is either close or reuse to re-extend to a new * introduction point. */ @@ -1100,7 +1126,7 @@ handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, return ret; } -/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The +/** Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The * encoded cell is in payload of length payload_len. Return 0 on success or a * negative value on error. On error, the circuit is marked for close. */ STATIC int @@ -1162,7 +1188,7 @@ handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, return ret; } -/* Return true iff the client can fetch a descriptor for this service public +/** Return true iff the client can fetch a descriptor for this service public * identity key and status_out if not NULL is untouched. If the client can * _not_ fetch the descriptor and if status_out is not NULL, it is set with * the fetch status code. */ @@ -1230,7 +1256,27 @@ can_client_refetch_desc(const ed25519_public_key_t *identity_pk, return 0; } -/* Return the client auth in the map using the service identity public key. +/** Purge the client authorization cache of all ephemeral entries that is the + * entries that are not flagged with CLIENT_AUTH_FLAG_IS_PERMANENT. + * + * This is called from the hs_client_purge_state() used by a SIGNEWNYM. */ +STATIC void +purge_ephemeral_client_auth(void) +{ + DIGEST256MAP_FOREACH_MODIFY(client_auths, key, + hs_client_service_authorization_t *, auth) { + /* Cleanup every entry that are _NOT_ permanent that is ephemeral. */ + if (!(auth->flags & CLIENT_AUTH_FLAG_IS_PERMANENT)) { + MAP_DEL_CURRENT(key); + client_service_authorization_free(auth); + } + } DIGESTMAP_FOREACH_END; + + log_info(LD_REND, "Client onion service ephemeral authorization " + "cache has been purged."); +} + +/** Return the client auth in the map using the service identity public key. * Return NULL if it does not exist in the map. */ static hs_client_service_authorization_t * find_client_auth(const ed25519_public_key_t *service_identity_pk) @@ -1243,10 +1289,530 @@ find_client_auth(const ed25519_public_key_t *service_identity_pk) return digest256map_get(client_auths, service_identity_pk->pubkey); } +/** This is called when a descriptor has arrived following a fetch request and + * has been stored in the client cache. The given entry connections, matching + * the service identity key, will get attached to the service circuit. */ +static void +client_desc_has_arrived(const smartlist_t *entry_conns) +{ + time_t now = time(NULL); + + tor_assert(entry_conns); + + SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) { + const hs_descriptor_t *desc; + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + const ed25519_public_key_t *identity_pk = + &edge_conn->hs_ident->identity_pk; + + /* We were just called because we stored the descriptor for this service + * so not finding a descriptor means we have a bigger problem. */ + desc = hs_cache_lookup_as_client(identity_pk); + if (BUG(desc == NULL)) { + goto end; + } + + if (!hs_client_any_intro_points_usable(identity_pk, desc)) { + log_info(LD_REND, "Hidden service descriptor is unusable. " + "Closing streams."); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_RESOLVEFAILED); + /* We are unable to use the descriptor so remove the directory request + * from the cache so the next connection can try again. */ + note_connection_attempt_succeeded(edge_conn->hs_ident); + continue; + } + + log_info(LD_REND, "Descriptor has arrived. Launching circuits."); + + /* Mark connection as waiting for a circuit since we do have a usable + * descriptor now. */ + mark_conn_as_waiting_for_circuit(&edge_conn->base_, now); + } SMARTLIST_FOREACH_END(entry_conn); + + end: + return; +} + +/** This is called when a descriptor fetch was successful but the descriptor + * couldn't be decrypted due to missing or bad client authorization. */ +static void +client_desc_missing_bad_client_auth(const smartlist_t *entry_conns, + hs_desc_decode_status_t status) +{ + tor_assert(entry_conns); + + SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) { + socks5_reply_status_t code; + if (status == HS_DESC_DECODE_BAD_CLIENT_AUTH) { + code = SOCKS5_HS_BAD_CLIENT_AUTH; + } else if (status == HS_DESC_DECODE_NEED_CLIENT_AUTH) { + code = SOCKS5_HS_MISSING_CLIENT_AUTH; + } else { + /* We should not be called with another type of status. Recover by + * sending a generic error. */ + tor_assert_nonfatal_unreached(); + code = SOCKS5_GENERAL_ERROR; + } + entry_conn->socks_request->socks_extended_error_code = code; + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_MISC); + } SMARTLIST_FOREACH_END(entry_conn); +} + +/** Called when we get a 200 directory fetch status code. */ +static void +client_dir_fetch_200(dir_connection_t *dir_conn, + const smartlist_t *entry_conns, const char *body) +{ + hs_desc_decode_status_t decode_status; + + tor_assert(dir_conn); + tor_assert(entry_conns); + tor_assert(body); + + /* We got something: Try storing it in the cache. */ + decode_status = hs_cache_store_as_client(body, + &dir_conn->hs_ident->identity_pk); + switch (decode_status) { + case HS_DESC_DECODE_OK: + case HS_DESC_DECODE_NEED_CLIENT_AUTH: + case HS_DESC_DECODE_BAD_CLIENT_AUTH: + log_info(LD_REND, "Stored hidden service descriptor successfully."); + TO_CONN(dir_conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; + if (decode_status == HS_DESC_DECODE_OK) { + client_desc_has_arrived(entry_conns); + } else { + /* This handles both client auth decode status. */ + client_desc_missing_bad_client_auth(entry_conns, decode_status); + log_info(LD_REND, "Stored hidden service descriptor requires " + "%s client authorization.", + decode_status == HS_DESC_DECODE_NEED_CLIENT_AUTH ? "missing" + : "new"); + } + /* Fire control port RECEIVED event. */ + hs_control_desc_event_received(dir_conn->hs_ident, + dir_conn->identity_digest); + hs_control_desc_event_content(dir_conn->hs_ident, + dir_conn->identity_digest, body); + break; + case HS_DESC_DECODE_ENCRYPTED_ERROR: + case HS_DESC_DECODE_SUPERENC_ERROR: + case HS_DESC_DECODE_PLAINTEXT_ERROR: + case HS_DESC_DECODE_GENERIC_ERROR: + default: + log_info(LD_REND, "Failed to store hidden service descriptor. " + "Descriptor decoding status: %d", decode_status); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(dir_conn->hs_ident, + dir_conn->identity_digest, "BAD_DESC"); + hs_control_desc_event_content(dir_conn->hs_ident, + dir_conn->identity_digest, NULL); + break; + } +} + +/** Called when we get a 404 directory fetch status code. */ +static void +client_dir_fetch_404(dir_connection_t *dir_conn, + const smartlist_t *entry_conns) +{ + tor_assert(entry_conns); + + /* Not there. We'll retry when connection_about_to_close_connection() tries + * to clean this conn up. */ + log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " + "Retrying at another directory."); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest, + "NOT_FOUND"); + hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest, + NULL); + + /* Flag every entry connections that the descriptor was not found. */ + SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) { + entry_conn->socks_request->socks_extended_error_code = + SOCKS5_HS_NOT_FOUND; + } SMARTLIST_FOREACH_END(entry_conn); +} + +/** Called when we get a 400 directory fetch status code. */ +static void +client_dir_fetch_400(dir_connection_t *dir_conn, const char *reason) +{ + tor_assert(dir_conn); + + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "query? Retrying at another directory.", + escaped(reason)); + + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest, + "QUERY_REJECTED"); + hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest, + NULL); +} + +/** Called when we get an unexpected directory fetch status code. */ +static void +client_dir_fetch_unexpected(dir_connection_t *dir_conn, const char *reason, + const int status_code) +{ + tor_assert(dir_conn); + + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status %d (%s) response unexpected from HSDir " + "server '%s:%d'. Retrying at another directory.", + status_code, escaped(reason), TO_CONN(dir_conn)->address, + TO_CONN(dir_conn)->port); + /* Fire control port FAILED event. */ + hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest, + "UNEXPECTED"); + hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest, + 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>. + **/ +hs_client_register_auth_status_t +hs_client_register_auth_credentials(hs_client_service_authorization_t *creds) +{ + ed25519_public_key_t service_identity_pk; + hs_client_service_authorization_t *old_creds = NULL; + hs_client_register_auth_status_t retval = REGISTER_SUCCESS; + + tor_assert(creds); + + if (!client_auths) { + client_auths = digest256map_new(); + } + + if (hs_parse_address(creds->onion_address, &service_identity_pk, + NULL, NULL) < 0) { + client_service_authorization_free(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); + client_service_authorization_free(old_creds); + retval = REGISTER_SUCCESS_ALREADY_EXISTS; + } + + digest256map_set(client_auths, service_identity_pk.pubkey, creds); + + /** Now that we set the new credentials, also try to decrypt any cached + * descriptors. */ + if (hs_cache_client_new_auth_parse(&service_identity_pk)) { + retval = REGISTER_SUCCESS_AND_DECRYPTED; + } + + 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) +{ + ed25519_public_key_t service_identity_pk; + + if (!client_auths) { + return REMOVAL_SUCCESS_NOT_FOUND; + } + + if (hs_parse_address(hsaddress, &service_identity_pk, NULL, NULL) < 0) { + return REMOVAL_BAD_ADDRESS; + } + + 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); + } + + /* Remove associated descriptor if any. */ + hs_cache_remove_as_client(&service_identity_pk); + + client_service_authorization_free(cred); + return REMOVAL_SUCCESS; + } + + return REMOVAL_SUCCESS_NOT_FOUND; +} + +/** Get the HS client auth map. */ +digest256map_t * +get_hs_client_auths_map(void) +{ + return client_auths; +} + /* ========== */ /* 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 @@ -1268,17 +1834,19 @@ hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) } } -/* With the given encoded descriptor in desc_str and the service key in +/** With the given encoded descriptor in desc_str and the service key in * service_identity_pk, decode the descriptor and set the desc pointer with a * newly allocated descriptor object. * - * Return 0 on success else a negative value and desc is set to NULL. */ -int + * On success, HS_DESC_DECODE_OK is returned and desc is set to the decoded + * descriptor. On error, desc is set to NULL and a decoding error status is + * returned depending on what was the issue. */ +hs_desc_decode_status_t hs_client_decode_descriptor(const char *desc_str, const ed25519_public_key_t *service_identity_pk, hs_descriptor_t **desc) { - int ret; + hs_desc_decode_status_t ret; uint8_t subcredential[DIGEST256_LEN]; ed25519_public_key_t blinded_pubkey; hs_client_service_authorization_t *client_auth = NULL; @@ -1306,7 +1874,7 @@ hs_client_decode_descriptor(const char *desc_str, ret = hs_desc_decode_descriptor(desc_str, subcredential, client_auth_sk, desc); memwipe(subcredential, 0, sizeof(subcredential)); - if (ret < 0) { + if (ret != HS_DESC_DECODE_OK) { goto err; } @@ -1319,15 +1887,16 @@ hs_client_decode_descriptor(const char *desc_str, log_warn(LD_GENERAL, "Descriptor signing key certificate signature " "doesn't validate with computed blinded key: %s", tor_cert_describe_signature_status(cert)); + ret = HS_DESC_DECODE_GENERIC_ERROR; goto err; } - return 0; + return HS_DESC_DECODE_OK; err: - return -1; + return ret; } -/* Return true iff there are at least one usable intro point in the service +/** Return true iff there are at least one usable intro point in the service * descriptor desc. */ int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, @@ -1376,7 +1945,7 @@ hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) return status; } -/* This is called when we are trying to attach an AP connection to these +/** This is called when we are trying to attach an AP connection to these * hidden service circuits from connection_ap_handshake_attach_circuit(). * Return 0 on success, -1 for a transient error that is actions were * triggered to recover or -2 for a permenent error where both circuits will @@ -1392,7 +1961,7 @@ hs_client_send_introduce1(origin_circuit_t *intro_circ, rend_circ); } -/* Called when the client circuit circ has been established. It can be either +/** Called when the client circuit circ has been established. It can be either * an introduction or rendezvous circuit. This function handles all hidden * service versions. */ void @@ -1422,7 +1991,7 @@ hs_client_circuit_has_opened(origin_circuit_t *circ) } } -/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of +/** Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a * negative value and the circuit marked for close. */ int @@ -1464,16 +2033,14 @@ hs_client_receive_rendezvous_acked(origin_circuit_t *circ, return -1; } -#define client_service_authorization_free(auth) \ - FREE_AND_NULL(hs_client_service_authorization_t, \ - client_service_authorization_free_, (auth)) - -static void +void client_service_authorization_free_(hs_client_service_authorization_t *auth) { - if (auth) { - memwipe(auth, 0, sizeof(*auth)); + if (!auth) { + return; } + + memwipe(auth, 0, sizeof(*auth)); tor_free(auth); } @@ -1493,7 +2060,7 @@ client_service_authorization_free_all(void) digest256map_free(client_auths, client_service_authorization_free_void); } -/* Check if the auth key file name is valid or not. Return 1 if valid, +/** Check if the auth key file name is valid or not. Return 1 if valid, * otherwise return 0. */ STATIC int auth_key_filename_is_valid(const char *filename) @@ -1515,6 +2082,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) { @@ -1545,7 +2119,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; @@ -1554,11 +2128,24 @@ parse_auth_file_content(const char *client_key_str) auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t)); if (base32_decode((char *) auth->enc_seckey.secret_key, sizeof(auth->enc_seckey.secret_key), - seckey_b32, strlen(seckey_b32)) < 0) { + seckey_b32, strlen(seckey_b32)) != + sizeof(auth->enc_seckey.secret_key)) { + log_warn(LD_REND, "Client authorization encoded base32 private key " + "can't be decoded: %s", seckey_b32); goto err; } + + if (fast_mem_is_zero((const char*)auth->enc_seckey.secret_key, + sizeof(auth->enc_seckey.secret_key))) { + log_warn(LD_REND, "Client authorization private key can't be all-zeroes"); + goto err; + } + 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; @@ -1575,7 +2162,7 @@ parse_auth_file_content(const char *client_key_str) return auth; } -/* From a set of <b>options</b>, setup every client authorization detail +/** From a set of <b>options</b>, setup every client authorization detail * found. Return 0 on success or -1 on failure. If <b>validate_only</b> * is set, parse, warn and return as normal, but don't actually change * the configuration. */ @@ -1585,10 +2172,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); @@ -1599,82 +2183,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); @@ -1690,65 +2246,48 @@ hs_config_client_authorization(const or_options_t *options, return ret; } -/* This is called when a descriptor has arrived following a fetch request and - * has been stored in the client cache. Every entry connection that matches - * the service identity key in the ident will get attached to the hidden - * service circuit. */ +/** Called when a descriptor directory fetch is done. + * + * Act accordingly on all entry connections depending on the HTTP status code + * we got. In case of an error, the SOCKS error is set (if ExtendedErrors is + * set). + * + * The reason is a human readable string returned by the directory server + * which can describe the status of the request. The body is the response + * content, on 200 code it is the descriptor itself. Finally, the status_code + * is the HTTP code returned by the directory server. */ void -hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) +hs_client_dir_fetch_done(dir_connection_t *dir_conn, const char *reason, + const char *body, const int status_code) { - time_t now = time(NULL); - smartlist_t *conns = NULL; - - tor_assert(ident); + smartlist_t *entry_conns; - conns = connection_list_by_type_state(CONN_TYPE_AP, - AP_CONN_STATE_RENDDESC_WAIT); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { - const hs_descriptor_t *desc; - entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); - const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + tor_assert(dir_conn); + tor_assert(body); - /* Only consider the entry connections that matches the service for which - * we just fetched its descriptor. */ - if (!edge_conn->hs_ident || - !ed25519_pubkey_eq(&ident->identity_pk, - &edge_conn->hs_ident->identity_pk)) { - continue; - } - assert_connection_ok(base_conn, now); + /* Get all related entry connections. */ + entry_conns = find_entry_conns(&dir_conn->hs_ident->identity_pk); - /* We were just called because we stored the descriptor for this service - * so not finding a descriptor means we have a bigger problem. */ - desc = hs_cache_lookup_as_client(&ident->identity_pk); - if (BUG(desc == NULL)) { - goto end; - } - - if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) { - log_info(LD_REND, "Hidden service descriptor is unusable. " - "Closing streams."); - connection_mark_unattached_ap(entry_conn, - END_STREAM_REASON_RESOLVEFAILED); - /* We are unable to use the descriptor so remove the directory request - * from the cache so the next connection can try again. */ - note_connection_attempt_succeeded(edge_conn->hs_ident); - continue; - } - - log_info(LD_REND, "Descriptor has arrived. Launching circuits."); - - /* Mark connection as waiting for a circuit since we do have a usable - * descriptor now. */ - mark_conn_as_waiting_for_circuit(base_conn, now); - } SMARTLIST_FOREACH_END(base_conn); + switch (status_code) { + case 200: + client_dir_fetch_200(dir_conn, entry_conns, body); + break; + case 404: + client_dir_fetch_404(dir_conn, entry_conns); + break; + case 400: + client_dir_fetch_400(dir_conn, reason); + break; + default: + client_dir_fetch_unexpected(dir_conn, reason, status_code); + break; + } - end: /* We don't have ownership of the objects in this list. */ - smartlist_free(conns); + smartlist_free(entry_conns); } -/* Return a newly allocated extend_info_t for a randomly chosen introduction +/** Return a newly allocated extend_info_t for a randomly chosen introduction * point for the given edge connection identifier ident. Return NULL if we * can't pick any usable introduction points. */ extend_info_t * @@ -1761,7 +2300,7 @@ hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) rend_client_get_random_intro(edge_conn->rend_data); } -/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ. +/** Called when get an INTRODUCE_ACK cell on the introduction circuit circ. * Return 0 on success else a negative value is returned. The circuit will be * closed or reuse to extend again to another intro point. */ int @@ -1790,7 +2329,7 @@ hs_client_receive_introduce_ack(origin_circuit_t *circ, return ret; } -/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return +/** Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return * 0 on success else a negative value is returned. The circuit will be closed * on error. */ int @@ -1823,7 +2362,7 @@ hs_client_receive_rendezvous2(origin_circuit_t *circ, return ret; } -/* Extend the introduction circuit circ to another valid introduction point +/** Extend the introduction circuit circ to another valid introduction point * for the hidden service it is trying to connect to, or mark it and launch a * new circuit if we can't extend it. Return 0 on success or possible * success. Return -1 and mark the introduction circuit for close on permanent @@ -1873,7 +2412,7 @@ hs_client_reextend_intro_circuit(origin_circuit_t *circ) return ret; } -/* Close all client introduction circuits related to the given descriptor. +/** Close all client introduction circuits related to the given descriptor. * This is called with a descriptor that is about to get replaced in the * client cache. * @@ -1905,7 +2444,7 @@ hs_client_close_intro_circuits_from_desc(const hs_descriptor_t *desc) } } -/* Release all the storage held by the client subsystem. */ +/** Release all the storage held by the client subsystem. */ void hs_client_free_all(void) { @@ -1914,7 +2453,7 @@ hs_client_free_all(void) client_service_authorization_free_all(); } -/* Purge all potentially remotely-detectable state held in the hidden +/** Purge all potentially remotely-detectable state held in the hidden * service client code. Called on SIGNAL NEWNYM. */ void hs_client_purge_state(void) @@ -1931,11 +2470,13 @@ hs_client_purge_state(void) hs_cache_purge_as_client(); /* Purge the last hidden service request cache. */ hs_purge_last_hid_serv_requests(); + /* Purge ephemeral client authorization. */ + purge_ephemeral_client_auth(); log_info(LD_REND, "Hidden service client state has been purged."); } -/* Called when our directory information has changed. */ +/** Called when our directory information has changed. */ void hs_client_dir_info_changed(void) { @@ -1947,10 +2488,10 @@ hs_client_dir_info_changed(void) #ifdef TOR_UNIT_TESTS -STATIC digest256map_t * -get_hs_client_auths_map(void) +STATIC void +set_hs_client_auths_map(digest256map_t *map) { - return client_auths; + client_auths = map; } #endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index dadfa024b8..d0a3a7015f 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,41 +10,94 @@ #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" -/* Status code of a descriptor fetch request. */ +/** Status code of a descriptor fetch request. */ typedef enum { - /* Something internally went wrong. */ + /** Something internally went wrong. */ HS_CLIENT_FETCH_ERROR = -1, - /* The fetch request has been launched successfully. */ + /** The fetch request has been launched successfully. */ HS_CLIENT_FETCH_LAUNCHED = 0, - /* We already have a usable descriptor. No fetch. */ + /** We already have a usable descriptor. No fetch. */ HS_CLIENT_FETCH_HAVE_DESC = 1, - /* No more HSDir available to query. */ + /** No more HSDir available to query. */ HS_CLIENT_FETCH_NO_HSDIRS = 2, - /* The fetch request is not allowed. */ + /** The fetch request is not allowed. */ HS_CLIENT_FETCH_NOT_ALLOWED = 3, - /* We are missing information to be able to launch a request. */ + /** We are missing information to be able to launch a request. */ HS_CLIENT_FETCH_MISSING_INFO = 4, - /* There is a pending fetch for the requested service. */ + /** There is a pending fetch for the requested service. */ HS_CLIENT_FETCH_PENDING = 5, } hs_client_fetch_status_t; -/** Client-side configuration of authorization for a service. */ +/* Status code of client auth credential registration */ +typedef enum { + /* We successfuly registered these credentials */ + REGISTER_SUCCESS, + /* We successfully registered these credentials, but had to replace some + * existing ones. */ + REGISTER_SUCCESS_ALREADY_EXISTS, + /* We successfuly registered these credentials, and also decrypted a cached + * descriptor. */ + REGISTER_SUCCESS_AND_DECRYPTED, + /* We failed to register these credentials, because of a bad HS address. */ + REGISTER_FAIL_BAD_ADDRESS, + /* We failed to store these credentials in a persistent file on disk. */ + REGISTER_FAIL_PERMANENT_STORAGE, +} hs_client_register_auth_status_t; + +/* Status code of client auth credential removal */ +typedef enum { + /* We successfuly removed these credentials */ + REMOVAL_SUCCESS, + /* No need to remove those credentials, because they were not there. */ + REMOVAL_SUCCESS_NOT_FOUND, + /* We failed to register these credentials, because of a bad HS address. */ + REMOVAL_BAD_ADDRESS, +} hs_client_removal_auth_status_t; + +/** Flag to set when a client auth is permanent (saved on disk). */ +#define CLIENT_AUTH_FLAG_IS_PERMANENT (1<<0) + +/** Client-side configuration of client authorization */ typedef struct hs_client_service_authorization_t { - /* An curve25519 secret key used to compute decryption keys that + /** An curve25519 secret key used to compute decryption keys that * allow the client to decrypt the hidden service descriptor. */ curve25519_secret_key_t enc_seckey; - /* An onion address that is used to connect to the onion service. */ + /** An onion address that is used to connect to the onion service. */ char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1]; + + /* Optional flags for this client. */ + int flags; } hs_client_service_authorization_t; +hs_client_register_auth_status_t +hs_client_register_auth_credentials(hs_client_service_authorization_t *creds); + +hs_client_removal_auth_status_t +hs_client_remove_auth_credentials(const char *hsaddress); + +digest256map_t *get_hs_client_auths_map(void); + +#define client_service_authorization_free(auth) \ + FREE_AND_NULL(hs_client_service_authorization_t, \ + client_service_authorization_free_, (auth)) + +void +client_service_authorization_free_(hs_client_service_authorization_t *auth); + void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); -int hs_client_decode_descriptor( +void hs_client_launch_v3_desc_fetch( + const ed25519_public_key_t *onion_identity_pk, + const smartlist_t *hsdirs); + +hs_desc_decode_status_t hs_client_decode_descriptor( const char *desc_str, const ed25519_public_key_t *service_identity_pk, hs_descriptor_t **desc); @@ -57,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, @@ -68,7 +122,8 @@ int hs_client_receive_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); -void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); +void hs_client_dir_fetch_done(dir_connection_t *dir_conn, const char *reason, + const char *body, const int status_code); extend_info_t *hs_client_get_random_intro_from_edge( const edge_connection_t *edge_conn); @@ -107,13 +162,14 @@ MOCK_DECL(STATIC hs_client_fetch_status_t, STATIC void retry_all_socks_conn_waiting_for_desc(void); +STATIC void purge_ephemeral_client_auth(void); + #ifdef TOR_UNIT_TESTS -STATIC digest256map_t *get_hs_client_auths_map(void); +STATIC void set_hs_client_auths_map(digest256map_t *map); #endif /* defined(TOR_UNIT_TESTS) */ #endif /* defined(HS_CLIENT_PRIVATE) */ #endif /* !defined(TOR_HS_CLIENT_H) */ - diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c index de653037d1..d019d96b99 100644 --- a/src/feature/hs/hs_common.c +++ b/src/feature/hs/hs_common.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -21,6 +21,7 @@ #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_client.h" #include "feature/hs/hs_common.h" +#include "feature/hs/hs_dos.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_service.h" #include "feature/hs_common/shared_random_client.h" @@ -31,6 +32,7 @@ #include "feature/nodelist/routerset.h" #include "feature/rend/rendcommon.h" #include "feature/rend/rendservice.h" +#include "feature/relay/routermode.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" @@ -43,7 +45,7 @@ /* Trunnel */ #include "trunnel/ed25519_cert.h" -/* Ed25519 Basepoint value. Taken from section 5 of +/** Ed25519 Basepoint value. Taken from section 5 of * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ static const char *str_ed25519_basepoint = "(15112221349535400772501151409588531511" @@ -85,7 +87,7 @@ set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) return 0; } -#else /* !(defined(HAVE_SYS_UN_H)) */ +#else /* !defined(HAVE_SYS_UN_H) */ static int set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) @@ -105,7 +107,7 @@ add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) #endif /* defined(HAVE_SYS_UN_H) */ -/* Helper function: The key is a digest that we compare to a node_t object +/** Helper function: The key is a digest that we compare to a node_t object * current hsdir_index. */ static int compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) @@ -115,7 +117,7 @@ compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) return tor_memcmp(key, node->hsdir_index.fetch, DIGEST256_LEN); } -/* Helper function: The key is a digest that we compare to a node_t object +/** Helper function: The key is a digest that we compare to a node_t object * next hsdir_index. */ static int compare_digest_to_store_first_hsdir_index(const void *_key, @@ -126,7 +128,7 @@ compare_digest_to_store_first_hsdir_index(const void *_key, return tor_memcmp(key, node->hsdir_index.store_first, DIGEST256_LEN); } -/* Helper function: The key is a digest that we compare to a node_t object +/** Helper function: The key is a digest that we compare to a node_t object * next hsdir_index. */ static int compare_digest_to_store_second_hsdir_index(const void *_key, @@ -137,7 +139,7 @@ compare_digest_to_store_second_hsdir_index(const void *_key, return tor_memcmp(key, node->hsdir_index.store_second, DIGEST256_LEN); } -/* Helper function: Compare two node_t objects current hsdir_index. */ +/** Helper function: Compare two node_t objects current hsdir_index. */ static int compare_node_fetch_hsdir_index(const void **a, const void **b) { @@ -148,7 +150,7 @@ compare_node_fetch_hsdir_index(const void **a, const void **b) DIGEST256_LEN); } -/* Helper function: Compare two node_t objects next hsdir_index. */ +/** Helper function: Compare two node_t objects next hsdir_index. */ static int compare_node_store_first_hsdir_index(const void **a, const void **b) { @@ -159,7 +161,7 @@ compare_node_store_first_hsdir_index(const void **a, const void **b) DIGEST256_LEN); } -/* Helper function: Compare two node_t objects next hsdir_index. */ +/** Helper function: Compare two node_t objects next hsdir_index. */ static int compare_node_store_second_hsdir_index(const void **a, const void **b) { @@ -170,7 +172,7 @@ compare_node_store_second_hsdir_index(const void **a, const void **b) DIGEST256_LEN); } -/* Allocate and return a string containing the path to filename in directory. +/** Allocate and return a string containing the path to filename in directory. * This function will never return NULL. The caller must free this path. */ char * hs_path_from_filename(const char *directory, const char *filename) @@ -184,8 +186,9 @@ hs_path_from_filename(const char *directory, const char *filename) return file_path; } -/* Make sure that the directory for <b>service</b> is private, using the config - * <b>username</b>. +/** Make sure that the directory for <b>service</b> is private, using the + * config <b>username</b>. + * * If <b>create</b> is true: * - if the directory exists, change permissions if needed, * - if the directory does not exist, create it with the correct permissions. @@ -305,18 +308,18 @@ hs_get_next_time_period_num(time_t now) return hs_get_time_period_num(now) + 1; } -/* Get the number of the _previous_ HS time period, given that the current time - * is <b>now</b>. If <b>now</b> is not set, we try to get the time from a live - * consensus. */ +/** Get the number of the _previous_ HS time period, given that the current + * time is <b>now</b>. If <b>now</b> is not set, we try to get the time from a + * live consensus. */ uint64_t hs_get_previous_time_period_num(time_t now) { return hs_get_time_period_num(now) - 1; } -/* Return the start time of the upcoming time period based on <b>now</b>. If - <b>now</b> is not set, we try to get the time ourselves from a live - consensus. */ +/** Return the start time of the upcoming time period based on <b>now</b>. If + * <b>now</b> is not set, we try to get the time ourselves from a live + * consensus. */ time_t hs_get_start_time_of_next_time_period(time_t now) { @@ -331,7 +334,7 @@ hs_get_start_time_of_next_time_period(time_t now) return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset); } -/* Create a new rend_data_t for a specific given <b>version</b>. +/** Create a new rend_data_t for a specific given <b>version</b>. * Return a pointer to the newly allocated data structure. */ static rend_data_t * rend_data_alloc(uint32_t version) @@ -380,7 +383,7 @@ rend_data_free_(rend_data_t *data) } } -/* Allocate and return a deep copy of <b>data</b>. */ +/** Allocate and return a deep copy of <b>data</b>. */ rend_data_t * rend_data_dup(const rend_data_t *data) { @@ -410,7 +413,7 @@ rend_data_dup(const rend_data_t *data) return data_dup; } -/* Compute the descriptor ID for each HS descriptor replica and save them. A +/** Compute the descriptor ID for each HS descriptor replica and save them. A * valid onion address must be present in the <b>rend_data</b>. * * Return 0 on success else -1. */ @@ -448,7 +451,7 @@ compute_desc_id(rend_data_t *rend_data) return ret; } -/* Allocate and initialize a rend_data_t object for a service using the +/** Allocate and initialize a rend_data_t object for a service using the * provided arguments. All arguments are optional (can be NULL), except from * <b>onion_address</b> which MUST be set. The <b>pk_digest</b> is the hash of * the service private key. The <b>cookie</b> is the rendezvous cookie and @@ -480,7 +483,7 @@ rend_data_service_create(const char *onion_address, const char *pk_digest, return rend_data; } -/* Allocate and initialize a rend_data_t object for a client request using the +/** Allocate and initialize a rend_data_t object for a client request using the * given arguments. Either an onion address or a descriptor ID is needed. Both * can be given but in this case only the onion address will be used to make * the descriptor fetch. The <b>cookie</b> is the rendezvous cookie and @@ -521,7 +524,7 @@ rend_data_client_create(const char *onion_address, const char *desc_id, return NULL; } -/* Return the onion address from the rend data. Depending on the version, +/** Return the onion address from the rend data. Depending on the version, * the size of the address can vary but it's always NUL terminated. */ const char * rend_data_get_address(const rend_data_t *rend_data) @@ -537,7 +540,7 @@ rend_data_get_address(const rend_data_t *rend_data) } } -/* Return the descriptor ID for a specific replica number from the rend +/** Return the descriptor ID for a specific replica number from the rend * data. The returned data is a binary digest and depending on the version its * size can vary. The size of the descriptor ID is put in <b>len_out</b> if * non NULL. */ @@ -560,7 +563,7 @@ rend_data_get_desc_id(const rend_data_t *rend_data, uint8_t replica, } } -/* Return the public key digest using the given <b>rend_data</b>. The size of +/** Return the public key digest using the given <b>rend_data</b>. The size of * the digest is put in <b>len_out</b> (if set) which can differ depending on * the version. */ const uint8_t * @@ -583,7 +586,7 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } -/* Using the given time period number, compute the disaster shared random +/** Using the given time period number, compute the disaster shared random * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ static void compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) @@ -671,7 +674,7 @@ get_second_cached_disaster_srv(void) #endif /* defined(TOR_UNIT_TESTS) */ -/* When creating a blinded key, we need a parameter which construction is as +/** When creating a blinded key, we need a parameter which construction is as * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). * * The nonce has a pre-defined format which uses the time period number @@ -725,7 +728,7 @@ build_blinded_key_param(const ed25519_public_key_t *pubkey, memwipe(nonce, 0, sizeof(nonce)); } -/* Using an ed25519 public key and version to build the checksum of an +/** Using an ed25519 public key and version to build the checksum of an * address. Put in checksum_out. Format is: * SHA3-256(".onion checksum" || PUBKEY || VERSION) * @@ -752,7 +755,7 @@ build_hs_checksum(const ed25519_public_key_t *key, uint8_t version, DIGEST_SHA3_256); } -/* Using an ed25519 public key, checksum and version to build the binary +/** Using an ed25519 public key, checksum and version to build the binary * representation of a service address. Put in addr_out. Format is: * addr_out = PUBKEY || CHECKSUM || VERSION * @@ -775,7 +778,7 @@ build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum, tor_assert(offset == HS_SERVICE_ADDR_LEN); } -/* Helper for hs_parse_address(): Using a binary representation of a service +/** Helper for hs_parse_address(): Using a binary representation of a service * address, parse its content into the key_out, checksum_out and version_out. * Any out variable can be NULL in case the caller would want only one field. * checksum_out MUST at least be 2 bytes long. address must be at least @@ -807,7 +810,7 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, tor_assert(offset == HS_SERVICE_ADDR_LEN); } -/* Using the given identity public key and a blinded public key, compute the +/** Using the given identity public key and a blinded public key, compute the * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). * This can't fail. */ void @@ -847,7 +850,7 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, memwipe(credential, 0, sizeof(credential)); } -/* From the given list of hidden service ports, find the ones that match the +/** From the given list of hidden service ports, find the ones that match the * given edge connection conn, pick one at random and use it to set the * connection address. Return 0 on success or -1 if none. */ int @@ -904,7 +907,7 @@ hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) return (chosen_port) ? 0 : -1; } -/* Using a base32 representation of a service address, parse its content into +/** Using a base32 representation of a service address, parse its content into * the key_out, checksum_out and version_out. Any out variable can be NULL in * case the caller would want only one field. checksum_out MUST at least be 2 * bytes long. @@ -929,7 +932,8 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out, } /* Decode address so we can extract needed fields. */ - if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) { + if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) + != sizeof(decoded)) { log_warn(LD_REND, "Service address %s can't be decoded.", escaped_safe_str(address)); goto invalid; @@ -943,7 +947,7 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out, return -1; } -/* Validate a given onion address. The length, the base32 decoding and +/** Validate a given onion address. The length, the base32 decoding, and * checksum are validated. Return 1 if valid else 0. */ int hs_address_is_valid(const char *address) @@ -958,7 +962,7 @@ hs_address_is_valid(const char *address) goto invalid; } - /* Get the checksum it's suppose to be and compare it with what we have + /* Get the checksum it's supposed to be and compare it with what we have * encoded in the address. */ build_hs_checksum(&service_pubkey, version, target_checksum); if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) { @@ -982,11 +986,11 @@ hs_address_is_valid(const char *address) return 0; } -/* Build a service address using an ed25519 public key and a given version. +/** Build a service address using an ed25519 public key and a given version. * The returned address is base32 encoded and put in addr_out. The caller MUST * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long. * - * Format is as follow: + * Format is as follows: * base32(PUBKEY || CHECKSUM || VERSION) * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION) * */ @@ -1012,25 +1016,7 @@ hs_build_address(const ed25519_public_key_t *key, uint8_t version, tor_assert(hs_address_is_valid(addr_out)); } -/* Return a newly allocated copy of lspec. */ -link_specifier_t * -hs_link_specifier_dup(const link_specifier_t *lspec) -{ - link_specifier_t *result = link_specifier_new(); - memcpy(result, lspec, sizeof(*result)); - /* The unrecognized field is a dynamic array so make sure to copy its - * content and not the pointer. */ - link_specifier_setlen_un_unrecognized( - result, link_specifier_getlen_un_unrecognized(lspec)); - if (link_specifier_getlen_un_unrecognized(result)) { - memcpy(link_specifier_getarray_un_unrecognized(result), - link_specifier_getconstarray_un_unrecognized(lspec), - link_specifier_getlen_un_unrecognized(result)); - } - return result; -} - -/* From a given ed25519 public key pk and an optional secret, compute a +/** From a given ed25519 public key pk and an optional secret, compute a * blinded public key and put it in blinded_pk_out. This is only useful to * the client side because the client only has access to the identity public * key of the service. */ @@ -1045,7 +1031,7 @@ hs_build_blinded_pubkey(const ed25519_public_key_t *pk, tor_assert(pk); tor_assert(blinded_pk_out); - tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + tor_assert(!fast_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); build_blinded_key_param(pk, secret, secret_len, time_period_num, get_time_period_length(), param); @@ -1054,7 +1040,7 @@ hs_build_blinded_pubkey(const ed25519_public_key_t *pk, memwipe(param, 0, sizeof(param)); } -/* From a given ed25519 keypair kp and an optional secret, compute a blinded +/** From a given ed25519 keypair kp and an optional secret, compute a blinded * keypair for the current time period and put it in blinded_kp_out. This is * only useful by the service side because the client doesn't have access to * the identity secret key. */ @@ -1070,8 +1056,8 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, tor_assert(kp); tor_assert(blinded_kp_out); /* Extra safety. A zeroed key is bad. */ - tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); - tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + tor_assert(!fast_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!fast_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); build_blinded_key_param(&kp->pubkey, secret, secret_len, time_period_num, get_time_period_length(), param); @@ -1080,7 +1066,7 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, memwipe(param, 0, sizeof(param)); } -/* Return true if we are currently in the time segment between a new time +/** Return true if we are currently in the time segment between a new time * period and a new SRV (in the real network that happens between 12:00 and * 00:00 UTC). Here is a diagram showing exactly when this returns true: * @@ -1121,7 +1107,7 @@ hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) return 1; } -/* Return 1 if any virtual port in ports needs a circuit with good uptime. +/** Return 1 if any virtual port in ports needs a circuit with good uptime. * Else return 0. */ int hs_service_requires_uptime_circ(const smartlist_t *ports) @@ -1137,7 +1123,7 @@ hs_service_requires_uptime_circ(const smartlist_t *ports) return 0; } -/* Build hs_index which is used to find the responsible hsdirs. This index +/** Build hs_index which is used to find the responsible hsdirs. This index * value is used to select the responsible HSDir where their hsdir_index is * closest to this value. * SHA3-256("store-at-idx" | blinded_public_key | @@ -1179,7 +1165,7 @@ hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, crypto_digest_free(digest); } -/* Build hsdir_index which is used to find the responsible hsdirs. This is the +/** Build hsdir_index which is used to find the responsible hsdirs. This is the * index value that is compare to the hs_index when selecting an HSDir. * SHA3-256("node-idx" | node_identity | * shared_random_value | INT_8(period_length) | INT_8(period_num) ) @@ -1220,7 +1206,7 @@ hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, crypto_digest_free(digest); } -/* Return a newly allocated buffer containing the current shared random value +/** Return a newly allocated buffer containing the current shared random value * or if not present, a disaster value is computed using the given time period * number. If a consensus is provided in <b>ns</b>, use it to get the SRV * value. This function can't fail. */ @@ -1239,7 +1225,7 @@ hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) return sr_value; } -/* Return a newly allocated buffer containing the previous shared random +/** Return a newly allocated buffer containing the previous shared random * value or if not present, a disaster value is computed using the given time * period number. This function can't fail. */ uint8_t * @@ -1257,7 +1243,7 @@ hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) return sr_value; } -/* Return the number of replicas defined by a consensus parameter or the +/** Return the number of replicas defined by a consensus parameter or the * default value. */ int32_t hs_get_hsdir_n_replicas(void) @@ -1267,7 +1253,7 @@ hs_get_hsdir_n_replicas(void) HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); } -/* Return the spread fetch value defined by a consensus parameter or the +/** Return the spread fetch value defined by a consensus parameter or the * default value. */ int32_t hs_get_hsdir_spread_fetch(void) @@ -1277,7 +1263,7 @@ hs_get_hsdir_spread_fetch(void) HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); } -/* Return the spread store value defined by a consensus parameter or the +/** Return the spread store value defined by a consensus parameter or the * default value. */ int32_t hs_get_hsdir_spread_store(void) @@ -1304,15 +1290,15 @@ node_has_hsdir_index(const node_t *node) /* At this point, since the node has a desc, this node must also have an * hsdir index. If not, something went wrong, so BUG out. */ - if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.fetch, + if (BUG(fast_mem_is_zero((const char*)node->hsdir_index.fetch, DIGEST256_LEN))) { return 0; } - if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.store_first, + if (BUG(fast_mem_is_zero((const char*)node->hsdir_index.store_first, DIGEST256_LEN))) { return 0; } - if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.store_second, + if (BUG(fast_mem_is_zero((const char*)node->hsdir_index.store_second, DIGEST256_LEN))) { return 0; } @@ -1320,7 +1306,7 @@ node_has_hsdir_index(const node_t *node) return 1; } -/* For a given blinded key and time period number, get the responsible HSDir +/** For a given blinded key and time period number, get the responsible HSDir * and put their routerstatus_t object in the responsible_dirs list. If * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t * is used. If 'for_fetching' is true, the spread fetch consensus parameter is @@ -1612,20 +1598,25 @@ hs_purge_last_hid_serv_requests(void) /** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the * one that we should use to fetch a descriptor right now. Take into account * previous failed attempts at fetching this descriptor from HSDirs using the - * string identifier <b>req_key_str</b>. + * string identifier <b>req_key_str</b>. We return whether we are rate limited + * into *<b>is_rate_limited_out</b> if it is not NULL. * * Steals ownership of <b>responsible_dirs</b>. * * Return the routerstatus of the chosen HSDir if successful, otherwise return * NULL if no HSDirs are worth trying right now. */ routerstatus_t * -hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str, + bool *is_rate_limited_out) { smartlist_t *usable_responsible_dirs = smartlist_new(); const or_options_t *options = get_options(); routerstatus_t *hs_dir; time_t now = time(NULL); int excluded_some; + bool rate_limited = false; + int rate_limited_count = 0; + int responsible_dirs_count = smartlist_len(responsible_dirs); tor_assert(req_key_str); @@ -1645,6 +1636,7 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) if (last + hs_hsdir_requery_period(options) >= now || !node || !node_has_preferred_descriptor(node, 0)) { SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + rate_limited_count++; continue; } if (!routerset_contains_node(options->ExcludeNodes, node)) { @@ -1652,6 +1644,10 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) } } SMARTLIST_FOREACH_END(dir); + if (rate_limited_count > 0 || responsible_dirs_count > 0) { + rate_limited = rate_limited_count == responsible_dirs_count; + } + excluded_some = smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); @@ -1663,9 +1659,10 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) smartlist_free(responsible_dirs); smartlist_free(usable_responsible_dirs); if (!hs_dir) { + const char *warn_str = (rate_limited) ? "we are rate limited." : + "we requested them all recently without success"; log_info(LD_REND, "Could not pick one of the responsible hidden " - "service directories, because we requested them all " - "recently without success."); + "service directories, because %s.", warn_str); if (options->StrictNodes && excluded_some) { log_warn(LD_REND, "Could not pick a hidden service directory for the " "requested hidden service: they are all either down or " @@ -1677,17 +1674,23 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); } + if (is_rate_limited_out != NULL) { + *is_rate_limited_out = rate_limited; + } + return hs_dir; } -/* From a list of link specifier, an onion key and if we are requesting a - * direct connection (ex: single onion service), return a newly allocated - * extend_info_t object. This function always returns an extend info with - * an IPv4 address, or NULL. +/** Given a list of link specifiers lspecs, a curve 25519 onion_key, and + * a direct connection boolean direct_conn (true for single onion services), + * return a newly allocated extend_info_t object. + * + * This function always returns an extend info with a valid IP address and + * ORPort, or NULL. If direct_conn is false, the IP address is always IPv4. * * It performs the following checks: - * if either IPv4 or legacy ID is missing, return NULL. - * if direct_conn, and we can't reach the IPv4 address, return NULL. + * if there is no usable IP address, or legacy ID is missing, return NULL. + * if direct_conn, and we can't reach any IP address, return NULL. */ extend_info_t * hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, @@ -1696,21 +1699,40 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, { int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0; char legacy_id[DIGEST_LEN] = {0}; - uint16_t port_v4 = 0; - tor_addr_t addr_v4; ed25519_public_key_t ed25519_pk; extend_info_t *info = NULL; + tor_addr_port_t ap; + + tor_addr_make_null(&ap.addr, AF_UNSPEC); + ap.port = 0; + + if (lspecs == NULL) { + log_warn(LD_BUG, "Specified link specifiers is null"); + goto done; + } - tor_assert(lspecs); + if (onion_key == NULL) { + log_warn(LD_BUG, "Specified onion key is null"); + goto done; + } + + if (smartlist_len(lspecs) == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Empty link specifier list."); + /* Return NULL. */ + goto done; + } SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) { switch (link_specifier_get_ls_type(ls)) { case LS_IPV4: - /* Skip if we already seen a v4. */ - if (have_v4) continue; - tor_addr_from_ipv4h(&addr_v4, + /* Skip if we already seen a v4. If direct_conn is true, we skip this + * block because fascist_firewall_choose_address_ls() will set ap. If + * direct_conn is false, set ap to the first IPv4 address and port in + * the link specifiers.*/ + if (have_v4 || direct_conn) continue; + tor_addr_from_ipv4h(&ap.addr, link_specifier_get_un_ipv4_addr(ls)); - port_v4 = link_specifier_get_un_ipv4_port(ls); + ap.port = link_specifier_get_un_ipv4_port(ls); have_v4 = 1; break; case LS_LEGACY_ID: @@ -1734,52 +1756,45 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, } } SMARTLIST_FOREACH_END(ls); - /* Legacy ID is mandatory, and we require IPv4. */ - if (!have_v4 || !have_legacy_id) { + /* Choose a preferred address first, but fall back to an allowed address. */ + if (direct_conn) + fascist_firewall_choose_address_ls(lspecs, 0, &ap); + + /* Legacy ID is mandatory, and we require an IP address. */ + if (!tor_addr_port_is_valid_ap(&ap, 0)) { + /* If we're missing the IP address, log a warning and return NULL. */ + log_info(LD_NET, "Unreachable or invalid IP address in link state"); goto done; } - - /* We know we have IPv4, because we just checked. */ - if (!direct_conn) { - /* All clients can extend to any IPv4 via a 3-hop path. */ - goto validate; - } else if (direct_conn && - fascist_firewall_allows_address_addr(&addr_v4, port_v4, - FIREWALL_OR_CONNECTION, - 0, 0)) { - /* Direct connection and we can reach it in IPv4 so go for it. */ - goto validate; - - /* We will add support for falling back to a 3-hop path in a later - * release. */ - } else { - /* If we can't reach IPv4, return NULL. */ + if (!have_legacy_id) { + /* If we're missing the legacy ID, log a warning and return NULL. */ + log_warn(LD_PROTOCOL, "Missing Legacy ID in link state"); goto done; } - /* We will add support for IPv6 in a later release. */ + /* We will add support for falling back to a 3-hop path in a later + * release. */ - validate: /* We'll validate now that the address we've picked isn't a private one. If - * it is, are we allowing to extend to private address? */ - if (!extend_info_addr_is_allowed(&addr_v4)) { + * it is, are we allowed to extend to private addresses? */ + if (!extend_info_addr_is_allowed(&ap.addr)) { log_fn(LOG_PROTOCOL_WARN, LD_REND, "Requested address is private and we are not allowed to extend to " - "it: %s:%u", fmt_addr(&addr_v4), port_v4); + "it: %s:%u", fmt_addr(&ap.addr), ap.port); goto done; } /* We do have everything for which we think we can connect successfully. */ info = extend_info_new(NULL, legacy_id, (have_ed25519_id) ? &ed25519_pk : NULL, NULL, - onion_key, &addr_v4, port_v4); + onion_key, &ap.addr, ap.port); done: return info; } /***********************************************************************/ -/* Initialize the entire HS subsytem. This is called in tor_init() before any +/** Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void hs_init(void) @@ -1789,7 +1804,7 @@ hs_init(void) hs_cache_init(); } -/* Release and cleanup all memory of the HS subsystem (all version). This is +/** Release and cleanup all memory of the HS subsystem (all version). This is * called by tor_free_all(). */ void hs_free_all(void) @@ -1800,7 +1815,7 @@ hs_free_all(void) hs_client_free_all(); } -/* For the given origin circuit circ, decrement the number of rendezvous +/** For the given origin circuit circ, decrement the number of rendezvous * stream counter. This handles every hidden service version. */ void hs_dec_rdv_stream_counter(origin_circuit_t *circ) @@ -1817,7 +1832,7 @@ hs_dec_rdv_stream_counter(origin_circuit_t *circ) } } -/* For the given origin circuit circ, increment the number of rendezvous +/** For the given origin circuit circ, increment the number of rendezvous * stream counter. This handles every hidden service version. */ void hs_inc_rdv_stream_counter(origin_circuit_t *circ) @@ -1833,3 +1848,42 @@ hs_inc_rdv_stream_counter(origin_circuit_t *circ) tor_assert_nonfatal_unreached(); } } + +/** Return a newly allocated link specifier object that is a copy of dst. */ +link_specifier_t * +link_specifier_dup(const link_specifier_t *src) +{ + link_specifier_t *dup = NULL; + uint8_t *buf = NULL; + + if (BUG(!src)) { + goto err; + } + + ssize_t encoded_len_alloc = link_specifier_encoded_len(src); + if (BUG(encoded_len_alloc < 0)) { + goto err; + } + + buf = tor_malloc_zero(encoded_len_alloc); + ssize_t encoded_len_data = link_specifier_encode(buf, + encoded_len_alloc, + src); + if (BUG(encoded_len_data < 0)) { + goto err; + } + + ssize_t parsed_len = link_specifier_parse(&dup, buf, encoded_len_alloc); + if (BUG(parsed_len < 0)) { + goto err; + } + + goto done; + + err: + dup = NULL; + + done: + tor_free(buf); + return dup; +} diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h index a44505930a..8f743d4d37 100644 --- a/src/feature/hs/hs_common.h +++ b/src/feature/hs/hs_common.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,13 +19,14 @@ struct ed25519_keypair_t; /* Trunnel */ #include "trunnel/ed25519_cert.h" -/* Protocol version 2. Use this instead of hardcoding "2" in the code base, +/** Protocol version 2. Use this instead of hardcoding "2" in the code base, * this adds a clearer semantic to the value when used. */ #define HS_VERSION_TWO 2 -/* Version 3 of the protocol (prop224). */ +/** Version 3 of the protocol (prop224). */ #define HS_VERSION_THREE 3 -/* Earliest and latest version we support. */ +/** Earliest version we support. */ #define HS_VERSION_MIN HS_VERSION_TWO +/** Latest version we support. */ #define HS_VERSION_MAX HS_VERSION_THREE /** Try to maintain this many intro points per service by default. */ @@ -48,94 +49,95 @@ struct ed25519_keypair_t; * rendezvous point before giving up? */ #define MAX_REND_TIMEOUT 30 -/* String prefix for the signature of ESTABLISH_INTRO */ +/** String prefix for the signature of ESTABLISH_INTRO */ #define ESTABLISH_INTRO_SIG_PREFIX "Tor establish-intro cell v1" -/* The default HS time period length */ +/** The default HS time period length */ #define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ -/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +/** The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ -/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +/** The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ -/* Prefix of the onion address checksum. */ +/** Prefix of the onion address checksum. */ #define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" -/* Length of the checksum prefix minus the NUL terminated byte. */ +/** Length of the checksum prefix minus the NUL terminated byte. */ #define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \ (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1) -/* Length of the resulting checksum of the address. The construction of this +/** Length of the resulting checksum of the address. The construction of this * checksum looks like: * CHECKSUM = ".onion checksum" || PUBKEY || VERSION * where VERSION is 1 byte. This is pre-hashing. */ #define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \ (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t)) -/* The amount of bytes we use from the address checksum. */ +/** The amount of bytes we use from the address checksum. */ #define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2 -/* Length of the binary encoded service address which is of course before the +/** Length of the binary encoded service address which is of course before the * base32 encoding. Construction is: * PUBKEY || CHECKSUM || VERSION * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */ #define HS_SERVICE_ADDR_LEN \ (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t)) -/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the +/** Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the * length ends up to 56 bytes (not counting the terminated NUL byte.) */ #define HS_SERVICE_ADDR_LEN_BASE32 \ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) -/* The default HS time period length */ +/** The default HS time period length */ #define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ -/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +/** The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ -/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +/** The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ -/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ +/** The time period rotation offset as seen in prop224 section + * [TIME-PERIODS] */ #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ -/* Keyblinding parameter construction is as follow: +/** Keyblinding parameter construction is as follow: * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ #define HS_KEYBLIND_NONCE_PREFIX "key-blind" #define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1) #define HS_KEYBLIND_NONCE_LEN \ (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) -/* Credential and subcredential prefix value. */ +/** Credential and subcredential prefix value. */ #define HS_CREDENTIAL_PREFIX "credential" #define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1) #define HS_SUBCREDENTIAL_PREFIX "subcredential" #define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) -/* Node hidden service stored at index prefix value. */ +/** Node hidden service stored at index prefix value. */ #define HS_INDEX_PREFIX "store-at-idx" #define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1) -/* Node hidden service directory index prefix value. */ +/** Node hidden service directory index prefix value. */ #define HSDIR_INDEX_PREFIX "node-idx" #define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1) -/* Prefix of the shared random value disaster mode. */ +/** Prefix of the shared random value disaster mode. */ #define HS_SRV_DISASTER_PREFIX "shared-random-disaster" #define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) -/* Default value of number of hsdir replicas (hsdir_n_replicas). */ +/** Default value of number of hsdir replicas (hsdir_n_replicas). */ #define HS_DEFAULT_HSDIR_N_REPLICAS 2 -/* Default value of hsdir spread store (hsdir_spread_store). */ +/** Default value of hsdir spread store (hsdir_spread_store). */ #define HS_DEFAULT_HSDIR_SPREAD_STORE 4 -/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +/** Default value of hsdir spread fetch (hsdir_spread_fetch). */ #define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 -/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is +/** The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is * bigger than the 84 bytes needed for version 3 so we need to pad up to that * length so it is indistinguishable between versions. */ #define HS_LEGACY_RENDEZVOUS_CELL_SIZE \ (REND_COOKIE_LEN + DH1024_KEY_LEN + DIGEST_LEN) -/* Type of authentication key used by an introduction point. */ +/** Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, HS_AUTH_KEY_TYPE_ED25519 = 2, } hs_auth_key_type_t; -/* Return value when adding an ephemeral service through the ADD_ONION +/** Return value when adding an ephemeral service through the ADD_ONION * control port command. Both v2 and v3 share these. */ typedef enum { RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */ @@ -146,18 +148,18 @@ typedef enum { RSAE_OKAY = 0 /**< Service added as expected */ } hs_service_add_ephemeral_status_t; -/* Represents the mapping from a virtual port of a rendezvous service to a +/** Represents the mapping from a virtual port of a rendezvous service to a * real port on some IP. */ typedef struct rend_service_port_config_t { - /* The incoming HS virtual port we're mapping */ + /** The incoming HS virtual port we're mapping */ uint16_t virtual_port; - /* Is this an AF_UNIX port? */ + /** Is this an AF_UNIX port? */ unsigned int is_unix_addr:1; - /* The outgoing TCP port to use, if !is_unix_addr */ + /** The outgoing TCP port to use, if !is_unix_addr */ uint16_t real_port; - /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ + /** The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ tor_addr_t real_addr; - /* The socket path to connect to, if is_unix_addr */ + /** The socket path to connect to, if is_unix_addr */ char unix_addr[FLEXIBLE_ARRAY_MEMBER]; } rend_service_port_config_t; @@ -217,8 +219,6 @@ uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); time_t hs_get_start_time_of_next_time_period(time_t now); -link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); - MOCK_DECL(int, hs_in_period_between_tp_and_srv, (const networkstatus_t *consensus, time_t now)); @@ -243,7 +243,8 @@ void hs_get_responsible_hsdirs(const struct ed25519_public_key_t *blinded_pk, int use_second_hsdir_index, int for_fetching, smartlist_t *responsible_dirs); routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, - const char *req_key_str); + const char *req_key_str, + bool *is_rate_limited_out); time_t hs_hsdir_requery_period(const or_options_t *options); time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, @@ -262,6 +263,8 @@ extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, const struct curve25519_public_key_t *onion_key, int direct_conn); +link_specifier_t *link_specifier_dup(const link_specifier_t *src); + #ifdef HS_COMMON_PRIVATE STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index ee4499ef5b..64656b1935 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -23,8 +23,6 @@ * every option that is common to all version (config_generic_service). **/ -#define HS_CONFIG_PRIVATE - #include "feature/hs/hs_common.h" #include "feature/hs/hs_config.h" #include "feature/hs/hs_client.h" @@ -34,7 +32,7 @@ #include "lib/encoding/confline.h" #include "app/config/or_options_st.h" -/* Using the given list of services, stage them into our global state. Every +/** Using the given list of services, stage them into our global state. Every * service version are handled. This function can remove entries in the given * service_list. * @@ -70,7 +68,7 @@ stage_services(smartlist_t *service_list) hs_service_stage_services(service_list); } -/* Validate the given service against all service in the given list. If the +/** Validate the given service against all service in the given list. If the * service is ephemeral, this function ignores it. Services with the same * directory path aren't allowed and will return an error. If a duplicate is * found, 1 is returned else 0 if none found. */ @@ -118,7 +116,7 @@ service_is_duplicate_in_list(const smartlist_t *service_list, return ret; } -/* Helper function: Given an configuration option name, its value, a minimum +/** Helper function: Given an configuration option name, its value, a minimum * min and a maxium max, parse the value as a uint64_t. On success, ok is set * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be * ignored. This function logs both on error and success. */ @@ -173,7 +171,7 @@ helper_parse_circuit_id_protocol(const char *key, const char *value, int *ok) return ret; } -/* Return the service version by trying to learn it from the key on disk if +/** Return the service version by trying to learn it from the key on disk if * any. If nothing is found, the current service configured version is * returned. */ static int @@ -191,7 +189,7 @@ config_learn_service_version(hs_service_t *service) return version; } -/* Return true iff the given options starting at line_ for a hidden service +/** Return true iff the given options starting at line_ for a hidden service * contains at least one invalid option. Each hidden service option don't * apply to all versions so this function can find out. The line_ MUST start * right after the HiddenServiceDir line of this service. @@ -218,6 +216,9 @@ config_has_invalid_options(const config_line_t *line_, const char *opts_exclude_v2[] = { "HiddenServiceExportCircuitID", + "HiddenServiceEnableIntroDoSDefense", + "HiddenServiceEnableIntroDoSRatePerSec", + "HiddenServiceEnableIntroDoSBurstPerSec", NULL /* End marker. */ }; @@ -250,6 +251,16 @@ config_has_invalid_options(const config_line_t *line_, "version %" PRIu32 " of service in %s", opt, service->config.version, service->config.directory_path); + + if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + /* Special case this v2 option so that we can offer alternatives. + * If more such special cases appear, it would be good to + * generalize the exception mechanism here. */ + log_warn(LD_CONFIG, "For v3 onion service client authorization, " + "please read the 'CLIENT AUTHORIZATION' section in the " + "manual."); + } + ret = 1; /* Continue the loop so we can find all possible options. */ continue; @@ -260,7 +271,7 @@ config_has_invalid_options(const config_line_t *line_, return ret; } -/* Validate service configuration. This is used when loading the configuration +/** Validate service configuration. This is used when loading the configuration * and once we've setup a service object, it's config object is passed to this * function for further validation. This does not validate service key * material. Return 0 if valid else -1 if invalid. */ @@ -276,13 +287,22 @@ config_validate_service(const hs_service_config_t *config) goto invalid; } + /* DoS validation values. */ + if (config->has_dos_defense_enabled && + (config->intro_dos_burst_per_sec < config->intro_dos_rate_per_sec)) { + log_warn(LD_CONFIG, "Hidden service DoS defenses burst (%" PRIu32 ") can " + "not be smaller than the rate value (%" PRIu32 ").", + config->intro_dos_burst_per_sec, config->intro_dos_rate_per_sec); + goto invalid; + } + /* Valid. */ return 0; invalid: return -1; } -/* Configuration funcion for a version 3 service. The line_ must be pointing +/** Configuration funcion for a version 3 service. The line_ must be pointing * to the directive directly after a HiddenServiceDir. That way, when hitting * the next HiddenServiceDir line or reaching the end of the list of lines, we * know that we have to stop looking for more options. The given service @@ -296,6 +316,8 @@ config_service_v3(const config_line_t *line_, { int have_num_ip = 0; bool export_circuit_id = false; /* just to detect duplicate options */ + bool dos_enabled = false, dos_rate_per_sec = false; + bool dos_burst_per_sec = false; const char *dup_opt_seen = NULL; const config_line_t *line; @@ -334,6 +356,52 @@ config_service_v3(const config_line_t *line_, export_circuit_id = true; continue; } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSDefense")) { + config->has_dos_defense_enabled = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT, + 1, &ok); + if (!ok || dos_enabled) { + if (dos_enabled) { + dup_opt_seen = line->key; + } + goto err; + } + dos_enabled = true; + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSRatePerSec")) { + config->intro_dos_rate_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX, &ok); + if (!ok || dos_rate_per_sec) { + if (dos_rate_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_rate_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses rate set to: %" PRIu32, + config->intro_dos_rate_per_sec); + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSBurstPerSec")) { + config->intro_dos_burst_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX, &ok); + if (!ok || dos_burst_per_sec) { + if (dos_burst_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_burst_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses burst set to: %" PRIu32, + config->intro_dos_burst_per_sec); + continue; + } } /* We do not load the key material for the service at this stage. This is @@ -353,7 +421,7 @@ config_service_v3(const config_line_t *line_, return -1; } -/* Configure a service using the given options in line_ and options. This is +/** Configure a service using the given options in line_ and options. This is * called for any service regardless of its version which means that all * directives in this function are generic to any service version. This * function will also check the validity of the service directory path. @@ -496,15 +564,6 @@ config_generic_service(const config_line_t *line_, * becomes a single onion service. */ if (rend_service_non_anonymous_mode_enabled(options)) { config->is_single_onion = 1; - /* We will add support for IPv6-only v3 single onion services in a future - * Tor version. This won't catch "ReachableAddresses reject *4", but that - * option doesn't work anyway. */ - if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) { - log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not " - "supported. Set HiddenServiceSingleHopMode 0 and " - "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1."); - goto err; - } } /* Success */ @@ -516,7 +575,7 @@ config_generic_service(const config_line_t *line_, return -1; } -/* Configure a service using the given line and options. This function will +/** Configure a service using the given line and options. This function will * call the corresponding configuration function for a specific service * version and validate the service against the other ones. On success, add * the service to the given list and return 0. On error, nothing is added to @@ -602,7 +661,7 @@ config_service(const config_line_t *line, const or_options_t *options, return -1; } -/* From a set of <b>options</b>, setup every hidden service found. Return 0 on +/** From a set of <b>options</b>, setup every hidden service found. Return 0 on * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and * return as normal, but don't actually change the configured services. */ int @@ -670,7 +729,7 @@ hs_config_service_all(const or_options_t *options, int validate_only) return ret; } -/* From a set of <b>options</b>, setup every client authorization found. +/** From a set of <b>options</b>, setup every client authorization found. * Return 0 on success or -1 on failure. If <b>validate_only</b> is set, * parse, warn and return as normal, but don't actually change the * configured state. */ diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h index 040e451f13..5694cf1e9b 100644 --- a/src/feature/hs/hs_config.h +++ b/src/feature/hs/hs_config.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,6 +15,15 @@ #define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 /* Maximum number of intro points per version 3 services. */ #define HS_CONFIG_V3_MAX_INTRO_POINTS 20 +/* Default value for the introduction DoS defenses. The MIN/MAX are inclusive + * meaning they can be used as valid values. */ +#define HS_CONFIG_V3_DOS_DEFENSE_DEFAULT 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT 25 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX INT32_MAX +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT 200 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX INT32_MAX /* API */ diff --git a/src/feature/hs/hs_control.c b/src/feature/hs/hs_control.c index 9970fdd123..78b0735c29 100644 --- a/src/feature/hs/hs_control.c +++ b/src/feature/hs/hs_control.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -7,9 +7,10 @@ **/ #include "core/or/or.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "lib/crypt_ops/crypto_format.h" #include "lib/crypt_ops/crypto_util.h" +#include "feature/hs/hs_client.h" #include "feature/hs/hs_common.h" #include "feature/hs/hs_control.h" #include "feature/hs/hs_descriptor.h" @@ -19,7 +20,7 @@ #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerstatus_st.h" -/* Send on the control port the "HS_DESC REQUESTEDÂ [...]" event. +/** Send on the control port the "HS_DESC REQUESTEDÂ [...]" event. * * The onion_pk is the onion service public key, base64_blinded_pk is the * base64 encoded blinded key for the service and hsdir_rs is the routerstatus @@ -56,7 +57,7 @@ hs_control_desc_event_requested(const ed25519_public_key_t *onion_pk, memwipe(onion_address, 0, sizeof(onion_address)); } -/* Send on the control port the "HS_DESC FAILED [...]" event. +/** Send on the control port the "HS_DESC FAILED [...]" event. * * Using a directory connection identifier, the HSDir identity digest and a * reason for the failure. None can be NULL. */ @@ -73,17 +74,14 @@ hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident, tor_assert(reason); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hsv3_descriptor_failed(onion_address, base64_blinded_pk, hsdir_id_digest, reason); } -/* Send on the control port the "HS_DESC RECEIVED [...]" event. +/** Send on the control port the "HS_DESC RECEIVED [...]" event. * * Using a directory connection identifier and the HSDir identity digest. * None can be NULL. */ @@ -98,17 +96,14 @@ hs_control_desc_event_received(const hs_ident_dir_conn_t *ident, tor_assert(hsdir_id_digest); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hsv3_descriptor_received(onion_address, base64_blinded_pk, hsdir_id_digest); } -/* Send on the control port the "HS_DESC CREATED [...]" event. +/** Send on the control port the "HS_DESC CREATED [...]" event. * * Using the onion address of the descriptor's service and the blinded public * key of the descriptor as a descriptor ID. None can be NULL. */ @@ -122,16 +117,14 @@ hs_control_desc_event_created(const char *onion_address, tor_assert(blinded_pk); /* Build base64 encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, blinded_pk); /* Version 3 doesn't use the replica number in its descriptor ID computation * so we pass negative value so the control port subsystem can ignore it. */ control_event_hs_descriptor_created(onion_address, base64_blinded_pk, -1); } -/* Send on the control port the "HS_DESC UPLOAD [...]" event. +/** Send on the control port the "HS_DESC UPLOAD [...]" event. * * Using the onion address of the descriptor's service, the HSDir identity * digest, the blinded public key of the descriptor as a descriptor ID and the @@ -150,9 +143,7 @@ hs_control_desc_event_upload(const char *onion_address, tor_assert(hsdir_index); /* Build base64 encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, blinded_pk); control_event_hs_descriptor_upload(onion_address, hsdir_id_digest, base64_blinded_pk, @@ -160,7 +151,7 @@ hs_control_desc_event_upload(const char *onion_address, DIGEST256_LEN)); } -/* Send on the control port the "HS_DESC UPLOADED [...]" event. +/** Send on the control port the "HS_DESC UPLOADED [...]" event. * * Using the directory connection identifier and the HSDir identity digest. * None can be NULL. */ @@ -178,7 +169,7 @@ hs_control_desc_event_uploaded(const hs_ident_dir_conn_t *ident, control_event_hs_descriptor_uploaded(hsdir_id_digest, onion_address); } -/* Send on the control port the "HS_DESC_CONTENT [...]" event. +/** Send on the control port the "HS_DESC_CONTENT [...]" event. * * Using the directory connection identifier, the HSDir identity digest and * the body of the descriptor (as it was received from the directory). None @@ -195,17 +186,14 @@ hs_control_desc_event_content(const hs_ident_dir_conn_t *ident, tor_assert(hsdir_id_digest); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hs_descriptor_content(onion_address, base64_blinded_pk, hsdir_id_digest, body); } -/* Handle the "HSPOST [...]" command. The body is an encoded descriptor for +/** Handle the "HSPOST [...]" command. The body is an encoded descriptor for * the given onion_address. The descriptor will be uploaded to each directory * in hsdirs_rs. If NULL, the responsible directories for the current time * period will be selected. @@ -259,3 +247,16 @@ hs_control_hspost_command(const char *body, const char *onion_address, smartlist_free(hsdirs); return ret; } + +/** With a given <b>onion_identity_pk</b>, fetch its descriptor, optionally + * using the list of directory servers given in <b>hsdirs</b>, or a random + * server if it is NULL. This function calls hs_client_launch_v3_desc_fetch(). + */ +void +hs_control_hsfetch_command(const ed25519_public_key_t *onion_identity_pk, + const smartlist_t *hsdirs) +{ + tor_assert(onion_identity_pk); + + hs_client_launch_v3_desc_fetch(onion_identity_pk, hsdirs); +} diff --git a/src/feature/hs/hs_control.h b/src/feature/hs/hs_control.h index f7ab642652..947b0ebf1c 100644 --- a/src/feature/hs/hs_control.h +++ b/src/feature/hs/hs_control.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -48,5 +48,9 @@ void hs_control_desc_event_content(const hs_ident_dir_conn_t *ident, int hs_control_hspost_command(const char *body, const char *onion_address, const smartlist_t *hsdirs_rs); +/* Command "HSFETCH [...]" */ +void hs_control_hsfetch_command(const ed25519_public_key_t *onion_identity_pk, + const smartlist_t *hsdirs); + #endif /* !defined(TOR_HS_CONTROL_H) */ diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c index b6abf14a11..d1c81bbff8 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -56,6 +56,7 @@ #define HS_DESCRIPTOR_PRIVATE #include "core/or/or.h" +#include "app/config/config.h" #include "trunnel/ed25519_cert.h" /* Trunnel interface. */ #include "feature/hs/hs_descriptor.h" #include "core/or/circuitbuild.h" @@ -102,7 +103,7 @@ #define str_desc_auth_client "auth-client" #define str_encrypted "encrypted" -/* Authentication supported types. */ +/** Authentication supported types. */ static const struct { hs_desc_auth_type_t type; const char *identifier; @@ -112,7 +113,7 @@ static const struct { { 0, NULL } }; -/* Descriptor ruleset. */ +/** Descriptor ruleset. */ static token_rule_t hs_desc_v3_token_table[] = { T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ), T1(str_lifetime, R3_DESC_LIFETIME, EQ(1), NO_OBJ), @@ -123,7 +124,7 @@ static token_rule_t hs_desc_v3_token_table[] = { END_OF_TABLE }; -/* Descriptor ruleset for the superencrypted section. */ +/** Descriptor ruleset for the superencrypted section. */ static token_rule_t hs_desc_superencrypted_v3_token_table[] = { T1_START(str_desc_auth_type, R3_DESC_AUTH_TYPE, GE(1), NO_OBJ), T1(str_desc_auth_key, R3_DESC_AUTH_KEY, GE(1), NO_OBJ), @@ -132,7 +133,7 @@ static token_rule_t hs_desc_superencrypted_v3_token_table[] = { END_OF_TABLE }; -/* Descriptor ruleset for the encrypted section. */ +/** Descriptor ruleset for the encrypted section. */ static token_rule_t hs_desc_encrypted_v3_token_table[] = { T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ), T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, ARGS, NO_OBJ), @@ -140,7 +141,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { END_OF_TABLE }; -/* Descriptor ruleset for the introduction points section. */ +/** Descriptor ruleset for the introduction points section. */ static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK), @@ -152,7 +153,7 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out. +/** Using a key, salt and encrypted payload, build a MAC and put it in mac_out. * We use SHA3-256 for the MAC computation. * This function can't fail. */ static void @@ -184,7 +185,7 @@ build_mac(const uint8_t *mac_key, size_t mac_key_len, crypto_digest_free(digest); } -/* Using a secret data and a given decriptor object, build the secret +/** Using a secret data and a given decriptor object, build the secret * input needed for the KDF. * * secret_input = SECRET_DATA | subcredential | INT_8(revision_counter) @@ -224,7 +225,7 @@ build_secret_input(const hs_descriptor_t *desc, return secret_input_len; } -/* Do the KDF construction and put the resulting data in key_out which is of +/** Do the KDF construction and put the resulting data in key_out which is of * key_out_len length. It uses SHAKE-256 as specified in the spec. */ static void build_kdf_key(const hs_descriptor_t *desc, @@ -269,7 +270,7 @@ build_kdf_key(const hs_descriptor_t *desc, tor_free(secret_input); } -/* Using the given descriptor, secret data, and salt, run it through our +/** Using the given descriptor, secret data, and salt, run it through our * KDF function and then extract a secret key in key_out, the IV in iv_out * and MAC in mac_out. This function can't fail. */ static void @@ -308,7 +309,7 @@ build_secret_key_iv_mac(const hs_descriptor_t *desc, /* === ENCODING === */ -/* Encode the given link specifier objects into a newly allocated string. +/** Encode the given link specifier objects into a newly allocated string. * This can't fail so caller can always assume a valid string being * returned. */ STATIC char * @@ -324,12 +325,11 @@ encode_link_specifiers(const smartlist_t *specs) link_specifier_list_set_n_spec(lslist, smartlist_len(specs)); - SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, + SMARTLIST_FOREACH_BEGIN(specs, const link_specifier_t *, spec) { - link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec); - if (ls) { - link_specifier_list_add_spec(lslist, ls); - } + link_specifier_t *ls = link_specifier_dup(spec); + tor_assert(ls); + link_specifier_list_add_spec(lslist, ls); } SMARTLIST_FOREACH_END(spec); { @@ -356,7 +356,7 @@ encode_link_specifiers(const smartlist_t *specs) return encoded_b64; } -/* Encode an introduction point legacy key and certificate. Return a newly +/** Encode an introduction point legacy key and certificate. Return a newly * allocated string with it. On failure, return NULL. */ static char * encode_legacy_key(const hs_desc_intro_point_t *ip) @@ -393,7 +393,7 @@ encode_legacy_key(const hs_desc_intro_point_t *ip) return encoded; } -/* Encode an introduction point encryption key and certificate. Return a newly +/** Encode an introduction point encryption key and certificate. Return a newly * allocated string with it. On failure, return NULL. */ static char * encode_enc_key(const hs_desc_intro_point_t *ip) @@ -404,9 +404,7 @@ encode_enc_key(const hs_desc_intro_point_t *ip) tor_assert(ip); /* Base64 encode the encryption key for the "enc-key" field. */ - if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) { - goto done; - } + curve25519_public_to_base64(key_b64, &ip->enc_key); if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) { goto done; } @@ -421,8 +419,8 @@ encode_enc_key(const hs_desc_intro_point_t *ip) return encoded; } -/* Encode an introduction point onion key. Return a newly allocated string - * with it. On failure, return NULL. */ +/** Encode an introduction point onion key. Return a newly allocated string + * with it. Can not fail. */ static char * encode_onion_key(const hs_desc_intro_point_t *ip) { @@ -432,16 +430,13 @@ encode_onion_key(const hs_desc_intro_point_t *ip) tor_assert(ip); /* Base64 encode the encryption key for the "onion-key" field. */ - if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { - goto done; - } + curve25519_public_to_base64(key_b64, &ip->onion_key); tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); - done: return encoded; } -/* Encode an introduction point object and return a newly allocated string +/** Encode an introduction point object and return a newly allocated string * with it. On failure, return NULL. */ static char * encode_intro_point(const ed25519_public_key_t *sig_key, @@ -511,7 +506,7 @@ encode_intro_point(const ed25519_public_key_t *sig_key, return encoded_ip; } -/* Given a source length, return the new size including padding for the +/** Given a source length, return the new size including padding for the * plaintext encryption. */ static size_t compute_padded_plaintext_length(size_t plaintext_len) @@ -531,7 +526,7 @@ compute_padded_plaintext_length(size_t plaintext_len) return plaintext_padded_len; } -/* Given a buffer, pad it up to the encrypted section padding requirement. Set +/** Given a buffer, pad it up to the encrypted section padding requirement. Set * the newly allocated string in padded_out and return the length of the * padded buffer. */ STATIC size_t @@ -554,7 +549,7 @@ build_plaintext_padding(const char *plaintext, size_t plaintext_len, return padded_len; } -/* Using a key, IV and plaintext data of length plaintext_len, create the +/** Using a key, IV and plaintext data of length plaintext_len, create the * encrypted section by encrypting it and setting encrypted_out with the * data. Return size of the encrypted data buffer. */ static size_t @@ -599,7 +594,7 @@ build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, return encrypted_len; } -/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> and +/** Encrypt the given <b>plaintext</b> buffer using <b>desc</b> and * <b>secret_data</b> to get the keys. Set encrypted_out with the encrypted * data and return the length of it. <b>is_superencrypted_layer</b> is set * if this is the outer encrypted layer of the descriptor. */ @@ -669,7 +664,7 @@ encrypt_descriptor_data(const hs_descriptor_t *desc, return final_blob_len; } -/* Create and return a string containing a client-auth entry. It's the +/** Create and return a string containing a client-auth entry. It's the * responsibility of the caller to free the returned string. This function * will never fail. */ static char * @@ -684,7 +679,7 @@ get_auth_client_str(const hs_desc_authorized_client_t *client) char encrypted_cookie_b64[HS_DESC_ENCRYPED_COOKIE_LEN * 2]; #define ASSERT_AND_BASE64(field) STMT_BEGIN \ - tor_assert(!tor_mem_is_zero((char *) client->field, \ + tor_assert(!fast_mem_is_zero((char *) client->field, \ sizeof(client->field))); \ ret = base64_encode_nopad(field##_b64, sizeof(field##_b64), \ client->field, sizeof(client->field)); \ @@ -739,7 +734,7 @@ get_all_auth_client_lines(const hs_descriptor_t *desc) return auth_client_lines_str; } -/* Create the inner layer of the descriptor (which includes the intro points, +/** Create the inner layer of the descriptor (which includes the intro points, * etc.). Return a newly-allocated string with the layer plaintext, or NULL if * an error occurred. It's the responsibility of the caller to free the * returned string. */ @@ -795,11 +790,11 @@ get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc) return encoded_str; } -/* Create the middle layer of the descriptor, which includes the client auth +/** Create the middle layer of the descriptor, which includes the client auth * data and the encrypted inner layer (provided as a base64 string at * <b>layer2_b64_ciphertext</b>). Return a newly-allocated string with the - * layer plaintext, or NULL if an error occurred. It's the responsibility of - * the caller to free the returned string. */ + * layer plaintext. It's the responsibility of the caller to free the returned + * string. Can not fail. */ static char * get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, const char *layer2_b64_ciphertext) @@ -815,13 +810,10 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, const curve25519_public_key_t *ephemeral_pubkey; ephemeral_pubkey = &desc->superencrypted_data.auth_ephemeral_pubkey; - tor_assert(!tor_mem_is_zero((char *) ephemeral_pubkey->public_key, + tor_assert(!fast_mem_is_zero((char *) ephemeral_pubkey->public_key, CURVE25519_PUBKEY_LEN)); - if (curve25519_public_to_base64(ephemeral_key_base64, - ephemeral_pubkey) < 0) { - goto done; - } + curve25519_public_to_base64(ephemeral_key_base64, ephemeral_pubkey); smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_key, ephemeral_key_base64); @@ -846,7 +838,6 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, layer1_str = smartlist_join_strings(lines, "", 0, NULL); - done: /* We need to memwipe all lines because it contains the ephemeral key */ SMARTLIST_FOREACH(lines, char *, a, memwipe(a, 0, strlen(a))); SMARTLIST_FOREACH(lines, char *, a, tor_free(a)); @@ -855,7 +846,7 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, return layer1_str; } -/* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before +/** Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before * returning it. <b>desc</b> is provided to derive the encryption * keys. <b>secret_data</b> is also proved to derive the encryption keys. * <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the @@ -888,7 +879,7 @@ encrypt_desc_data_and_base64(const hs_descriptor_t *desc, return enc_b64; } -/* Generate the secret data which is used to encrypt/decrypt the descriptor. +/** Generate the secret data which is used to encrypt/decrypt the descriptor. * * SECRET_DATA = blinded-public-key * SECRET_DATA = blinded-public-key | descriptor_cookie @@ -935,7 +926,7 @@ build_secret_data(const ed25519_public_key_t *blinded_pubkey, return secret_data_len; } -/* Generate and encode the superencrypted portion of <b>desc</b>. This also +/** Generate and encode the superencrypted portion of <b>desc</b>. This also * involves generating the encrypted portion of the descriptor, and performing * the superencryption. A newly allocated NUL-terminated string pointer * containing the encrypted encoded blob is put in encrypted_blob_out. Return 0 @@ -1009,7 +1000,7 @@ encode_superencrypted_data(const hs_descriptor_t *desc, return ret; } -/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the +/** Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the * newly allocated string of the encoded descriptor. On error, -1 is returned * and encoded_out is untouched. */ static int @@ -1092,11 +1083,7 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_free(encoded_str); goto err; } - if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) { - log_warn(LD_BUG, "Can't base64 encode descriptor signature!"); - tor_free(encoded_str); - goto err; - } + ed25519_signature_to_base64(ed_sig_b64, &sig); /* Create the signature line. */ smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64); } @@ -1125,7 +1112,7 @@ desc_encode_v3(const hs_descriptor_t *desc, /* === DECODING === */ -/* Given the token tok for an auth client, decode it as +/** Given the token tok for an auth client, decode it as * hs_desc_authorized_client_t. tok->args MUST contain at least 3 elements * Return 0 on success else -1 on failure. */ static int @@ -1161,7 +1148,7 @@ decode_auth_client(const directory_token_t *tok, return ret; } -/* Given an encoded string of the link specifiers, return a newly allocated +/** Given an encoded string of the link specifiers, return a newly allocated * list of decoded link specifiers. Return NULL on error. */ STATIC smartlist_t * decode_link_specifiers(const char *encoded) @@ -1190,52 +1177,22 @@ decode_link_specifiers(const char *encoded) results = smartlist_new(); for (i = 0; i < link_specifier_list_getlen_spec(specs); i++) { - hs_desc_link_specifier_t *hs_spec; link_specifier_t *ls = link_specifier_list_get_spec(specs, i); - tor_assert(ls); - - hs_spec = tor_malloc_zero(sizeof(*hs_spec)); - hs_spec->type = link_specifier_get_ls_type(ls); - switch (hs_spec->type) { - case LS_IPV4: - tor_addr_from_ipv4h(&hs_spec->u.ap.addr, - link_specifier_get_un_ipv4_addr(ls)); - hs_spec->u.ap.port = link_specifier_get_un_ipv4_port(ls); - break; - case LS_IPV6: - tor_addr_from_ipv6_bytes(&hs_spec->u.ap.addr, (const char *) - link_specifier_getarray_un_ipv6_addr(ls)); - hs_spec->u.ap.port = link_specifier_get_un_ipv6_port(ls); - break; - case LS_LEGACY_ID: - /* Both are known at compile time so let's make sure they are the same - * else we can copy memory out of bound. */ - tor_assert(link_specifier_getlen_un_legacy_id(ls) == - sizeof(hs_spec->u.legacy_id)); - memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), - sizeof(hs_spec->u.legacy_id)); - break; - case LS_ED25519_ID: - /* Both are known at compile time so let's make sure they are the same - * else we can copy memory out of bound. */ - tor_assert(link_specifier_getlen_un_ed25519_id(ls) == - sizeof(hs_spec->u.ed25519_id)); - memcpy(hs_spec->u.ed25519_id, - link_specifier_getconstarray_un_ed25519_id(ls), - sizeof(hs_spec->u.ed25519_id)); - break; - default: - tor_free(hs_spec); + if (BUG(!ls)) { goto err; } - - smartlist_add(results, hs_spec); + link_specifier_t *ls_dup = link_specifier_dup(ls); + if (BUG(!ls_dup)) { + goto err; + } + smartlist_add(results, ls_dup); } goto done; err: if (results) { - SMARTLIST_FOREACH(results, hs_desc_link_specifier_t *, s, tor_free(s)); + SMARTLIST_FOREACH(results, link_specifier_t *, s, + link_specifier_free(s)); smartlist_free(results); results = NULL; } @@ -1245,7 +1202,7 @@ decode_link_specifiers(const char *encoded) return results; } -/* Given a list of authentication types, decode it and put it in the encrypted +/** Given a list of authentication types, decode it and put it in the encrypted * data section. Return 1 if we at least know one of the type or 0 if we know * none of them. */ static int @@ -1273,7 +1230,7 @@ decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list) return match; } -/* Parse a space-delimited list of integers representing CREATE2 formats into +/** Parse a space-delimited list of integers representing CREATE2 formats into * the bitfield in hs_desc_encrypted_data_t. Ignore unrecognized values. */ static void decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list) @@ -1307,7 +1264,7 @@ decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list) smartlist_free(tokens); } -/* Given a certificate, validate the certificate for certain conditions which +/** Given a certificate, validate the certificate for certain conditions which * are if the given type matches the cert's one, if the signing key is * included and if the that key was actually used to sign the certificate. * @@ -1331,11 +1288,20 @@ cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type) log_warn(LD_REND, "Signing key is NOT included for %s.", log_obj_type); goto err; } + /* The following will not only check if the signature matches but also the * expiration date and overall validity. */ if (tor_cert_checksig(cert, &cert->signing_key, approx_time()) < 0) { - log_warn(LD_REND, "Invalid signature for %s: %s", log_obj_type, - tor_cert_describe_signature_status(cert)); + if (cert->cert_expired) { + char expiration_str[ISO_TIME_LEN+1]; + format_iso_time(expiration_str, cert->valid_until); + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Invalid signature for %s: %s (%s)", + log_obj_type, tor_cert_describe_signature_status(cert), + expiration_str); + } else { + log_warn(LD_REND, "Invalid signature for %s: %s", + log_obj_type, tor_cert_describe_signature_status(cert)); + } goto err; } @@ -1344,7 +1310,7 @@ cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type) return 0; } -/* Given some binary data, try to parse it to get a certificate object. If we +/** Given some binary data, try to parse it to get a certificate object. If we * have a valid cert, validate it using the given wanted type. On error, print * a log using the err_msg has the certificate identifier adding semantic to * the log and cert_out is set to NULL. On success, 0 is returned and cert_out @@ -1381,7 +1347,7 @@ cert_parse_and_validate(tor_cert_t **cert_out, const char *data, return -1; } -/* Return true iff the given length of the encrypted data of a descriptor +/** Return true iff the given length of the encrypted data of a descriptor * passes validation. */ STATIC int encrypted_data_length_is_valid(size_t len) @@ -1400,7 +1366,51 @@ encrypted_data_length_is_valid(size_t len) return 0; } -/* Decrypt the descriptor cookie given the descriptor, the auth client, +/** Build the KEYS component for the authorized client computation. The format + * of the construction is: + * + * SECRET_SEED = x25519(sk, pk) + * KEYS = KDF(subcredential | SECRET_SEED, 40) + * + * Set the <b>keys_out</b> argument to point to the buffer containing the KEYS, + * and return the buffer's length. The caller should wipe and free its content + * once done with it. This function can't fail. */ +static size_t +build_descriptor_cookie_keys(const uint8_t *subcredential, + size_t subcredential_len, + const curve25519_secret_key_t *sk, + const curve25519_public_key_t *pk, + uint8_t **keys_out) +{ + uint8_t secret_seed[CURVE25519_OUTPUT_LEN]; + uint8_t *keystream; + size_t keystream_len = HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN; + crypto_xof_t *xof; + + tor_assert(subcredential); + tor_assert(sk); + tor_assert(pk); + tor_assert(keys_out); + + keystream = tor_malloc_zero(keystream_len); + + /* Calculate x25519(sk, pk) to get the secret seed. */ + curve25519_handshake(secret_seed, sk, pk); + + /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, subcredential, subcredential_len); + crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); + crypto_xof_squeeze_bytes(xof, keystream, keystream_len); + crypto_xof_free(xof); + + memwipe(secret_seed, 0, sizeof(secret_seed)); + + *keys_out = keystream; + return keystream_len; +} + +/** Decrypt the descriptor cookie given the descriptor, the auth client, * and the client secret key. On sucess, return 0 and a newly allocated * descriptor cookie descriptor_cookie_out. On error or if the client id * is invalid, return -1 and descriptor_cookie_out is set to @@ -1412,33 +1422,33 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc, uint8_t **descriptor_cookie_out) { int ret = -1; - uint8_t secret_seed[CURVE25519_OUTPUT_LEN]; - uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN]; - uint8_t *cookie_key = NULL; + uint8_t *keystream = NULL; + size_t keystream_length = 0; uint8_t *descriptor_cookie = NULL; + const uint8_t *cookie_key = NULL; crypto_cipher_t *cipher = NULL; - crypto_xof_t *xof = NULL; tor_assert(desc); tor_assert(client); tor_assert(client_auth_sk); - tor_assert(!tor_mem_is_zero( + tor_assert(!fast_mem_is_zero( (char *) &desc->superencrypted_data.auth_ephemeral_pubkey, sizeof(desc->superencrypted_data.auth_ephemeral_pubkey))); - tor_assert(!tor_mem_is_zero((char *) client_auth_sk, - sizeof(*client_auth_sk))); - tor_assert(!tor_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN)); + tor_assert(!fast_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN)); - /* Calculate x25519(client_x, hs_Y) */ - curve25519_handshake(secret_seed, client_auth_sk, - &desc->superencrypted_data.auth_ephemeral_pubkey); + /* Catch potential code-flow cases of an unitialized private key sneaking + * into this function. */ + if (BUG(fast_mem_is_zero((char *)client_auth_sk, sizeof(*client_auth_sk)))) { + goto done; + } - /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ - xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, desc->subcredential, DIGEST256_LEN); - crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); - crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)); - crypto_xof_free(xof); + /* Get the KEYS component to derive the CLIENT-ID and COOKIE-KEY. */ + keystream_length = + build_descriptor_cookie_keys(desc->subcredential, DIGEST256_LEN, + client_auth_sk, + &desc->superencrypted_data.auth_ephemeral_pubkey, + &keystream); + tor_assert(keystream_length > 0); /* If the client id of auth client is not the same as the calculcated * client id, it means that this auth client is invaild according to the @@ -1464,8 +1474,8 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc, if (cipher) { crypto_cipher_free(cipher); } - memwipe(secret_seed, 0, sizeof(secret_seed)); - memwipe(keystream, 0, sizeof(keystream)); + memwipe(keystream, 0, keystream_length); + tor_free(keystream); return ret; } @@ -1481,10 +1491,8 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc, */ MOCK_IMPL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, const uint8_t *descriptor_cookie, - int is_superencrypted_layer, + bool is_superencrypted_layer, char **decrypted_out)) { uint8_t *decrypted = NULL; @@ -1494,6 +1502,12 @@ decrypt_desc_layer,(const hs_descriptor_t *desc, uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN]; const uint8_t *salt, *encrypted, *desc_mac; size_t encrypted_len, result_len = 0; + const uint8_t *encrypted_blob = (is_superencrypted_layer) + ? desc->plaintext_data.superencrypted_blob + : desc->superencrypted_data.encrypted_blob; + size_t encrypted_blob_size = (is_superencrypted_layer) + ? desc->plaintext_data.superencrypted_blob_size + : desc->superencrypted_data.encrypted_blob_size; tor_assert(decrypted_out); tor_assert(desc); @@ -1592,7 +1606,7 @@ decrypt_desc_layer,(const hs_descriptor_t *desc, return result_len; } -/* Decrypt the superencrypted section of the descriptor using the given +/** Decrypt the superencrypted section of the descriptor using the given * descriptor object <b>desc</b>. A newly allocated NUL terminated string is * put in decrypted_out which contains the superencrypted layer of the * descriptor. Return the length of decrypted_out on success else 0 is @@ -1607,9 +1621,8 @@ desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out) tor_assert(decrypted_out); superencrypted_len = decrypt_desc_layer(desc, - desc->plaintext_data.superencrypted_blob, - desc->plaintext_data.superencrypted_blob_size, - NULL, 1, &superencrypted_plaintext); + NULL, + true, &superencrypted_plaintext); if (!superencrypted_len) { log_warn(LD_REND, "Decrypting superencrypted desc failed."); @@ -1625,7 +1638,7 @@ desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out) return superencrypted_len; } -/* Decrypt the encrypted section of the descriptor using the given descriptor +/** Decrypt the encrypted section of the descriptor using the given descriptor * object <b>desc</b>. A newly allocated NUL terminated string is put in * decrypted_out which contains the encrypted layer of the descriptor. * Return the length of decrypted_out on success else 0 is returned and @@ -1658,9 +1671,9 @@ desc_decrypt_encrypted(const hs_descriptor_t *desc, } encrypted_len = decrypt_desc_layer(desc, - desc->superencrypted_data.encrypted_blob, - desc->superencrypted_data.encrypted_blob_size, - descriptor_cookie, 0, &encrypted_plaintext); + descriptor_cookie, + false, &encrypted_plaintext); + if (!encrypted_len) { goto err; } @@ -1678,7 +1691,7 @@ desc_decrypt_encrypted(const hs_descriptor_t *desc, return encrypted_len; } -/* Given the token tok for an intro point legacy key, the list of tokens, the +/** Given the token tok for an intro point legacy key, the list of tokens, the * introduction point ip being decoded and the descriptor desc from which it * comes from, decode the legacy key and set the intro point object. Return 0 * on success else -1 on failure. */ @@ -1736,7 +1749,7 @@ decode_intro_legacy_key(const directory_token_t *tok, return -1; } -/* Dig into the descriptor <b>tokens</b> to find the onion key we should use +/** Dig into the descriptor <b>tokens</b> to find the onion key we should use * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it * was found and well-formed, otherwise return -1 in case of errors. */ static int @@ -1780,7 +1793,7 @@ set_intro_point_onion_key(curve25519_public_key_t *onion_key_out, return retval; } -/* Given the start of a section and the end of it, decode a single +/** Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't * be decoded. */ @@ -1909,7 +1922,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) return ip; } -/* Given a descriptor string at <b>data</b>, decode all possible introduction +/** Given a descriptor string at <b>data</b>, decode all possible introduction * points that we can find. Add the introduction point object to desc_enc as we * find them. This function can't fail and it is possible that zero * introduction points can be decoded. */ @@ -1972,7 +1985,8 @@ decode_intro_points(const hs_descriptor_t *desc, SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a)); smartlist_free(intro_points); } -/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded + +/** Return 1 iff the given base64 encoded signature in b64_sig from the encoded * descriptor in encoded_desc validates the descriptor content. */ STATIC int desc_sig_is_valid(const char *b64_sig, @@ -2031,14 +2045,14 @@ desc_sig_is_valid(const char *b64_sig, return ret; } -/* Decode descriptor plaintext data for version 3. Given a list of tokens, an +/** Decode descriptor plaintext data for version 3. Given a list of tokens, an * allocated plaintext object that will be populated and the encoded * descriptor with its length. The last one is needed for signature * verification. Unknown tokens are simply ignored so this won't error on * unknowns but requires that all v3 token be present and valid. * * Return 0 on success else a negative value. */ -static int +static hs_desc_decode_status_t desc_decode_plaintext_v3(smartlist_t *tokens, hs_desc_plaintext_data_t *desc, const char *encoded_desc, size_t encoded_len) @@ -2128,21 +2142,19 @@ desc_decode_plaintext_v3(smartlist_t *tokens, goto err; } - return 0; - + return HS_DESC_DECODE_OK; err: - return -1; + return HS_DESC_DECODE_PLAINTEXT_ERROR; } -/* Decode the version 3 superencrypted section of the given descriptor desc. - * The desc_superencrypted_out will be populated with the decoded data. - * Return 0 on success else -1. */ -static int +/** Decode the version 3 superencrypted section of the given descriptor desc. + * The desc_superencrypted_out will be populated with the decoded data. */ +static hs_desc_decode_status_t desc_decode_superencrypted_v3(const hs_descriptor_t *desc, hs_desc_superencrypted_data_t * desc_superencrypted_out) { - int ret = -1; + int ret = HS_DESC_DECODE_SUPERENC_ERROR; char *message = NULL; size_t message_len; memarea_t *area = NULL; @@ -2228,11 +2240,11 @@ desc_decode_superencrypted_v3(const hs_descriptor_t *desc, tok->object_size); superencrypted->encrypted_blob_size = tok->object_size; - ret = 0; + ret = HS_DESC_DECODE_OK; goto done; err: - tor_assert(ret < 0); + tor_assert(ret < HS_DESC_DECODE_OK); hs_desc_superencrypted_data_free_contents(desc_superencrypted_out); done: @@ -2249,15 +2261,14 @@ desc_decode_superencrypted_v3(const hs_descriptor_t *desc, return ret; } -/* Decode the version 3 encrypted section of the given descriptor desc. The - * desc_encrypted_out will be populated with the decoded data. Return 0 on - * success else -1. */ -static int +/** Decode the version 3 encrypted section of the given descriptor desc. The + * desc_encrypted_out will be populated with the decoded data. */ +static hs_desc_decode_status_t desc_decode_encrypted_v3(const hs_descriptor_t *desc, const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_encrypted_out) { - int ret = -1; + int ret = HS_DESC_DECODE_ENCRYPTED_ERROR; char *message = NULL; size_t message_len; memarea_t *area = NULL; @@ -2280,12 +2291,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, * authorization is failing. */ log_warn(LD_REND, "Client authorization for requested onion address " "is invalid. Can't decrypt the descriptor."); + ret = HS_DESC_DECODE_BAD_CLIENT_AUTH; } else { /* Inform at notice level that the onion address requested can't be * reached without client authorization most likely. */ log_notice(LD_REND, "Fail to decrypt descriptor for requested onion " "address. It is likely requiring client " "authorization."); + ret = HS_DESC_DECODE_NEED_CLIENT_AUTH; } goto err; } @@ -2343,11 +2356,11 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, /* NOTE: Unknown fields are allowed because this function could be used to * decode other descriptor version. */ - ret = 0; + ret = HS_DESC_DECODE_OK; goto done; err: - tor_assert(ret < 0); + tor_assert(ret < HS_DESC_DECODE_OK); hs_desc_encrypted_data_free_contents(desc_encrypted_out); done: @@ -2364,9 +2377,9 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, return ret; } -/* Table of encrypted decode function version specific. The function are +/** Table of encrypted decode function version specific. The function are * indexed by the version number so v3 callback is at index 3 in the array. */ -static int +static hs_desc_decode_status_t (*decode_encrypted_handlers[])( const hs_descriptor_t *desc, const curve25519_secret_key_t *client_auth_sk, @@ -2376,15 +2389,15 @@ static int desc_decode_encrypted_v3, }; -/* Decode the encrypted data section of the given descriptor and store the +/** Decode the encrypted data section of the given descriptor and store the * data in the given encrypted data object. Return 0 on success else a * negative value on error. */ -int +hs_desc_decode_status_t hs_desc_decode_encrypted(const hs_descriptor_t *desc, const curve25519_secret_key_t *client_auth_sk, hs_desc_encrypted_data_t *desc_encrypted) { - int ret; + int ret = HS_DESC_DECODE_ENCRYPTED_ERROR; uint32_t version; tor_assert(desc); @@ -2398,7 +2411,6 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc, /* Let's make sure we have a supported version as well. By correctly parsing * the plaintext, this should not fail. */ if (BUG(!hs_desc_is_supported_version(version))) { - ret = -1; goto err; } /* Extra precaution. Having no handler for the supported version should @@ -2417,9 +2429,9 @@ hs_desc_decode_encrypted(const hs_descriptor_t *desc, return ret; } -/* Table of superencrypted decode function version specific. The function are +/** Table of superencrypted decode function version specific. The function are * indexed by the version number so v3 callback is at index 3 in the array. */ -static int +static hs_desc_decode_status_t (*decode_superencrypted_handlers[])( const hs_descriptor_t *desc, hs_desc_superencrypted_data_t *desc_superencrypted) = @@ -2428,15 +2440,14 @@ static int desc_decode_superencrypted_v3, }; -/* Decode the superencrypted data section of the given descriptor and store the - * data in the given superencrypted data object. Return 0 on success else a - * negative value on error. */ -int +/** Decode the superencrypted data section of the given descriptor and store + * the data in the given superencrypted data object. */ +hs_desc_decode_status_t hs_desc_decode_superencrypted(const hs_descriptor_t *desc, hs_desc_superencrypted_data_t * desc_superencrypted) { - int ret; + int ret = HS_DESC_DECODE_SUPERENC_ERROR; uint32_t version; tor_assert(desc); @@ -2450,7 +2461,6 @@ hs_desc_decode_superencrypted(const hs_descriptor_t *desc, /* Let's make sure we have a supported version as well. By correctly parsing * the plaintext, this should not fail. */ if (BUG(!hs_desc_is_supported_version(version))) { - ret = -1; goto err; } /* Extra precaution. Having no handler for the supported version should @@ -2468,9 +2478,9 @@ hs_desc_decode_superencrypted(const hs_descriptor_t *desc, return ret; } -/* Table of plaintext decode function version specific. The function are +/** Table of plaintext decode function version specific. The function are * indexed by the version number so v3 callback is at index 3 in the array. */ -static int +static hs_desc_decode_status_t (*decode_plaintext_handlers[])( smartlist_t *tokens, hs_desc_plaintext_data_t *desc, @@ -2481,13 +2491,13 @@ static int desc_decode_plaintext_v3, }; -/* Fully decode the given descriptor plaintext and store the data in the - * plaintext data object. Returns 0 on success else a negative value. */ -int +/** Fully decode the given descriptor plaintext and store the data in the + * plaintext data object. */ +hs_desc_decode_status_t hs_desc_decode_plaintext(const char *encoded, hs_desc_plaintext_data_t *plaintext) { - int ok = 0, ret = -1; + int ok = 0, ret = HS_DESC_DECODE_PLAINTEXT_ERROR; memarea_t *area = NULL; smartlist_t *tokens = NULL; size_t encoded_len; @@ -2537,11 +2547,11 @@ hs_desc_decode_plaintext(const char *encoded, /* Run the version specific plaintext decoder. */ ret = decode_plaintext_handlers[plaintext->version](tokens, plaintext, encoded, encoded_len); - if (ret < 0) { + if (ret != HS_DESC_DECODE_OK) { goto err; } /* Success. Descriptor has been populated with the data. */ - ret = 0; + ret = HS_DESC_DECODE_OK; err: if (tokens) { @@ -2554,19 +2564,19 @@ hs_desc_decode_plaintext(const char *encoded, return ret; } -/* Fully decode an encoded descriptor and set a newly allocated descriptor +/** Fully decode an encoded descriptor and set a newly allocated descriptor * object in desc_out. Client secret key is used to decrypt the "encrypted" * section if not NULL else it's ignored. * * Return 0 on success. A negative value is returned on error and desc_out is * set to NULL. */ -int +hs_desc_decode_status_t hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, const curve25519_secret_key_t *client_auth_sk, hs_descriptor_t **desc_out) { - int ret = -1; + hs_desc_decode_status_t ret = HS_DESC_DECODE_GENERIC_ERROR; hs_descriptor_t *desc; tor_assert(encoded); @@ -2575,7 +2585,7 @@ hs_desc_decode_descriptor(const char *encoded, /* Subcredentials are not optional. */ if (BUG(!subcredential || - tor_mem_is_zero((char*)subcredential, DIGEST256_LEN))) { + fast_mem_is_zero((char*)subcredential, DIGEST256_LEN))) { log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); goto err; } @@ -2583,17 +2593,17 @@ hs_desc_decode_descriptor(const char *encoded, memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); - if (ret < 0) { + if (ret != HS_DESC_DECODE_OK) { goto err; } ret = hs_desc_decode_superencrypted(desc, &desc->superencrypted_data); - if (ret < 0) { + if (ret != HS_DESC_DECODE_OK) { goto err; } ret = hs_desc_decode_encrypted(desc, client_auth_sk, &desc->encrypted_data); - if (ret < 0) { + if (ret != HS_DESC_DECODE_OK) { goto err; } @@ -2614,7 +2624,7 @@ hs_desc_decode_descriptor(const char *encoded, return ret; } -/* Table of encode function version specific. The functions are indexed by the +/** Table of encode function version specific. The functions are indexed by the * version number so v3 callback is at index 3 in the array. */ static int (*encode_handlers[])( @@ -2627,7 +2637,7 @@ static int desc_encode_v3, }; -/* Encode the given descriptor desc including signing with the given key pair +/** Encode the given descriptor desc including signing with the given key pair * signing_kp and encrypting with the given descriptor cookie. * * If the client authorization is enabled, descriptor_cookie must be the same @@ -2672,7 +2682,8 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc, if (!descriptor_cookie) { ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL, NULL); - if (BUG(ret < 0)) { + if (BUG(ret != HS_DESC_DECODE_OK)) { + ret = -1; goto err; } } @@ -2684,7 +2695,7 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc, return ret; } -/* Free the content of the plaintext section of a descriptor. */ +/** Free the content of the plaintext section of a descriptor. */ void hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) { @@ -2700,7 +2711,7 @@ hs_desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) memwipe(desc, 0, sizeof(*desc)); } -/* Free the content of the superencrypted section of a descriptor. */ +/** Free the content of the superencrypted section of a descriptor. */ void hs_desc_superencrypted_data_free_contents(hs_desc_superencrypted_data_t *desc) { @@ -2720,7 +2731,7 @@ hs_desc_superencrypted_data_free_contents(hs_desc_superencrypted_data_t *desc) memwipe(desc, 0, sizeof(*desc)); } -/* Free the content of the encrypted section of a descriptor. */ +/** Free the content of the encrypted section of a descriptor. */ void hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) { @@ -2740,7 +2751,7 @@ hs_desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) memwipe(desc, 0, sizeof(*desc)); } -/* Free the descriptor plaintext data object. */ +/** Free the descriptor plaintext data object. */ void hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc) { @@ -2748,7 +2759,7 @@ hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc) tor_free(desc); } -/* Free the descriptor plaintext data object. */ +/** Free the descriptor plaintext data object. */ void hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc) { @@ -2756,7 +2767,7 @@ hs_desc_superencrypted_data_free_(hs_desc_superencrypted_data_t *desc) tor_free(desc); } -/* Free the descriptor encrypted data object. */ +/** Free the descriptor encrypted data object. */ void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc) { @@ -2764,7 +2775,7 @@ hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc) tor_free(desc); } -/* Free the given descriptor object. */ +/** Free the given descriptor object. */ void hs_descriptor_free_(hs_descriptor_t *desc) { @@ -2778,7 +2789,7 @@ hs_descriptor_free_(hs_descriptor_t *desc) tor_free(desc); } -/* Return the size in bytes of the given plaintext data object. A sizeof() is +/** Return the size in bytes of the given plaintext data object. A sizeof() is * not enough because the object contains pointers and the encrypted blob. * This is particularly useful for our OOM subsystem that tracks the HSDir * cache size for instance. */ @@ -2790,7 +2801,7 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } -/* Return the size in bytes of the given encrypted data object. Used by OOM +/** Return the size in bytes of the given encrypted data object. Used by OOM * subsystem. */ static size_t hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) @@ -2810,18 +2821,20 @@ hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) return sizeof(*data) + intro_size; } -/* Return the size in bytes of the given descriptor object. Used by OOM +/** Return the size in bytes of the given descriptor object. Used by OOM * subsystem. */ size_t hs_desc_obj_size(const hs_descriptor_t *data) { - tor_assert(data); + if (data == NULL) { + return 0; + } return (hs_desc_plaintext_obj_size(&data->plaintext_data) + hs_desc_encrypted_obj_size(&data->encrypted_data) + sizeof(data->subcredential)); } -/* Return a newly allocated descriptor intro point. */ +/** Return a newly allocated descriptor intro point. */ hs_desc_intro_point_t * hs_desc_intro_point_new(void) { @@ -2830,7 +2843,7 @@ hs_desc_intro_point_new(void) return ip; } -/* Free a descriptor intro point object. */ +/** Free a descriptor intro point object. */ void hs_desc_intro_point_free_(hs_desc_intro_point_t *ip) { @@ -2838,8 +2851,8 @@ hs_desc_intro_point_free_(hs_desc_intro_point_t *ip) return; } if (ip->link_specifiers) { - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, hs_desc_link_specifier_free(ls)); + SMARTLIST_FOREACH(ip->link_specifiers, link_specifier_t *, + ls, link_specifier_free(ls)); smartlist_free(ip->link_specifiers); } tor_cert_free(ip->auth_key_cert); @@ -2849,7 +2862,7 @@ hs_desc_intro_point_free_(hs_desc_intro_point_t *ip) tor_free(ip); } -/* Allocate and build a new fake client info for the descriptor. Return a +/** Allocate and build a new fake client info for the descriptor. Return a * newly allocated object. This can't fail. */ hs_desc_authorized_client_t * hs_desc_build_fake_authorized_client(void) @@ -2867,7 +2880,7 @@ hs_desc_build_fake_authorized_client(void) return client_auth; } -/* Using the service's subcredential, client public key, auth ephemeral secret +/** Using the service's subcredential, client public key, auth ephemeral secret * key, and descriptor cookie, build the auth client so we can then encode the * descriptor for publication. client_out must be already allocated. */ void @@ -2878,38 +2891,33 @@ hs_desc_build_authorized_client(const uint8_t *subcredential, const uint8_t *descriptor_cookie, hs_desc_authorized_client_t *client_out) { - uint8_t secret_seed[CURVE25519_OUTPUT_LEN]; - uint8_t keystream[HS_DESC_CLIENT_ID_LEN + HS_DESC_COOKIE_KEY_LEN]; - uint8_t *cookie_key; + uint8_t *keystream = NULL; + size_t keystream_length = 0; + const uint8_t *cookie_key; crypto_cipher_t *cipher; - crypto_xof_t *xof; tor_assert(client_auth_pk); tor_assert(auth_ephemeral_sk); tor_assert(descriptor_cookie); tor_assert(client_out); tor_assert(subcredential); - tor_assert(!tor_mem_is_zero((char *) auth_ephemeral_sk, + tor_assert(!fast_mem_is_zero((char *) auth_ephemeral_sk, sizeof(*auth_ephemeral_sk))); - tor_assert(!tor_mem_is_zero((char *) client_auth_pk, + tor_assert(!fast_mem_is_zero((char *) client_auth_pk, sizeof(*client_auth_pk))); - tor_assert(!tor_mem_is_zero((char *) descriptor_cookie, + tor_assert(!fast_mem_is_zero((char *) descriptor_cookie, HS_DESC_DESCRIPTOR_COOKIE_LEN)); - tor_assert(!tor_mem_is_zero((char *) subcredential, + tor_assert(!fast_mem_is_zero((char *) subcredential, DIGEST256_LEN)); - /* Calculate x25519(hs_y, client_X) */ - curve25519_handshake(secret_seed, - auth_ephemeral_sk, - client_auth_pk); - - /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ - xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, subcredential, DIGEST256_LEN); - crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); - crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)); - crypto_xof_free(xof); + /* Get the KEYS part so we can derive the CLIENT-ID and COOKIE-KEY. */ + keystream_length = + build_descriptor_cookie_keys(subcredential, DIGEST256_LEN, + auth_ephemeral_sk, client_auth_pk, + &keystream); + tor_assert(keystream_length > 0); + /* Extract the CLIENT-ID and COOKIE-KEY from the KEYS. */ memcpy(client_out->client_id, keystream, HS_DESC_CLIENT_ID_LEN); cookie_key = keystream + HS_DESC_CLIENT_ID_LEN; @@ -2924,83 +2932,20 @@ hs_desc_build_authorized_client(const uint8_t *subcredential, (const char *) descriptor_cookie, HS_DESC_DESCRIPTOR_COOKIE_LEN); - memwipe(secret_seed, 0, sizeof(secret_seed)); - memwipe(keystream, 0, sizeof(keystream)); + memwipe(keystream, 0, keystream_length); + tor_free(keystream); crypto_cipher_free(cipher); } -/* Free an authoriezd client object. */ +/** Free an authoriezd client object. */ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client) { tor_free(client); } -/* Free the given descriptor link specifier. */ -void -hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls) -{ - if (ls == NULL) { - return; - } - tor_free(ls); -} - -/* Return a newly allocated descriptor link specifier using the given extend - * info and requested type. Return NULL on error. */ -hs_desc_link_specifier_t * -hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) -{ - hs_desc_link_specifier_t *ls = NULL; - - tor_assert(info); - - ls = tor_malloc_zero(sizeof(*ls)); - ls->type = type; - switch (ls->type) { - case LS_IPV4: - if (info->addr.family != AF_INET) { - goto err; - } - tor_addr_copy(&ls->u.ap.addr, &info->addr); - ls->u.ap.port = info->port; - break; - case LS_IPV6: - if (info->addr.family != AF_INET6) { - goto err; - } - tor_addr_copy(&ls->u.ap.addr, &info->addr); - ls->u.ap.port = info->port; - break; - case LS_LEGACY_ID: - /* Bug out if the identity digest is not set */ - if (BUG(tor_mem_is_zero(info->identity_digest, - sizeof(info->identity_digest)))) { - goto err; - } - memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); - break; - case LS_ED25519_ID: - /* ed25519 keys are optional for intro points */ - if (ed25519_public_key_is_zero(&info->ed_identity)) { - goto err; - } - memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, - sizeof(ls->u.ed25519_id)); - break; - default: - /* Unknown type is code flow error. */ - tor_assert(0); - } - - return ls; - err: - tor_free(ls); - return NULL; -} - -/* From the given descriptor, remove and free every introduction point. */ +/** From the given descriptor, remove and free every introduction point. */ void hs_descriptor_clear_intro_points(hs_descriptor_t *desc) { @@ -3015,59 +2960,3 @@ hs_descriptor_clear_intro_points(hs_descriptor_t *desc) smartlist_clear(ips); } } - -/* From a descriptor link specifier object spec, returned a newly allocated - * link specifier object that is the encoded representation of spec. Return - * NULL on error. */ -link_specifier_t * -hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec) -{ - tor_assert(spec); - - link_specifier_t *ls = link_specifier_new(); - link_specifier_set_ls_type(ls, spec->type); - - switch (spec->type) { - case LS_IPV4: - link_specifier_set_un_ipv4_addr(ls, - tor_addr_to_ipv4h(&spec->u.ap.addr)); - link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); - /* Four bytes IPv4 and two bytes port. */ - link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + - sizeof(spec->u.ap.port)); - break; - case LS_IPV6: - { - size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); - const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); - uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); - memcpy(ipv6_array, in6_addr, addr_len); - link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); - /* Sixteen bytes IPv6 and two bytes port. */ - link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); - break; - } - case LS_LEGACY_ID: - { - size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); - uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); - memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); - link_specifier_set_ls_len(ls, legacy_id_len); - break; - } - case LS_ED25519_ID: - { - size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); - uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); - memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); - link_specifier_set_ls_len(ls, ed25519_id_len); - break; - } - default: - tor_assert_nonfatal_unreached(); - link_specifier_free(ls); - ls = NULL; - } - - return ls; -} diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h index 04a8e16d63..639dd31c8f 100644 --- a/src/feature/hs/hs_descriptor.h +++ b/src/feature/hs/hs_descriptor.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,106 +18,113 @@ /* Trunnel */ struct link_specifier_t; -/* The earliest descriptor format version we support. */ +/** The earliest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 -/* The latest descriptor format version we support. */ +/** The latest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 -/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours +/** Default lifetime of a descriptor in seconds. The valus is set at 3 hours * which is 180 minutes or 10800 seconds. */ #define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) -/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours +/** Maximum lifetime of a descriptor in seconds. The value is set at 12 hours * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) -/* Lifetime of certificate in the descriptor. This defines the lifetime of the +/** Lifetime of certificate in the descriptor. This defines the lifetime of the * descriptor signing key and the cross certification cert of that key. It is * set to 54 hours because a descriptor can be around for 48 hours and because * consensuses are used after the hour, add an extra 6 hours to give some time * for the service to stop using it. */ #define HS_DESC_CERT_LIFETIME (54 * 60 * 60) -/* Length of the salt needed for the encrypted section of a descriptor. */ +/** Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 -/* Length of the KDF output value which is the length of the secret key, +/** Length of the KDF output value which is the length of the secret key, * the secret IV and MAC key length which is the length of H() output. */ #define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \ CIPHER256_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN -/* Pad plaintext of superencrypted data section before encryption so that its +/** Pad plaintext of superencrypted data section before encryption so that its * length is a multiple of this value. */ #define HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE 10000 -/* Maximum length in bytes of a full hidden service descriptor. */ +/** Maximum length in bytes of a full hidden service descriptor. */ #define HS_DESC_MAX_LEN 50000 /* 50kb max size */ -/* Key length for the descriptor symmetric encryption. As specified in the +/** Key length for the descriptor symmetric encryption. As specified in the * protocol, we use AES-256 for the encrypted section of the descriptor. The * following is the length in bytes and the bit size. */ #define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN #define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8) -/* Length of each components in the auth client section in the descriptor. */ +/** Length of each components in the auth client section in the descriptor. */ #define HS_DESC_CLIENT_ID_LEN 8 #define HS_DESC_DESCRIPTOR_COOKIE_LEN 16 #define HS_DESC_COOKIE_KEY_LEN 32 #define HS_DESC_COOKIE_KEY_BIT_SIZE (HS_DESC_COOKIE_KEY_LEN * 8) #define HS_DESC_ENCRYPED_COOKIE_LEN HS_DESC_DESCRIPTOR_COOKIE_LEN -/* The number of auth client entries in the descriptor must be the multiple +/** The number of auth client entries in the descriptor must be the multiple * of this constant. */ #define HS_DESC_AUTH_CLIENT_MULTIPLE 16 -/* Type of authentication in the descriptor. */ +/** Type of authentication in the descriptor. */ typedef enum { HS_DESC_AUTH_ED25519 = 1 } hs_desc_auth_type_t; -/* Link specifier object that contains information on how to extend to the - * relay that is the address, port and handshake type. */ -typedef struct hs_desc_link_specifier_t { - /* Indicate the type of link specifier. See trunnel ed25519_cert - * specification. */ - uint8_t type; - - /* It must be one of these types, can't be more than one. */ - union { - /* IP address and port of the relay use to extend. */ - tor_addr_port_t ap; - /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ - uint8_t legacy_id[DIGEST_LEN]; - /* ed25519 identity. A 32-byte key. */ - uint8_t ed25519_id[ED25519_PUBKEY_LEN]; - } u; -} hs_desc_link_specifier_t; - -/* Introduction point information located in a descriptor. */ +/** Error code when decoding a descriptor. */ +typedef enum { + /* The configured client authorization for the requested .onion address + * failed to decode the descriptor. */ + HS_DESC_DECODE_BAD_CLIENT_AUTH = -6, + + /* The requested .onion address requires a client authorization. */ + HS_DESC_DECODE_NEED_CLIENT_AUTH = -5, + + /* Error during decryption of the encrypted layer. */ + HS_DESC_DECODE_ENCRYPTED_ERROR = -4, + + /* Error during decryption of the super encrypted layer. */ + HS_DESC_DECODE_SUPERENC_ERROR = -3, + + /* Error while decoding the plaintext section. */ + HS_DESC_DECODE_PLAINTEXT_ERROR = -2, + + /* Generic error. */ + HS_DESC_DECODE_GENERIC_ERROR = -1, + + /* Decoding a descriptor was successful. */ + HS_DESC_DECODE_OK = 0, +} hs_desc_decode_status_t; + +/** Introduction point information located in a descriptor. */ typedef struct hs_desc_intro_point_t { - /* Link specifier(s) which details how to extend to the relay. This list - * contains hs_desc_link_specifier_t object. It MUST have at least one. */ + /** Link specifier(s) which details how to extend to the relay. This list + * contains link_specifier_t objects. It MUST have at least one. */ smartlist_t *link_specifiers; - /* Onion key of the introduction point used to extend to it for the ntor + /** Onion key of the introduction point used to extend to it for the ntor * handshake. */ curve25519_public_key_t onion_key; - /* Authentication key used to establish the introduction point circuit and + /** Authentication key used to establish the introduction point circuit and * cross-certifies the blinded public key for the replica thus signed by * the blinded key and in turn signs it. */ tor_cert_t *auth_key_cert; - /* Encryption key for the "ntor" type. */ + /** Encryption key for the "ntor" type. */ curve25519_public_key_t enc_key; - /* Certificate cross certifying the descriptor signing key by the encryption + /** Certificate cross certifying the descriptor signing key by the encryption * curve25519 key. This certificate contains the signing key and is of type * CERT_TYPE_CROSS_HS_IP_KEYS [0B]. */ tor_cert_t *enc_key_cert; - /* (Optional): If this introduction point is a legacy one that is version <= + /** (Optional): If this introduction point is a legacy one that is version <= * 0.2.9.x (HSIntro=3), we use this extra key for the intro point to be able * to relay the cells to the service correctly. */ struct { - /* RSA public key. */ + /** RSA public key. */ crypto_pk_t *key; - /* Cross certified cert with the descriptor signing key (RSA->Ed). Because + /** Cross certified cert with the descriptor signing key (RSA->Ed). Because * of the cross certification API, we need to keep the certificate binary * blob and its length in order to properly encode it after. */ struct { @@ -126,115 +133,115 @@ typedef struct hs_desc_intro_point_t { } cert; } legacy; - /* True iff the introduction point has passed the cross certification. Upon + /** True iff the introduction point has passed the cross certification. Upon * decoding an intro point, this must be true. */ unsigned int cross_certified : 1; } hs_desc_intro_point_t; -/* Authorized client information located in a descriptor. */ +/** Authorized client information located in a descriptor. */ typedef struct hs_desc_authorized_client_t { - /* An identifier that the client will use to identify which auth client + /** An identifier that the client will use to identify which auth client * entry it needs to use. */ uint8_t client_id[HS_DESC_CLIENT_ID_LEN]; - /* An IV that is used to decrypt the encrypted descriptor cookie. */ + /** An IV that is used to decrypt the encrypted descriptor cookie. */ uint8_t iv[CIPHER_IV_LEN]; - /* An encrypted descriptor cookie that the client needs to decrypt to use + /** An encrypted descriptor cookie that the client needs to decrypt to use * it to decrypt the descriptor. */ uint8_t encrypted_cookie[HS_DESC_ENCRYPED_COOKIE_LEN]; } hs_desc_authorized_client_t; -/* The encrypted data section of a descriptor. Obviously the data in this is +/** The encrypted data section of a descriptor. Obviously the data in this is * in plaintext but encrypted once encoded. */ typedef struct hs_desc_encrypted_data_t { - /* Bitfield of CREATE2 cell supported formats. The only currently supported + /** Bitfield of CREATE2 cell supported formats. The only currently supported * format is ntor. */ unsigned int create2_ntor : 1; - /* A list of authentication types that a client must at least support one + /** A list of authentication types that a client must at least support one * in order to contact the service. Contains NULL terminated strings. */ smartlist_t *intro_auth_types; - /* Is this descriptor a single onion service? */ + /** Is this descriptor a single onion service? */ unsigned int single_onion_service : 1; - /* A list of intro points. Contains hs_desc_intro_point_t objects. */ + /** A list of intro points. Contains hs_desc_intro_point_t objects. */ smartlist_t *intro_points; } hs_desc_encrypted_data_t; -/* The superencrypted data section of a descriptor. Obviously the data in +/** The superencrypted data section of a descriptor. Obviously the data in * this is in plaintext but encrypted once encoded. */ typedef struct hs_desc_superencrypted_data_t { - /* This field contains ephemeral x25519 public key which is used by + /** This field contains ephemeral x25519 public key which is used by * the encryption scheme in the client authorization. */ curve25519_public_key_t auth_ephemeral_pubkey; - /* A list of authorized clients. Contains hs_desc_authorized_client_t + /** A list of authorized clients. Contains hs_desc_authorized_client_t * objects. */ smartlist_t *clients; - /* Decoding only: The b64-decoded encrypted blob from the descriptor */ + /** Decoding only: The b64-decoded encrypted blob from the descriptor */ uint8_t *encrypted_blob; - /* Decoding only: Size of the encrypted_blob */ + /** Decoding only: Size of the encrypted_blob */ size_t encrypted_blob_size; } hs_desc_superencrypted_data_t; -/* Plaintext data that is unencrypted information of the descriptor. */ +/** Plaintext data that is unencrypted information of the descriptor. */ typedef struct hs_desc_plaintext_data_t { - /* Version of the descriptor format. Spec specifies this field as a + /** Version of the descriptor format. Spec specifies this field as a * positive integer. */ uint32_t version; - /* The lifetime of the descriptor in seconds. */ + /** The lifetime of the descriptor in seconds. */ uint32_t lifetime_sec; - /* Certificate with the short-term ed22519 descriptor signing key for the + /** Certificate with the short-term ed22519 descriptor signing key for the * replica which is signed by the blinded public key for that replica. */ tor_cert_t *signing_key_cert; - /* Signing public key which is used to sign the descriptor. Same public key + /** Signing public key which is used to sign the descriptor. Same public key * as in the signing key certificate. */ ed25519_public_key_t signing_pubkey; - /* Blinded public key used for this descriptor derived from the master + /** Blinded public key used for this descriptor derived from the master * identity key and generated for a specific replica number. */ ed25519_public_key_t blinded_pubkey; - /* Revision counter is incremented at each upload, regardless of whether + /** Revision counter is incremented at each upload, regardless of whether * the descriptor has changed. This avoids leaking whether the descriptor * has changed. Spec specifies this as a 8 bytes positive integer. */ uint64_t revision_counter; - /* Decoding only: The b64-decoded superencrypted blob from the descriptor */ + /** Decoding only: The b64-decoded superencrypted blob from the descriptor */ uint8_t *superencrypted_blob; - /* Decoding only: Size of the superencrypted_blob */ + /** Decoding only: Size of the superencrypted_blob */ size_t superencrypted_blob_size; } hs_desc_plaintext_data_t; -/* Service descriptor in its decoded form. */ +/** Service descriptor in its decoded form. */ typedef struct hs_descriptor_t { - /* Contains the plaintext part of the descriptor. */ + /** Contains the plaintext part of the descriptor. */ hs_desc_plaintext_data_t plaintext_data; - /* The following contains what's in the superencrypted part of the + /** The following contains what's in the superencrypted part of the * descriptor. It's only encrypted in the encoded version of the descriptor * thus the data contained in that object is in plaintext. */ hs_desc_superencrypted_data_t superencrypted_data; - /* The following contains what's in the encrypted part of the descriptor. + /** The following contains what's in the encrypted part of the descriptor. * It's only encrypted in the encoded version of the descriptor thus the * data contained in that object is in plaintext. */ hs_desc_encrypted_data_t encrypted_data; - /* Subcredentials of a service, used by the client and service to decrypt + /** Subcredentials of a service, used by the client and service to decrypt * the encrypted data. */ uint8_t subcredential[DIGEST256_LEN]; } hs_descriptor_t; -/* Return true iff the given descriptor format version is supported. */ +/** Return true iff the given descriptor format version is supported. */ static inline int hs_desc_is_supported_version(uint32_t version) { @@ -261,12 +268,6 @@ void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc); #define hs_desc_encrypted_data_free(desc) \ FREE_AND_NULL(hs_desc_encrypted_data_t, hs_desc_encrypted_data_free_, (desc)) -void hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls); -#define hs_desc_link_specifier_free(ls) \ - FREE_AND_NULL(hs_desc_link_specifier_t, hs_desc_link_specifier_free_, (ls)) - -hs_desc_link_specifier_t *hs_desc_link_specifier_new( - const extend_info_t *info, uint8_t type); void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); MOCK_DECL(int, @@ -299,10 +300,8 @@ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client); FREE_AND_NULL(hs_desc_authorized_client_t, \ hs_desc_authorized_client_free_, (client)) -link_specifier_t *hs_desc_lspec_to_trunnel( - const hs_desc_link_specifier_t *spec); - hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void); + void hs_desc_build_authorized_client(const uint8_t *subcredential, const curve25519_public_key_t * client_auth_pk, @@ -335,10 +334,8 @@ STATIC int desc_sig_is_valid(const char *b64_sig, const char *encoded_desc, size_t encoded_len); MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, const uint8_t *descriptor_cookie, - int is_superencrypted_layer, + bool is_superencrypted_layer, char **decrypted_out)); #endif /* defined(HS_DESCRIPTOR_PRIVATE) */ diff --git a/src/feature/hs/hs_dos.c b/src/feature/hs/hs_dos.c new file mode 100644 index 0000000000..1f7415a280 --- /dev/null +++ b/src/feature/hs/hs_dos.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2019-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_dos.c + * \brief Implement denial of service mitigation for the onion service + * subsystem. + * + * This module defenses: + * + * - Introduction Rate Limiting: If enabled by the consensus, an introduction + * point will rate limit client introduction towards the service (INTRODUCE2 + * cells). It uses a token bucket model with a rate and burst per second. + * + * Proposal 305 will expand this module by allowing an operator to define + * these values into the ESTABLISH_INTRO cell. Not yet implemented. + **/ + +#define HS_DOS_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" + +#include "core/or/circuitlist.h" + +#include "feature/hs/hs_circuitmap.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/relay/routermode.h" + +#include "lib/evloop/token_bucket.h" + +#include "feature/hs/hs_dos.h" + +/** Default value of the allowed INTRODUCE2 cell rate per second. Above that + * value per second, the introduction is denied. */ +#define HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC 25 + +/** Default value of the allowed INTRODUCE2 cell burst per second. This is the + * maximum value a token bucket has per second. We thus allow up to this value + * of INTRODUCE2 cell per second but the bucket is refilled by the rate value + * but never goes above that burst value. */ +#define HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC 200 + +/** Default value of the consensus parameter enabling or disabling the + * introduction DoS defense. Disabled by default. */ +#define HS_DOS_INTRODUCE_ENABLED_DEFAULT 0 + +/** INTRODUCE2 rejected request counter. */ +static uint64_t intro2_rejected_count = 0; + +/* Consensus parameters. The ESTABLISH_INTRO DoS cell extension have higher + * priority than these values. If no extension is sent, these are used only by + * the introduction point. */ +static uint32_t consensus_param_introduce_rate_per_sec = + HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC; +static uint32_t consensus_param_introduce_burst_per_sec = + HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC; +static uint32_t consensus_param_introduce_defense_enabled = + HS_DOS_INTRODUCE_ENABLED_DEFAULT; + +STATIC uint32_t +get_intro2_enable_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSDefense", + HS_DOS_INTRODUCE_ENABLED_DEFAULT, 0, 1); +} + +/** Return the parameter for the introduction rate per sec. */ +STATIC uint32_t +get_intro2_rate_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSRatePerSec", + HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC, + 0, INT32_MAX); +} + +/** Return the parameter for the introduction burst per sec. */ +STATIC uint32_t +get_intro2_burst_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSBurstPerSec", + HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC, + 0, INT32_MAX); +} + +/** Go over all introduction circuit relay side and adjust their rate/burst + * values using the global parameters. This is called right after the + * consensus parameters might have changed. */ +static void +update_intro_circuits(void) +{ + /* Returns all HS version intro circuits. */ + smartlist_t *intro_circs = hs_circuitmap_get_all_intro_circ_relay_side(); + + SMARTLIST_FOREACH_BEGIN(intro_circs, circuit_t *, circ) { + /* Defenses might have been enabled or disabled. */ + TO_OR_CIRCUIT(circ)->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; + /* Adjust the rate/burst value that might have changed. */ + token_bucket_ctr_adjust(&TO_OR_CIRCUIT(circ)->introduce2_bucket, + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec); + } SMARTLIST_FOREACH_END(circ); + + smartlist_free(intro_circs); +} + +/** Set consensus parameters. */ +static void +set_consensus_parameters(const networkstatus_t *ns) +{ + consensus_param_introduce_rate_per_sec = + get_intro2_rate_consensus_param(ns); + consensus_param_introduce_burst_per_sec = + get_intro2_burst_consensus_param(ns); + consensus_param_introduce_defense_enabled = + get_intro2_enable_consensus_param(ns); + + /* The above might have changed which means we need to go through all + * introduction circuits (relay side) and update the token buckets. */ + update_intro_circuits(); +} + +/* + * Public API. + */ + +/** Initialize the INTRODUCE2 token bucket for the DoS defenses using the + * consensus/default values. We might get a cell extension that changes those + * later but if we don't, the default or consensus parameters are used. */ +void +hs_dos_setup_default_intro2_defenses(or_circuit_t *circ) +{ + tor_assert(circ); + + circ->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; + token_bucket_ctr_init(&circ->introduce2_bucket, + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec, + (uint32_t) approx_time()); +} + +/** Called when the consensus has changed. We might have new consensus + * parameters to look at. */ +void +hs_dos_consensus_has_changed(const networkstatus_t *ns) +{ + /* No point on updating these values if we are not a public relay that can + * be picked to be an introduction point. */ + if (!public_server_mode(get_options())) { + return; + } + + set_consensus_parameters(ns); +} + +/** Return true iff an INTRODUCE2 cell can be sent on the given service + * introduction circuit. */ +bool +hs_dos_can_send_intro2(or_circuit_t *s_intro_circ) +{ + tor_assert(s_intro_circ); + + /* Allow to send the cell if the DoS defenses are disabled on the circuit. + * This can be set by the consensus, the ESTABLISH_INTRO cell extension or + * the hardcoded values in tor code. */ + if (!s_intro_circ->introduce2_dos_defense_enabled) { + goto allow; + } + + /* Should not happen but if so, scream loudly. */ + if (BUG(TO_CIRCUIT(s_intro_circ)->purpose != CIRCUIT_PURPOSE_INTRO_POINT)) { + goto disallow; + } + + /* This is called just after we got a valid and parsed INTRODUCE1 cell. The + * service has been found and we have its introduction circuit. + * + * First, the INTRODUCE2 bucket will be refilled (if any). Then, decremented + * because we are about to send or not the cell we just got. Finally, + * evaluate if we can send it based on our token bucket state. */ + + /* Refill INTRODUCE2 bucket. */ + token_bucket_ctr_refill(&s_intro_circ->introduce2_bucket, + (uint32_t) approx_time()); + + /* Decrement the bucket for this valid INTRODUCE1 cell we just got. Don't + * underflow else we end up with a too big of a bucket. */ + if (token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0) { + token_bucket_ctr_dec(&s_intro_circ->introduce2_bucket, 1); + } + + /* Finally, we can send a new INTRODUCE2 if there are still tokens. */ + if (token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0) { + goto allow; + } + + /* If we reach this point, then it means the bucket has reached zero, and + we're going to disallow. */ + + disallow: + /* Increment stats counter, we are rejecting the INTRO2 cell. */ + intro2_rejected_count++; + return false; + + allow: + return true; +} + +/** Return rolling count of rejected INTRO2. */ +uint64_t +hs_dos_get_intro2_rejected_count(void) +{ + return intro2_rejected_count; +} + +/** Initialize the onion service Denial of Service subsystem. */ +void +hs_dos_init(void) +{ + set_consensus_parameters(NULL); +} diff --git a/src/feature/hs/hs_dos.h b/src/feature/hs/hs_dos.h new file mode 100644 index 0000000000..8e36ece204 --- /dev/null +++ b/src/feature/hs/hs_dos.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2019-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_dos.h + * \brief Header file containing denial of service defenses for the HS + * subsystem for all versions. + **/ + +#ifndef TOR_HS_DOS_H +#define TOR_HS_DOS_H + +#include "core/or/or_circuit_st.h" + +#include "feature/nodelist/networkstatus_st.h" + +/* Init */ +void hs_dos_init(void); + +/* Consensus. */ +void hs_dos_consensus_has_changed(const networkstatus_t *ns); + +/* Introduction Point. */ +bool hs_dos_can_send_intro2(or_circuit_t *s_intro_circ); +void hs_dos_setup_default_intro2_defenses(or_circuit_t *circ); + +/* Statistics. */ +uint64_t hs_dos_get_intro2_rejected_count(void); + +#ifdef HS_DOS_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC uint32_t get_intro2_enable_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_rate_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_burst_consensus_param(const networkstatus_t *ns); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(HS_DOS_PRIVATE) */ + +#endif /* !defined(TOR_HS_DOS_H) */ diff --git a/src/feature/hs/hs_ident.c b/src/feature/hs/hs_ident.c index 8fd0013941..1d93ff9610 100644 --- a/src/feature/hs/hs_ident.c +++ b/src/feature/hs/hs_ident.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,21 +10,17 @@ #include "lib/crypt_ops/crypto_util.h" #include "feature/hs/hs_ident.h" -/* Return a newly allocated circuit identifier. The given public key is copied +/** Return a newly allocated circuit identifier. The given public key is copied * identity_pk into the identifier. */ hs_ident_circuit_t * -hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, - hs_ident_circuit_type_t circuit_type) +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk) { - tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || - circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); ed25519_pubkey_copy(&ident->identity_pk, identity_pk); - ident->circuit_type = circuit_type; return ident; } -/* Free the given circuit identifier. */ +/** Free the given circuit identifier. */ void hs_ident_circuit_free_(hs_ident_circuit_t *ident) { @@ -35,7 +31,7 @@ hs_ident_circuit_free_(hs_ident_circuit_t *ident) tor_free(ident); } -/* For a given circuit identifier src, return a newly allocated copy of it. +/** For a given circuit identifier src, return a newly allocated copy of it. * This can't fail. */ hs_ident_circuit_t * hs_ident_circuit_dup(const hs_ident_circuit_t *src) @@ -45,7 +41,7 @@ hs_ident_circuit_dup(const hs_ident_circuit_t *src) return ident; } -/* For a given directory connection identifier src, return a newly allocated +/** For a given directory connection identifier src, return a newly allocated * copy of it. This can't fail. */ hs_ident_dir_conn_t * hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) @@ -55,7 +51,7 @@ hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) return ident; } -/* Free the given directory connection identifier. */ +/** Free the given directory connection identifier. */ void hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident) { @@ -66,7 +62,7 @@ hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident) tor_free(ident); } -/* Initialized the allocated ident object with identity_pk and blinded_pk. +/** Initialized the allocated ident object with identity_pk and blinded_pk. * None of them can be NULL since a valid directory connection identifier must * have all fields set. */ void @@ -82,7 +78,7 @@ hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk); } -/* Return a newly allocated edge connection identifier. The given public key +/** Return a newly allocated edge connection identifier. The given public key * identity_pk is copied into the identifier. */ hs_ident_edge_conn_t * hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) @@ -92,7 +88,7 @@ hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) return ident; } -/* Free the given edge connection identifier. */ +/** Free the given edge connection identifier. */ void hs_ident_edge_conn_free_(hs_ident_edge_conn_t *ident) { @@ -103,7 +99,7 @@ hs_ident_edge_conn_free_(hs_ident_edge_conn_t *ident) tor_free(ident); } -/* Return true if the given ident is valid for an introduction circuit. */ +/** Return true if the given ident is valid for an introduction circuit. */ int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) { @@ -124,4 +120,3 @@ hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) invalid: return 0; } - diff --git a/src/feature/hs/hs_ident.h b/src/feature/hs/hs_ident.h index 8c46936a1e..f4b9b2432d 100644 --- a/src/feature/hs/hs_ident.h +++ b/src/feature/hs/hs_ident.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -25,77 +25,71 @@ #include "feature/hs/hs_common.h" -/* Length of the rendezvous cookie that is used to connect circuits at the +/** Length of the rendezvous cookie that is used to connect circuits at the * rendezvous point. */ #define HS_REND_COOKIE_LEN DIGEST_LEN -/* Type of circuit an hs_ident_t object is associated with. */ +/** Type of circuit an hs_ident_t object is associated with. */ typedef enum { HS_IDENT_CIRCUIT_INTRO = 1, HS_IDENT_CIRCUIT_RENDEZVOUS = 2, } hs_ident_circuit_type_t; -/* Client and service side circuit identifier that is used for hidden service +/** Client and service side circuit identifier that is used for hidden service * circuit establishment. Not all fields contain data, it depends on the * circuit purpose. This is attached to an origin_circuit_t. All fields are * used by both client and service. */ typedef struct hs_ident_circuit_t { - /* (All circuit) The public key used to uniquely identify the service. It is + /** (All circuit) The public key used to uniquely identify the service. It is * the one found in the onion address. */ ed25519_public_key_t identity_pk; - /* (All circuit) The type of circuit this identifier is attached to. - * Accessors of the fields in this object assert non fatal on this circuit - * type. In other words, if a rendezvous field is being accessed, the - * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is - * set when an object is initialized in its constructor. */ - hs_ident_circuit_type_t circuit_type; - - /* (All circuit) Introduction point authentication key. It's also needed on + /** (All circuit) Introduction point authentication key. It's also needed on * the rendezvous circuit for the ntor handshake. It's used as the unique key * of the introduction point so it should not be shared between multiple * intro points. */ ed25519_public_key_t intro_auth_pk; - /* (Only client rendezvous circuit) Introduction point encryption public + /** (Only client rendezvous circuit) Introduction point encryption public * key. We keep it in the rendezvous identifier for the ntor handshake. */ curve25519_public_key_t intro_enc_pk; - /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + /** (Only rendezvous circuit) Rendezvous cookie sent from the client to the * service with an INTRODUCE1 cell and used by the service in an * RENDEZVOUS1 cell. */ uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; - /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the + /** (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the * RENDEZVOUS1 cell of the service. The construction is as follows: - * SERVER_PK [32 bytes] - * AUTH_MAC [32 bytes] + * + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] */ uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; - /* (Only client rendezvous circuit) Client ephemeral keypair needed for the + /** (Only client rendezvous circuit) Client ephemeral keypair needed for the * e2e encryption with the service. */ curve25519_keypair_t rendezvous_client_kp; - /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for + /** (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for * the e2e encryption with the client on the circuit. */ uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; - /* (Only rendezvous circuit) Number of streams associated with this + /** (Only rendezvous circuit) Number of streams associated with this * rendezvous circuit. We track this because there is a check on a maximum * value. */ uint64_t num_rdv_streams; } hs_ident_circuit_t; -/* Client and service side directory connection identifier used for a +/** Client and service side directory connection identifier used for a * directory connection to identify which service is being queried. This is * attached to a dir_connection_t. */ typedef struct hs_ident_dir_conn_t { - /* The public key used to uniquely identify the service. It is the one found + /** The public key used to uniquely identify the service. It is the one found * in the onion address. */ ed25519_public_key_t identity_pk; - /* The blinded public key used to uniquely identify the descriptor that this + /** The blinded public key used to uniquely identify the descriptor that this * directory connection identifier is for. Only used by the service-side code * to fine control descriptor uploads. */ ed25519_public_key_t blinded_pk; @@ -103,15 +97,15 @@ typedef struct hs_ident_dir_conn_t { /* XXX: Client authorization. */ } hs_ident_dir_conn_t; -/* Client and service side edge connection identifier used for an edge +/** Client and service side edge connection identifier used for an edge * connection to identify which service is being queried. This is attached to * a edge_connection_t. */ typedef struct hs_ident_edge_conn_t { - /* The public key used to uniquely identify the service. It is the one found + /** The public key used to uniquely identify the service. It is the one found * in the onion address. */ ed25519_public_key_t identity_pk; - /* The original virtual port that was used by the client to access the onion + /** The original virtual port that was used by the client to access the onion * service, regardless of the internal port forwarding that might have * happened on the service-side. */ uint16_t orig_virtual_port; @@ -120,8 +114,7 @@ typedef struct hs_ident_edge_conn_t { /* Circuit identifier API. */ hs_ident_circuit_t *hs_ident_circuit_new( - const ed25519_public_key_t *identity_pk, - hs_ident_circuit_type_t circuit_type); + const ed25519_public_key_t *identity_pk); void hs_ident_circuit_free_(hs_ident_circuit_t *ident); #define hs_ident_circuit_free(id) \ FREE_AND_NULL(hs_ident_circuit_t, hs_ident_circuit_free_, (id)) @@ -147,4 +140,3 @@ void hs_ident_edge_conn_free_(hs_ident_edge_conn_t *ident); int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident); #endif /* !defined(TOR_HS_IDENT_H) */ - diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c index 7717ed53d4..e282d1f1bd 100644 --- a/src/feature/hs/hs_intropoint.c +++ b/src/feature/hs/hs_intropoint.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -10,6 +10,7 @@ #include "core/or/or.h" #include "app/config/config.h" +#include "core/or/channel.h" #include "core/or/circuitlist.h" #include "core/or/circuituse.h" #include "core/or/relay.h" @@ -24,9 +25,11 @@ #include "trunnel/hs/cell_introduce1.h" #include "feature/hs/hs_circuitmap.h" +#include "feature/hs/hs_common.h" +#include "feature/hs/hs_config.h" #include "feature/hs/hs_descriptor.h" +#include "feature/hs/hs_dos.h" #include "feature/hs/hs_intropoint.h" -#include "feature/hs/hs_common.h" #include "core/or/or_circuit_st.h" @@ -144,7 +147,7 @@ verify_establish_intro_cell(const trn_cell_establish_intro_t *cell, return 0; } -/* Send an INTRO_ESTABLISHED cell to <b>circ</b>. */ +/** Send an INTRO_ESTABLISHED cell to <b>circ</b>. */ MOCK_IMPL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)) { @@ -179,6 +182,185 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) return ret; } +/** Validate the cell DoS extension parameters. Return true iff they've been + * bound check and can be used. Else return false. See proposal 305 for + * details and reasons about this validation. */ +STATIC bool +cell_dos_extension_parameters_are_valid(uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec) +{ + bool ret = false; + + /* Check that received value is not below the minimum. Don't check if minimum + is set to 0, since the param is a positive value and gcc will complain. */ +#if HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN > 0 + if (intro2_rate_per_sec < HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too small. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } +#endif /* HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN > 0 */ + + /* Check that received value is not above maximum */ + if (intro2_rate_per_sec > HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too big. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } + + /* Check that received value is not below the minimum */ +#if HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN > 0 + if (intro2_burst_per_sec < HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too small. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } +#endif /* HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN > 0 */ + + /* Check that received value is not above maximum */ + if (intro2_burst_per_sec > HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too big. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } + + /* In a rate limiting scenario, burst can never be smaller than the rate. At + * best it can be equal. */ + if (intro2_burst_per_sec < intro2_rate_per_sec) { + log_info(LD_REND, "Intro point DoS defenses burst is smaller than rate. " + "Rate: %" PRIu64 " vs Burst: %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + goto end; + } + + /* Passing validation. */ + ret = true; + + end: + return ret; +} + +/** Parse the cell DoS extension and apply defenses on the given circuit if + * validation passes. If the cell extension is malformed or contains unusable + * values, the DoS defenses is disabled on the circuit. */ +static void +handle_establish_intro_cell_dos_extension( + const trn_cell_extension_field_t *field, + or_circuit_t *circ) +{ + ssize_t ret; + uint64_t intro2_rate_per_sec = 0, intro2_burst_per_sec = 0; + trn_cell_extension_dos_t *dos = NULL; + + tor_assert(field); + tor_assert(circ); + + ret = trn_cell_extension_dos_parse(&dos, + trn_cell_extension_field_getconstarray_field(field), + trn_cell_extension_field_getlen_field(field)); + if (ret < 0) { + goto end; + } + + for (size_t i = 0; i < trn_cell_extension_dos_get_n_params(dos); i++) { + const trn_cell_extension_dos_param_t *param = + trn_cell_extension_dos_getconst_params(dos, i); + if (BUG(param == NULL)) { + goto end; + } + + switch (trn_cell_extension_dos_param_get_type(param)) { + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC: + intro2_rate_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC: + intro2_burst_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + default: + goto end; + } + } + + /* A value of 0 is valid in the sense that we accept it but we still disable + * the defenses so return false. */ + if (intro2_rate_per_sec == 0 || intro2_burst_per_sec == 0) { + log_info(LD_REND, "Intro point DoS defenses parameter set to 0. " + "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + circ->introduce2_dos_defense_enabled = 0; + goto end; + } + + /* If invalid, we disable the defense on the circuit. */ + if (!cell_dos_extension_parameters_are_valid(intro2_rate_per_sec, + intro2_burst_per_sec)) { + circ->introduce2_dos_defense_enabled = 0; + log_info(LD_REND, "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + goto end; + } + + /* We passed validation, enable defenses and apply rate/burst. */ + circ->introduce2_dos_defense_enabled = 1; + + /* Initialize the INTRODUCE2 token bucket for the rate limiting. */ + token_bucket_ctr_init(&circ->introduce2_bucket, + (uint32_t) intro2_rate_per_sec, + (uint32_t) intro2_burst_per_sec, + (uint32_t) approx_time()); + log_info(LD_REND, "Intro point DoS defenses enabled. Rate is %" PRIu64 + " and Burst is %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + + end: + trn_cell_extension_dos_free(dos); + return; +} + +/** Parse every cell extension in the given ESTABLISH_INTRO cell. */ +static void +handle_establish_intro_cell_extensions( + const trn_cell_establish_intro_t *parsed_cell, + or_circuit_t *circ) +{ + const trn_cell_extension_t *extensions; + + tor_assert(parsed_cell); + tor_assert(circ); + + extensions = trn_cell_establish_intro_getconst_extensions(parsed_cell); + if (extensions == NULL) { + goto end; + } + + /* Go over all extensions. */ + for (size_t idx = 0; idx < trn_cell_extension_get_num(extensions); idx++) { + const trn_cell_extension_field_t *field = + trn_cell_extension_getconst_fields(extensions, idx); + if (BUG(field == NULL)) { + /* The number of extensions should match the number of fields. */ + break; + } + + switch (trn_cell_extension_field_get_field_type(field)) { + case TRUNNEL_CELL_EXTENSION_TYPE_DOS: + /* After this, the circuit should be set for DoS defenses. */ + handle_establish_intro_cell_dos_extension(field, circ); + break; + default: + /* Unknown extension. Skip over. */ + break; + } + } + + end: + return; +} + /** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's * well-formed and passed our verifications. Perform appropriate actions to * establish an intro point. */ @@ -191,6 +373,13 @@ handle_verified_establish_intro_cell(or_circuit_t *circ, get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, parsed_cell); + /* Setup INTRODUCE2 defenses on the circuit. Must be done before parsing the + * cell extension that can possibly change the defenses' values. */ + hs_dos_setup_default_intro2_defenses(circ); + + /* Handle cell extension if any. */ + handle_establish_intro_cell_extensions(parsed_cell, circ); + /* Then notify the hidden service that the intro point is established by sending an INTRO_ESTABLISHED cell */ if (hs_intro_send_intro_established_cell(circ)) { @@ -268,7 +457,7 @@ handle_establish_intro(or_circuit_t *circ, const uint8_t *request, return retval; } -/* Return True if circuit is suitable for being an intro circuit. */ +/** Return True if circuit is suitable for being an intro circuit. */ static int circuit_is_suitable_intro_point(const or_circuit_t *circ, const char *log_cell_type_str) @@ -293,14 +482,14 @@ circuit_is_suitable_intro_point(const or_circuit_t *circ, return 1; } -/* Return True if circuit is suitable for being service-side intro circuit. */ +/** Return True if circuit is suitable for being service-side intro circuit. */ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ) { return circuit_is_suitable_intro_point(circ, "ESTABLISH_INTRO"); } -/* We just received an ESTABLISH_INTRO cell in <b>circ</b>. Figure out of it's +/** We just received an ESTABLISH_INTRO cell in <b>circ</b>. Figure out of it's * a legacy or a next gen cell, and pass it to the appropriate handler. */ int hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request, @@ -334,7 +523,7 @@ hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request, return -1; } -/* Send an INTRODUCE_ACK cell onto the circuit <b>circ</b> with the status +/** Send an INTRODUCE_ACK cell onto the circuit <b>circ</b> with the status * value in <b>status</b>. Depending on the status, it can be ACK or a NACK. * Return 0 on success else a negative value on error which will close the * circuit. */ @@ -378,7 +567,7 @@ send_introduce_ack_cell(or_circuit_t *circ, uint16_t status) return ret; } -/* Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a +/** Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a * negative value for an invalid cell that should be NACKed. */ STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell) @@ -392,7 +581,7 @@ validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell) * safety net here. The legacy ID must be zeroes in this case. */ legacy_key_id_len = trn_cell_introduce1_getlen_legacy_key_id(cell); legacy_key_id = trn_cell_introduce1_getconstarray_legacy_key_id(cell); - if (BUG(!tor_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) { + if (BUG(!fast_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) { goto invalid; } @@ -424,7 +613,7 @@ validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell) return -1; } -/* We just received a non legacy INTRODUCE1 cell on <b>client_circ</b> with +/** We just received a non legacy INTRODUCE1 cell on <b>client_circ</b> with * the payload in <b>request</b> of size <b>request_len</b>. Return 0 if * everything went well, or -1 if an error occurred. This function is in charge * of sending back an INTRODUCE_ACK cell and will close client_circ on error. @@ -480,6 +669,20 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, } } + /* Before sending, lets make sure this cell can be sent on the service + * circuit asking the DoS defenses. */ + if (!hs_dos_can_send_intro2(service_circ)) { + char *msg; + static ratelim_t rlimit = RATELIM_INIT(5 * 60); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_info(LD_PROTOCOL, "Can't relay INTRODUCE1 v3 cell due to DoS " + "limitations. Sending NACK to client."); + tor_free(msg); + } + status = TRUNNEL_HS_INTRO_ACK_STATUS_UNKNOWN_ID; + goto send_ack; + } + /* Relay the cell to the service on its intro circuit with an INTRODUCE2 * cell which is the same exact payload. */ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ), @@ -509,7 +712,7 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, return ret; } -/* Identify if the encoded cell we just received is a legacy one or not. The +/** Identify if the encoded cell we just received is a legacy one or not. The * <b>request</b> should be at least DIGEST_LEN bytes long. */ STATIC int introduce1_cell_is_legacy(const uint8_t *request) @@ -518,7 +721,7 @@ introduce1_cell_is_legacy(const uint8_t *request) /* If the first 20 bytes of the cell (DIGEST_LEN) are NOT zeroes, it * indicates a legacy cell (v2). */ - if (!tor_mem_is_zero((const char *) request, DIGEST_LEN)) { + if (!fast_mem_is_zero((const char *) request, DIGEST_LEN)) { /* Legacy cell. */ return 1; } @@ -526,7 +729,7 @@ introduce1_cell_is_legacy(const uint8_t *request) return 0; } -/* Return true iff the circuit <b>circ</b> is suitable for receiving an +/** Return true iff the circuit <b>circ</b> is suitable for receiving an * INTRODUCE1 cell. */ STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ) @@ -546,10 +749,18 @@ circuit_is_suitable_for_introduce1(const or_circuit_t *circ) return 0; } + /* Disallow single hop client circuit. */ + if (circ->p_chan && channel_is_client(circ->p_chan)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Single hop client was rejected while trying to introduce. " + "Closing circuit."); + return 0; + } + return 1; } -/* We just received an INTRODUCE1 cell on <b>circ</b>. Figure out which type +/** We just received an INTRODUCE1 cell on <b>circ</b>. Figure out which type * it is and pass it to the appropriate handler. Return 0 on success else a * negative value and the circuit is closed. */ int @@ -593,8 +804,8 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } -/* Clear memory allocated by the given intropoint object ip (but don't free the - * object itself). */ +/** Clear memory allocated by the given intropoint object ip (but don't free + * the object itself). */ void hs_intropoint_clear(hs_intropoint_t *ip) { @@ -602,8 +813,8 @@ hs_intropoint_clear(hs_intropoint_t *ip) return; } tor_cert_free(ip->auth_key_cert); - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, - hs_desc_link_specifier_free(ls)); + SMARTLIST_FOREACH(ip->link_specifiers, link_specifier_t *, ls, + link_specifier_free(ls)); smartlist_free(ip->link_specifiers); memset(ip, 0, sizeof(hs_intropoint_t)); } diff --git a/src/feature/hs/hs_intropoint.h b/src/feature/hs/hs_intropoint.h index e82575f052..8b2b9892b3 100644 --- a/src/feature/hs/hs_intropoint.h +++ b/src/feature/hs/hs_intropoint.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,15 +12,15 @@ #include "lib/crypt_ops/crypto_curve25519.h" #include "feature/nodelist/torcert.h" -/* Object containing introduction point common data between the service and +/** Object containing introduction point common data between the service and * the client side. */ typedef struct hs_intropoint_t { - /* Does this intro point only supports legacy ID ?. */ + /** Does this intro point only supports legacy ID ?. */ unsigned int is_only_legacy : 1; - /* Authentication key certificate from the descriptor. */ + /** Authentication key certificate from the descriptor. */ tor_cert_t *auth_key_cert; - /* A list of link specifier. */ + /** A list of link specifier. */ smartlist_t *link_specifiers; } hs_intropoint_t; @@ -57,8 +57,10 @@ STATIC int handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, size_t request_len); STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); +STATIC bool cell_dos_extension_parameters_are_valid( + uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec); #endif /* defined(HS_INTROPOINT_PRIVATE) */ #endif /* !defined(TOR_HS_INTRO_H) */ - diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index e820ce9d0b..54bc572d11 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -31,7 +31,6 @@ #include "feature/rend/rendservice.h" #include "lib/crypt_ops/crypto_ope.h" #include "lib/crypt_ops/crypto_rand.h" -#include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/hs/hs_circuit.h" @@ -68,7 +67,8 @@ #include <unistd.h> #endif -/* Helper macro. Iterate over every service in the global map. The var is the +#ifndef COCCI +/** Helper macro. Iterate over every service in the global map. The var is the * name of the service pointer. */ #define FOR_EACH_SERVICE_BEGIN(var) \ STMT_BEGIN \ @@ -77,7 +77,7 @@ var = *var##_iter; #define FOR_EACH_SERVICE_END } STMT_END ; -/* Helper macro. Iterate over both current and previous descriptor of a +/** Helper macro. Iterate over both current and previous descriptor of a * service. The var is the name of the descriptor pointer. This macro skips * any descriptor object of the service that is NULL. */ #define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \ @@ -89,6 +89,7 @@ (var = service->desc_next); \ if (var == NULL) continue; #define FOR_EACH_DESCRIPTOR_END } STMT_END ; +#endif /* !defined(COCCI) */ /* Onion service directory file names. */ static const char fname_keyfile_prefix[] = "hs_ed25519"; @@ -96,7 +97,7 @@ static const char dname_client_pubkeys[] = "authorized_clients"; static const char fname_hostname[] = "hostname"; static const char address_tld[] = "onion"; -/* Staging list of service object. When configuring service, we add them to +/** Staging list of service object. When configuring service, we add them to * this list considered a staging area and they will get added to our global * map once the keys have been loaded. These two steps are separated because * loading keys requires that we are an actual running tor process. */ @@ -119,7 +120,7 @@ static int service_encode_descriptor(const hs_service_t *service, const ed25519_keypair_t *signing_kp, char **encoded_out); -/* Helper: Function to compare two objects in the service map. Return 1 if the +/** Helper: Function to compare two objects in the service map. Return 1 if the * two service have the same master public identity key. */ static inline int hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second) @@ -131,7 +132,7 @@ hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second) &second->keys.identity_pk); } -/* Helper: Function for the service hash table code below. The key used is the +/** Helper: Function for the service hash table code below. The key used is the * master public identity key which is ultimately the onion address. */ static inline unsigned int hs_service_ht_hash(const hs_service_t *service) @@ -141,7 +142,7 @@ hs_service_ht_hash(const hs_service_t *service) sizeof(service->keys.identity_pk.pubkey)); } -/* This is _the_ global hash map of hidden services which indexed the service +/** This is _the_ global hash map of hidden services which indexed the service * contained in it by master public identity key which is roughly the onion * address of the service. */ static struct hs_service_ht *hs_service_map; @@ -157,7 +158,7 @@ HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node, hs_service_ht_hash, hs_service_ht_eq, 0.6, tor_reallocarray, tor_free_) -/* Query the given service map with a public key and return a service object +/** Query the given service map with a public key and return a service object * if found else NULL. It is also possible to set a directory path in the * search query. If pk is NULL, then it will be set to zero indicating the * hash table to compare the directory path instead. */ @@ -172,7 +173,7 @@ find_service(hs_service_ht *map, const ed25519_public_key_t *pk) return HT_FIND(hs_service_ht, map, &dummy_service); } -/* Register the given service in the given map. If the service already exists +/** Register the given service in the given map. If the service already exists * in the map, -1 is returned. On success, 0 is returned and the service * ownership has been transferred to the global map. */ STATIC int @@ -197,7 +198,7 @@ register_service(hs_service_ht *map, hs_service_t *service) return 0; } -/* Remove a given service from the given map. If service is NULL or the +/** Remove a given service from the given map. If service is NULL or the * service key is unset, return gracefully. */ STATIC void remove_service(hs_service_ht *map, hs_service_t *service) @@ -227,7 +228,7 @@ remove_service(hs_service_ht *map, hs_service_t *service) } } -/* Set the default values for a service configuration object <b>c</b>. */ +/** Set the default values for a service configuration object <b>c</b>. */ static void set_service_default_config(hs_service_config_t *c, const or_options_t *options) @@ -243,9 +244,12 @@ set_service_default_config(hs_service_config_t *c, c->is_single_onion = 0; c->dir_group_readable = 0; c->is_ephemeral = 0; + c->has_dos_defense_enabled = HS_CONFIG_V3_DOS_DEFENSE_DEFAULT; + c->intro_dos_rate_per_sec = HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT; + c->intro_dos_burst_per_sec = HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT; } -/* From a service configuration object config, clear everything from it +/** From a service configuration object config, clear everything from it * meaning free allocated pointers and reset the values. */ STATIC void service_clear_config(hs_service_config_t *config) @@ -267,7 +271,7 @@ service_clear_config(hs_service_config_t *config) memset(config, 0, sizeof(*config)); } -/* Helper function to return a human readable description of the given intro +/** Helper function to return a human readable description of the given intro * point object. * * This function is not thread-safe. Each call to this invalidates the @@ -281,9 +285,10 @@ describe_intro_point(const hs_service_intro_point_t *ip) const char *legacy_id = NULL; SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, - const hs_desc_link_specifier_t *, lspec) { - if (lspec->type == LS_LEGACY_ID) { - legacy_id = (const char *) lspec->u.legacy_id; + const link_specifier_t *, lspec) { + if (link_specifier_get_ls_type(lspec) == LS_LEGACY_ID) { + legacy_id = (const char *) + link_specifier_getconstarray_un_legacy_id(lspec); break; } } SMARTLIST_FOREACH_END(lspec); @@ -298,7 +303,7 @@ describe_intro_point(const hs_service_intro_point_t *ip) return buf; } -/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we +/** Return the lower bound of maximum INTRODUCE2 cells per circuit before we * rotate intro point (defined by a consensus parameter or the default * value). */ static int32_t @@ -311,7 +316,7 @@ get_intro_point_min_introduce2(void) 0, INT32_MAX); } -/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we +/** Return the upper bound of maximum INTRODUCE2 cells per circuit before we * rotate intro point (defined by a consensus parameter or the default * value). */ static int32_t @@ -324,8 +329,8 @@ get_intro_point_max_introduce2(void) 0, INT32_MAX); } -/* Return the minimum lifetime in seconds of an introduction point defined by a - * consensus parameter or the default value. */ +/** Return the minimum lifetime in seconds of an introduction point defined by + * a consensus parameter or the default value. */ static int32_t get_intro_point_min_lifetime(void) { @@ -341,8 +346,8 @@ get_intro_point_min_lifetime(void) 0, INT32_MAX); } -/* Return the maximum lifetime in seconds of an introduction point defined by a - * consensus parameter or the default value. */ +/** Return the maximum lifetime in seconds of an introduction point defined by + * a consensus parameter or the default value. */ static int32_t get_intro_point_max_lifetime(void) { @@ -358,7 +363,7 @@ get_intro_point_max_lifetime(void) 0, INT32_MAX); } -/* Return the number of extra introduction point defined by a consensus +/** Return the number of extra introduction point defined by a consensus * parameter or the default value. */ static int32_t get_intro_point_num_extra(void) @@ -369,7 +374,7 @@ get_intro_point_num_extra(void) NUM_INTRO_POINTS_EXTRA, 0, 128); } -/* Helper: Function that needs to return 1 for the HT for each loop which +/** Helper: Function that needs to return 1 for the HT for each loop which * frees every service in an hash map. */ static int ht_free_service_(struct hs_service_t *service, void *data) @@ -381,7 +386,7 @@ ht_free_service_(struct hs_service_t *service, void *data) return 1; } -/* Free every service that can be found in the global map. Once done, clear +/** Free every service that can be found in the global map. Once done, clear * and free the global map. */ static void service_free_all(void) @@ -403,7 +408,7 @@ service_free_all(void) } } -/* Free a given service intro point object. */ +/** Free a given service intro point object. */ STATIC void service_intro_point_free_(hs_service_intro_point_t *ip) { @@ -418,7 +423,7 @@ service_intro_point_free_(hs_service_intro_point_t *ip) tor_free(ip); } -/* Helper: free an hs_service_intro_point_t object. This function is used by +/** Helper: free an hs_service_intro_point_t object. This function is used by * digest256map_free() which requires a void * pointer. */ static void service_intro_point_free_void(void *obj) @@ -426,24 +431,17 @@ service_intro_point_free_void(void *obj) service_intro_point_free_(obj); } -/* Return a newly allocated service intro point and fully initialized from the - * given extend_info_t ei if non NULL. - * If is_legacy is true, we also generate the legacy key. - * If supports_ed25519_link_handshake_any is true, we add the relay's ed25519 - * key to the link specifiers. +/** Return a newly allocated service intro point and fully initialized from the + * given node_t node, if non NULL. * - * If ei is NULL, returns a hs_service_intro_point_t with an empty link + * If node is NULL, returns a hs_service_intro_point_t with an empty link * specifier list and no onion key. (This is used for testing.) * On any other error, NULL is returned. * - * ei must be an extend_info_t containing an IPv4 address. (We will add supoort - * for IPv6 in a later release.) When calling extend_info_from_node(), pass - * 0 in for_direct_connection to make sure ei always has an IPv4 address. */ + * node must be an node_t with an IPv4 address. */ STATIC hs_service_intro_point_t * -service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy, - unsigned int supports_ed25519_link_handshake_any) +service_intro_point_new(const node_t *node) { - hs_desc_link_specifier_t *ls; hs_service_intro_point_t *ip; ip = tor_malloc_zero(sizeof(*ip)); @@ -473,12 +471,17 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy, ip->replay_cache = replaycache_new(0, 0); /* Initialize the base object. We don't need the certificate object. */ - ip->base.link_specifiers = smartlist_new(); + ip->base.link_specifiers = node_get_link_specifier_smartlist(node, 0); + + if (node == NULL) { + goto done; + } /* Generate the encryption key for this intro point. */ curve25519_keypair_generate(&ip->enc_key_kp, 0); - /* Figure out if this chosen node supports v3 or is legacy only. */ - if (is_legacy) { + /* Figure out if this chosen node supports v3 or is legacy only. + * NULL nodes are used in the unit tests. */ + if (!node_supports_ed25519_hs_intro(node)) { ip->base.is_only_legacy = 1; /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */ ip->legacy_key = crypto_pk_new(); @@ -491,40 +494,13 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy, } } - if (ei == NULL) { - goto done; - } + /* Flag if this intro point supports the INTRO2 dos defenses. */ + ip->support_intro2_dos_defense = + node_supports_establish_intro_dos_extension(node); - /* We'll try to add all link specifiers. Legacy is mandatory. - * IPv4 or IPv6 is required, and we always send IPv4. */ - ls = hs_desc_link_specifier_new(ei, LS_IPV4); - /* It is impossible to have an extend info object without a v4. */ - if (BUG(!ls)) { - goto err; - } - smartlist_add(ip->base.link_specifiers, ls); - - ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID); - /* It is impossible to have an extend info object without an identity - * digest. */ - if (BUG(!ls)) { - goto err; - } - smartlist_add(ip->base.link_specifiers, ls); - - /* ed25519 identity key is optional for intro points. If the node supports - * ed25519 link authentication, we include it. */ - if (supports_ed25519_link_handshake_any) { - ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); - if (ls) { - smartlist_add(ip->base.link_specifiers, ls); - } - } - - /* IPv6 is not supported in this release. */ - - /* Finally, copy onion key from the extend_info_t object. */ - memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); + /* Finally, copy onion key from the node. */ + memcpy(&ip->onion_key, node_get_curve25519_onion_key(node), + sizeof(ip->onion_key)); done: return ip; @@ -533,7 +509,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy, return NULL; } -/* Add the given intro point object to the given intro point map. The intro +/** Add the given intro point object to the given intro point map. The intro * point MUST have its RSA encryption key set if this is a legacy type or the * authentication key set otherwise. */ STATIC void @@ -549,7 +525,7 @@ service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) tor_assert_nonfatal(!old_ip_entry); } -/* For a given service, remove the intro point from that service's descriptors +/** For a given service, remove the intro point from that service's descriptors * (check both current and next descriptor) */ STATIC void service_intro_point_remove(const hs_service_t *service, @@ -567,7 +543,7 @@ service_intro_point_remove(const hs_service_t *service, } FOR_EACH_DESCRIPTOR_END; } -/* For a given service and authentication key, return the intro point or NULL +/** For a given service and authentication key, return the intro point or NULL * if not found. This will check both descriptors in the service. */ STATIC hs_service_intro_point_t * service_intro_point_find(const hs_service_t *service, @@ -598,7 +574,7 @@ service_intro_point_find(const hs_service_t *service, return ip; } -/* For a given service and intro point, return the descriptor for which the +/** For a given service and intro point, return the descriptor for which the * intro point is assigned to. NULL is returned if not found. */ STATIC hs_service_descriptor_t * service_desc_find_by_intro(const hs_service_t *service, @@ -620,7 +596,7 @@ service_desc_find_by_intro(const hs_service_t *service, return descp; } -/* From a circuit identifier, get all the possible objects associated with the +/** From a circuit identifier, get all the possible objects associated with the * ident. If not NULL, service, ip or desc are set if the object can be found. * They are untouched if they can't be found. * @@ -653,20 +629,20 @@ get_objects_from_ident(const hs_ident_circuit_t *ident, } } -/* From a given intro point, return the first link specifier of type +/** From a given intro point, return the first link specifier of type * encountered in the link specifier list. Return NULL if it can't be found. * * The caller does NOT have ownership of the object, the intro point does. */ -static hs_desc_link_specifier_t * +static link_specifier_t * get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) { - hs_desc_link_specifier_t *lnk_spec = NULL; + link_specifier_t *lnk_spec = NULL; tor_assert(ip); SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, - hs_desc_link_specifier_t *, ls) { - if (ls->type == type) { + link_specifier_t *, ls) { + if (link_specifier_get_ls_type(ls) == type) { lnk_spec = ls; goto end; } @@ -676,13 +652,13 @@ get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) return lnk_spec; } -/* Given a service intro point, return the node_t associated to it. This can +/** Given a service intro point, return the node_t associated to it. This can * return NULL if the given intro point has no legacy ID or if the node can't * be found in the consensus. */ STATIC const node_t * get_node_from_intro_point(const hs_service_intro_point_t *ip) { - const hs_desc_link_specifier_t *ls; + const link_specifier_t *ls; tor_assert(ip); @@ -691,10 +667,11 @@ get_node_from_intro_point(const hs_service_intro_point_t *ip) return NULL; } /* XXX In the future, we want to only use the ed25519 ID (#22173). */ - return node_get_by_id((const char *) ls->u.legacy_id); + return node_get_by_id( + (const char *) link_specifier_getconstarray_un_legacy_id(ls)); } -/* Given a service intro point, return the extend_info_t for it. This can +/** Given a service intro point, return the extend_info_t for it. This can * return NULL if the node can't be found for the intro point or the extend * info can't be created for the found node. If direct_conn is set, the extend * info is validated on if we can connect directly. */ @@ -723,7 +700,7 @@ get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, return info; } -/* Return the number of introduction points that are established for the +/** Return the number of introduction points that are established for the * given descriptor. */ static unsigned int count_desc_circuit_established(const hs_service_descriptor_t *desc) @@ -734,13 +711,13 @@ count_desc_circuit_established(const hs_service_descriptor_t *desc) DIGEST256MAP_FOREACH(desc->intro_points.map, key, const hs_service_intro_point_t *, ip) { - count += ip->circuit_established; + count += !!hs_circ_service_get_established_intro_circ(ip); } DIGEST256MAP_FOREACH_END; return count; } -/* For a given service and descriptor of that service, close all active +/** For a given service and descriptor of that service, close all active * directory connections. */ static void close_directory_connections(const hs_service_t *service, @@ -775,7 +752,7 @@ close_directory_connections(const hs_service_t *service, smartlist_free(dir_conns); } -/* Close all rendezvous circuits for the given service. */ +/** Close all rendezvous circuits for the given service. */ static void close_service_rp_circuits(hs_service_t *service) { @@ -805,7 +782,7 @@ close_service_rp_circuits(hs_service_t *service) } } -/* Close the circuit(s) for the given map of introduction points. */ +/** Close the circuit(s) for the given map of introduction points. */ static void close_intro_circuits(hs_service_intropoints_t *intro_points) { @@ -823,7 +800,7 @@ close_intro_circuits(hs_service_intropoints_t *intro_points) } DIGEST256MAP_FOREACH_END; } -/* Close all introduction circuits for the given service. */ +/** Close all introduction circuits for the given service. */ static void close_service_intro_circuits(hs_service_t *service) { @@ -834,7 +811,7 @@ close_service_intro_circuits(hs_service_t *service) } FOR_EACH_DESCRIPTOR_END; } -/* Close any circuits related to the given service. */ +/** Close any circuits related to the given service. */ static void close_service_circuits(hs_service_t *service) { @@ -850,7 +827,7 @@ close_service_circuits(hs_service_t *service) close_service_rp_circuits(service); } -/* Move every ephemeral services from the src service map to the dst service +/** Move every ephemeral services from the src service map to the dst service * map. It is possible that a service can't be register to the dst map which * won't stop the process of moving them all but will trigger a log warn. */ static void @@ -880,7 +857,7 @@ move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst) } } -/* Return a const string of the directory path escaped. If this is an +/** Return a const string of the directory path escaped. If this is an * ephemeral service, it returns "[EPHEMERAL]". This can only be called from * the main thread because escaped() uses a static variable. */ static const char * @@ -914,7 +891,7 @@ move_hs_state(hs_service_t *src_service, hs_service_t *dst_service) src->replay_cache_rend_cookie = NULL; /* steal pointer reference */ } -/* Register services that are in the staging list. Once this function returns, +/** Register services that are in the staging list. Once this function returns, * the global service map will be set with the right content and all non * surviving services will be cleaned up. */ static void @@ -982,7 +959,7 @@ register_all_services(void) hs_service_map_has_changed(); } -/* Write the onion address of a given service to the given filename fname_ in +/** Write the onion address of a given service to the given filename fname_ in * the service directory. Return 0 on success else -1 on error. */ STATIC int write_address_to_file(const hs_service_t *service, const char *fname_) @@ -1023,7 +1000,7 @@ write_address_to_file(const hs_service_t *service, const char *fname_) return ret; } -/* Load and/or generate private keys for the given service. On success, the +/** Load and/or generate private keys for the given service. On success, the * hostname file will be written to disk along with the master private key iff * the service is not configured for offline keys. Return 0 on success else -1 * on failure. */ @@ -1099,7 +1076,7 @@ load_service_keys(hs_service_t *service) return ret; } -/* Check if the client file name is valid or not. Return 1 if valid, +/** Check if the client file name is valid or not. Return 1 if valid, * otherwise return 0. */ STATIC int client_filename_is_valid(const char *filename) @@ -1121,7 +1098,7 @@ client_filename_is_valid(const char *filename) return ret; } -/* Parse an authorized client from a string. The format of a client string +/** Parse an authorized client from a string. The format of a client string * looks like (see rend-spec-v3.txt): * * <auth-type>:<key-type>:<base32-encoded-public-key> @@ -1180,7 +1157,8 @@ parse_authorized_client(const char *client_key_str) client = tor_malloc_zero(sizeof(hs_service_authorized_client_t)); if (base32_decode((char *) client->client_pk.public_key, sizeof(client->client_pk.public_key), - pubkey_b32, strlen(pubkey_b32)) < 0) { + pubkey_b32, strlen(pubkey_b32)) != + sizeof(client->client_pk.public_key)) { log_warn(LD_REND, "Client authorization public key cannot be decoded: %s", pubkey_b32); goto err; @@ -1202,7 +1180,7 @@ parse_authorized_client(const char *client_key_str) return client; } -/* Load all the client public keys for the given service. Return 0 on +/** Load all the client public keys for the given service. Return 0 on * success else -1 on failure. */ static int load_client_keys(hs_service_t *service) @@ -1262,7 +1240,7 @@ load_client_keys(hs_service_t *service) client_key_str = read_file_to_str(client_key_file_path, 0, NULL); /* If we cannot read the file, continue with the next file. */ - if (!client_key_str) { + if (!client_key_str) { log_warn(LD_REND, "Client authorization file %s can't be read. " "Corrupted or verify permission? Ignoring.", client_key_file_path); @@ -1305,6 +1283,7 @@ load_client_keys(hs_service_t *service) return ret; } +/** Release all storage held in <b>client</b>. */ STATIC void service_authorized_client_free_(hs_service_authorized_client_t *client) { @@ -1315,7 +1294,7 @@ service_authorized_client_free_(hs_service_authorized_client_t *client) tor_free(client); } -/* Free a given service descriptor object and all key material is wiped. */ +/** Free a given service descriptor object and all key material is wiped. */ STATIC void service_descriptor_free_(hs_service_descriptor_t *desc) { @@ -1336,7 +1315,7 @@ service_descriptor_free_(hs_service_descriptor_t *desc) tor_free(desc); } -/* Return a newly allocated service descriptor object. */ +/** Return a newly allocated service descriptor object. */ STATIC hs_service_descriptor_t * service_descriptor_new(void) { @@ -1349,7 +1328,7 @@ service_descriptor_new(void) return sdesc; } -/* Allocate and return a deep copy of client. */ +/** Allocate and return a deep copy of client. */ static hs_service_authorized_client_t * service_authorized_client_dup(const hs_service_authorized_client_t *client) { @@ -1367,7 +1346,7 @@ service_authorized_client_dup(const hs_service_authorized_client_t *client) return client_dup; } -/* If two authorized clients are equal, return 0. If the first one should come +/** If two authorized clients are equal, return 0. If the first one should come * before the second, return less than zero. If the first should come after * the second, return greater than zero. */ static int @@ -1384,7 +1363,7 @@ service_authorized_client_cmp(const hs_service_authorized_client_t *client1, CURVE25519_PUBKEY_LEN); } -/* Helper for sorting authorized clients. */ +/** Helper for sorting authorized clients. */ static int compare_service_authorzized_client_(const void **_a, const void **_b) { @@ -1392,7 +1371,7 @@ compare_service_authorzized_client_(const void **_a, const void **_b) return service_authorized_client_cmp(a, b); } -/* If the list of hs_service_authorized_client_t's is different between +/** If the list of hs_service_authorized_client_t's is different between * src and dst, return 1. Otherwise, return 0. */ STATIC int service_authorized_client_config_equal(const hs_service_config_t *config1, @@ -1453,7 +1432,7 @@ service_authorized_client_config_equal(const hs_service_config_t *config1, return ret; } -/* Move descriptor(s) from the src service to the dst service and modify their +/** Move descriptor(s) from the src service to the dst service and modify their * content if necessary. We do this during SIGHUP when we re-create our * hidden services. */ static void @@ -1512,7 +1491,7 @@ move_descriptors(hs_service_t *src, hs_service_t *dst) service_descriptor_free(dst->desc_next); } -/* From the given service, remove all expired failing intro points for each +/** From the given service, remove all expired failing intro points for each * descriptor. */ static void remove_expired_failing_intro(hs_service_t *service, time_t now) @@ -1531,7 +1510,7 @@ remove_expired_failing_intro(hs_service_t *service, time_t now) } FOR_EACH_DESCRIPTOR_END; } -/* For the given descriptor desc, put all node_t object found from its failing +/** For the given descriptor desc, put all node_t object found from its failing * intro point list and put them in the given node_list. */ static void setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, @@ -1549,7 +1528,7 @@ setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, } DIGESTMAP_FOREACH_END; } -/* For the given failing intro point ip, we add its time of failure to the +/** For the given failing intro point ip, we add its time of failure to the * failed map and index it by identity digest (legacy ID) in the descriptor * desc failed id map. */ static void @@ -1557,7 +1536,7 @@ remember_failing_intro_point(const hs_service_intro_point_t *ip, hs_service_descriptor_t *desc, time_t now) { time_t *time_of_failure, *prev_ptr; - const hs_desc_link_specifier_t *legacy_ls; + const link_specifier_t *legacy_ls; tor_assert(ip); tor_assert(desc); @@ -1566,23 +1545,14 @@ remember_failing_intro_point(const hs_service_intro_point_t *ip, *time_of_failure = now; legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); tor_assert(legacy_ls); - prev_ptr = digestmap_set(desc->intro_points.failed_id, - (const char *) legacy_ls->u.legacy_id, - time_of_failure); + prev_ptr = digestmap_set( + desc->intro_points.failed_id, + (const char *) link_specifier_getconstarray_un_legacy_id(legacy_ls), + time_of_failure); tor_free(prev_ptr); } -/* Copy the descriptor link specifier object from src to dst. */ -static void -link_specifier_copy(hs_desc_link_specifier_t *dst, - const hs_desc_link_specifier_t *src) -{ - tor_assert(dst); - tor_assert(src); - memcpy(dst, src, sizeof(hs_desc_link_specifier_t)); -} - -/* Using a given descriptor signing keypair signing_kp, a service intro point +/** Using a given descriptor signing keypair signing_kp, a service intro point * object ip and the time now, setup the content of an already allocated * descriptor intro desc_ip. * @@ -1616,9 +1586,14 @@ setup_desc_intro_point(const ed25519_keypair_t *signing_kp, /* Copy link specifier(s). */ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, - const hs_desc_link_specifier_t *, ls) { - hs_desc_link_specifier_t *copy = tor_malloc_zero(sizeof(*copy)); - link_specifier_copy(copy, ls); + const link_specifier_t *, ls) { + if (BUG(!ls)) { + goto done; + } + link_specifier_t *copy = link_specifier_dup(ls); + if (BUG(!copy)) { + goto done; + } smartlist_add(desc_ip->link_specifiers, copy); } SMARTLIST_FOREACH_END(ls); @@ -1666,7 +1641,7 @@ setup_desc_intro_point(const ed25519_keypair_t *signing_kp, return ret; } -/* Using the given descriptor from the given service, build the descriptor +/** Using the given descriptor from the given service, build the descriptor * intro point list so we can then encode the descriptor for publication. This * function does not pick intro points, they have to be in the descriptor * current map. Cryptographic material (keys) must be initialized in the @@ -1687,7 +1662,7 @@ build_desc_intro_points(const hs_service_t *service, DIGEST256MAP_FOREACH(desc->intro_points.map, key, const hs_service_intro_point_t *, ip) { - if (!ip->circuit_established) { + if (!hs_circ_service_get_established_intro_circ(ip)) { /* Ignore un-established intro points. They can linger in that list * because their circuit has not opened and they haven't been removed * yet even though we have enough intro circuits. @@ -1706,7 +1681,7 @@ build_desc_intro_points(const hs_service_t *service, } DIGEST256MAP_FOREACH_END; } -/* Build the descriptor signing key certificate. */ +/** Build the descriptor signing key certificate. */ static void build_desc_signing_key_cert(hs_service_descriptor_t *desc, time_t now) { @@ -1732,7 +1707,7 @@ build_desc_signing_key_cert(hs_service_descriptor_t *desc, time_t now) tor_assert_nonfatal(plaintext->signing_key_cert); } -/* Populate the descriptor encrypted section from the given service object. +/** Populate the descriptor encrypted section from the given service object. * This will generate a valid list of introduction points that can be used * after for circuit creation. Return 0 on success else -1 on error. */ static int @@ -1762,7 +1737,7 @@ build_service_desc_encrypted(const hs_service_t *service, return 0; } -/* Populate the descriptor superencrypted section from the given service +/** Populate the descriptor superencrypted section from the given service * object. This will generate a valid list of hs_desc_authorized_client_t * of clients that are authorized to use the service. Return 0 on success * else -1 on error. */ @@ -1790,7 +1765,7 @@ build_service_desc_superencrypted(const hs_service_t *service, sizeof(curve25519_public_key_t)); /* Test that subcred is not zero because we might use it below */ - if (BUG(tor_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) { + if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) { return -1; } @@ -1845,7 +1820,7 @@ build_service_desc_superencrypted(const hs_service_t *service, return 0; } -/* Populate the descriptor plaintext section from the given service object. +/** Populate the descriptor plaintext section from the given service object. * The caller must make sure that the keys in the descriptors are valid that * is are non-zero. This can't fail. */ static void @@ -1856,9 +1831,9 @@ build_service_desc_plaintext(const hs_service_t *service, tor_assert(service); tor_assert(desc); - tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp, + tor_assert(!fast_mem_is_zero((char *) &desc->blinded_kp, sizeof(desc->blinded_kp))); - tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp, + tor_assert(!fast_mem_is_zero((char *) &desc->signing_kp, sizeof(desc->signing_kp))); /* Set the subcredential. */ @@ -1896,7 +1871,7 @@ generate_ope_cipher_for_desc(const hs_service_descriptor_t *hs_desc) return crypto_ope_new(key); } -/* For the given service and descriptor object, create the key material which +/** For the given service and descriptor object, create the key material which * is the blinded keypair, the descriptor signing keypair, the ephemeral * keypair, and the descriptor cookie. Return 0 on success else -1 on error * where the generated keys MUST be ignored. */ @@ -1908,7 +1883,7 @@ build_service_desc_keys(const hs_service_t *service, ed25519_keypair_t kp; tor_assert(desc); - tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk, + tor_assert(!fast_mem_is_zero((char *) &service->keys.identity_pk, ED25519_PUBKEY_LEN)); /* XXX: Support offline key feature (#18098). */ @@ -1958,7 +1933,7 @@ build_service_desc_keys(const hs_service_t *service, return ret; } -/* Given a service and the current time, build a descriptor for the service. +/** Given a service and the current time, build a descriptor for the service. * This function does not pick introduction point, this needs to be done by * the update function. On success, desc_out will point to the newly allocated * descriptor object. @@ -2015,7 +1990,7 @@ build_service_descriptor(hs_service_t *service, uint64_t time_period_num, service_descriptor_free(desc); } -/* Build both descriptors for the given service that has just booted up. +/** Build both descriptors for the given service that has just booted up. * Because it's a special case, it deserves its special function ;). */ static void build_descriptors_for_new_service(hs_service_t *service, time_t now) @@ -2065,7 +2040,7 @@ build_descriptors_for_new_service(hs_service_t *service, time_t now) safe_str_client(service->onion_address)); } -/* Build descriptors for each service if needed. There are conditions to build +/** Build descriptors for each service if needed. There are conditions to build * a descriptor which are details in the function. */ STATIC void build_all_descriptors(time_t now) @@ -2098,7 +2073,7 @@ build_all_descriptors(time_t now) } FOR_EACH_DESCRIPTOR_END; } -/* Randomly pick a node to become an introduction point but not present in the +/** Randomly pick a node to become an introduction point but not present in the * given exclude_nodes list. The chosen node is put in the exclude list * regardless of success or not because in case of failure, the node is simply * unsusable from that point on. @@ -2117,7 +2092,6 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) { const or_options_t *options = get_options(); const node_t *node; - extend_info_t *info = NULL; hs_service_intro_point_t *ip = NULL; /* Normal 3-hop introduction point flags. */ router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC; @@ -2146,47 +2120,21 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) * we don't want to use that node anymore. */ smartlist_add(exclude_nodes, (void *) node); - /* We do this to ease our life but also this call makes appropriate checks - * of the node object such as validating ntor support for instance. - * - * We must provide an extend_info for clients to connect over a 3-hop path, - * so we don't pass direct_conn here. */ - info = extend_info_from_node(node, 0); - if (BUG(info == NULL)) { - goto err; - } + /* Create our objects and populate them with the node information. */ + ip = service_intro_point_new(node); - /* Let's do a basic sanity check here so that we don't end up advertising the - * ed25519 identity key of relays that don't actually support the link - * protocol */ - if (!node_supports_ed25519_link_authentication(node, 0)) { - tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); - } else { - /* Make sure we *do* have an ed key if we support the link authentication. - * Sending an empty key would result in a failure to extend. */ - tor_assert_nonfatal(!ed25519_public_key_is_zero(&info->ed_identity)); - } - - /* Create our objects and populate them with the node information. - * We don't care if the intro's link auth is compatible with us, because - * we are sending the ed25519 key to a remote client via the descriptor. */ - ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node), - node_supports_ed25519_link_authentication(node, - 0)); if (ip == NULL) { goto err; } - log_info(LD_REND, "Picked intro point: %s", extend_info_describe(info)); - extend_info_free(info); + log_info(LD_REND, "Picked intro point: %s", node_describe(node)); return ip; err: service_intro_point_free(ip); - extend_info_free(info); return NULL; } -/* For a given descriptor from the given service, pick any needed intro points +/** For a given descriptor from the given service, pick any needed intro points * and update the current map with those newly picked intro points. Return the * number node that might have been added to the descriptor current map. */ static unsigned int @@ -2310,7 +2258,7 @@ service_desc_schedule_upload(hs_service_descriptor_t *desc, } } -/* Pick missing intro points for this descriptor if needed. */ +/** Pick missing intro points for this descriptor if needed. */ static void update_service_descriptor_intro_points(hs_service_t *service, hs_service_descriptor_t *desc, time_t now) @@ -2351,7 +2299,7 @@ update_service_descriptor_intro_points(hs_service_t *service, } } -/* Update descriptor intro points for each service if needed. We do this as +/** Update descriptor intro points for each service if needed. We do this as * part of the periodic event because we need to establish intro point circuits * before we publish descriptors. */ STATIC void @@ -2366,7 +2314,7 @@ update_all_descriptors_intro_points(time_t now) } FOR_EACH_SERVICE_END; } -/* Return true iff the given intro point has expired that is it has been used +/** Return true iff the given intro point has expired that is it has been used * for too long or we've reached our max seen INTRODUCE2 cell. */ STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, @@ -2388,15 +2336,66 @@ intro_point_should_expire(const hs_service_intro_point_t *ip, return 1; } -/* Go over the given set of intro points for each service and remove any - * invalid ones. The conditions for removal are: +/** Return true iff we should remove the intro point ip from its service. * - * - The node doesn't exists anymore (not in consensus) - * OR - * - The intro point maximum circuit retry count has been reached and no - * circuit can be found associated with it. - * OR - * - The intro point has expired and we should pick a new one. + * We remove an intro point from the service descriptor list if one of + * these criteria is met: + * - It has expired (either in INTRO2 count or in time). + * - No node was found (fell off the consensus). + * - We are over the maximum amount of retries. + * + * If an established or pending circuit is found for the given ip object, this + * return false indicating it should not be removed. */ +static bool +should_remove_intro_point(hs_service_intro_point_t *ip, time_t now) +{ + bool ret = false; + + tor_assert(ip); + + /* Any one of the following needs to be True to furfill the criteria to + * remove an intro point. */ + bool has_no_retries = (ip->circuit_retries > + MAX_INTRO_POINT_CIRCUIT_RETRIES); + bool has_no_node = (get_node_from_intro_point(ip) == NULL); + bool has_expired = intro_point_should_expire(ip, now); + + /* If the node fell off the consensus or the IP has expired, we have to + * remove it now. */ + if (has_no_node || has_expired) { + ret = true; + goto end; + } + + /* Pass this point, even though we might be over the retry limit, we check + * if a circuit (established or pending) exists. In that case, we should not + * remove it because it might simply be valid and opened at the previous + * scheduled event for the last retry. */ + + /* Do we simply have an existing circuit regardless of its state? */ + if (hs_circ_service_get_intro_circ(ip)) { + goto end; + } + + /* Getting here means we have _no_ circuits so then return if we have any + * remaining retries. */ + ret = has_no_retries; + + end: + /* Meaningful log in case we are about to remove the IP. */ + if (ret) { + log_info(LD_REND, "Intro point %s%s (retried: %u times). " + "Removing it.", + describe_intro_point(ip), + has_expired ? " has expired" : + (has_no_node) ? " fell off the consensus" : "", + ip->circuit_retries); + } + return ret; +} + +/** Go over the given set of intro points for each service and remove any + * invalid ones. * * If an intro point is removed, the circuit (if any) is immediately close. * If a circuit can't be found, the intro point is kept if it hasn't reached @@ -2405,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); @@ -2421,21 +2418,7 @@ cleanup_intro_points(hs_service_t *service, time_t now) * valid and remove any of them that aren't. */ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, hs_service_intro_point_t *, ip) { - const node_t *node = get_node_from_intro_point(ip); - int has_expired = intro_point_should_expire(ip, now); - - /* We cleanup an intro point if it has expired or if we do not know the - * node_t anymore (removed from our latest consensus) or if we've - * reached the maximum number of retry with a non existing circuit. */ - if (has_expired || node == NULL || - ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { - log_info(LD_REND, "Intro point %s%s (retried: %u times). " - "Removing it.", - describe_intro_point(ip), - has_expired ? " has expired" : - (node == NULL) ? " fell off the consensus" : "", - ip->circuit_retries); - + if (should_remove_intro_point(ip, now)) { /* We've retried too many times, remember it as a failed intro point * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */ if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { @@ -2472,7 +2455,7 @@ cleanup_intro_points(hs_service_t *service, time_t now) smartlist_free(ips_to_free); } -/* Set the next rotation time of the descriptors for the given service for the +/** Set the next rotation time of the descriptors for the given service for the * time now. */ static void set_rotation_time(hs_service_t *service) @@ -2491,7 +2474,7 @@ set_rotation_time(hs_service_t *service) } } -/* Return true iff the service should rotate its descriptor. The time now is +/** Return true iff the service should rotate its descriptor. The time now is * only used to fetch the live consensus and if none can be found, this * returns false. */ static unsigned int @@ -2544,7 +2527,7 @@ should_rotate_descriptors(hs_service_t *service, time_t now) return 1; } -/* Rotate the service descriptors of the given service. The current descriptor +/** Rotate the service descriptors of the given service. The current descriptor * will be freed, the next one put in as the current and finally the next * descriptor pointer is NULLified. */ static void @@ -2566,7 +2549,7 @@ rotate_service_descriptors(hs_service_t *service) set_rotation_time(service); } -/* Rotate descriptors for each service if needed. A non existing current +/** Rotate descriptors for each service if needed. A non existing current * descriptor will trigger a descriptor build for the next time period. */ STATIC void rotate_all_descriptors(time_t now) @@ -2595,7 +2578,7 @@ rotate_all_descriptors(time_t now) } FOR_EACH_SERVICE_END; } -/* Scheduled event run from the main loop. Make sure all our services are up +/** Scheduled event run from the main loop. Make sure all our services are up * to date and ready for the other scheduled events. This includes looking at * the introduction points status and descriptor rotation time. */ STATIC void @@ -2630,7 +2613,7 @@ run_housekeeping_event(time_t now) } FOR_EACH_SERVICE_END; } -/* Scheduled event run from the main loop. Make sure all descriptors are up to +/** Scheduled event run from the main loop. Make sure all descriptors are up to * date. Once this returns, each service descriptor needs to be considered for * new introduction circuits and then for upload. */ static void @@ -2653,7 +2636,7 @@ run_build_descriptor_event(time_t now) update_all_descriptors_intro_points(now); } -/* For the given service, launch any intro point circuits that could be +/** For the given service, launch any intro point circuits that could be * needed. This considers every descriptor of the service. */ static void launch_intro_point_circuits(hs_service_t *service) @@ -2707,7 +2690,7 @@ launch_intro_point_circuits(hs_service_t *service) } FOR_EACH_DESCRIPTOR_END; } -/* Don't try to build more than this many circuits before giving up for a +/** Don't try to build more than this many circuits before giving up for a * while. Dynamically calculated based on the configured number of intro * points for the given service and how many descriptor exists. The default * use case of 3 introduction points and two descriptors will allow 28 @@ -2723,7 +2706,7 @@ get_max_intro_circ_per_period(const hs_service_t *service) tor_assert(service->config.num_intro_points <= HS_CONFIG_V3_MAX_INTRO_POINTS); -/* For a testing network, allow to do it for the maximum amount so circuit +/** For a testing network, allow to do it for the maximum amount so circuit * creation and rotation and so on can actually be tested without limit. */ #define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 if (get_options()->TestingTorNetwork) { @@ -2752,7 +2735,7 @@ get_max_intro_circ_per_period(const hs_service_t *service) return (count * multiplier); } -/* For the given service, return 1 if the service is allowed to launch more +/** For the given service, return 1 if the service is allowed to launch more * introduction circuits else 0 if the maximum has been reached for the retry * period of INTRO_CIRC_RETRY_PERIOD. */ STATIC int @@ -2798,7 +2781,7 @@ can_service_launch_intro_circuit(hs_service_t *service, time_t now) return 1; } -/* Scheduled event run from the main loop. Make sure we have all the circuits +/** Scheduled event run from the main loop. Make sure we have all the circuits * we need for each service. */ static void run_build_circuit_event(time_t now) @@ -2828,7 +2811,7 @@ run_build_circuit_event(time_t now) } FOR_EACH_SERVICE_END; } -/* Encode and sign the service descriptor desc and upload it to the given +/** Encode and sign the service descriptor desc and upload it to the given * hidden service directory. This does nothing if PublishHidServDescriptors * is false. */ static void @@ -2958,13 +2941,13 @@ set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, /* The OPE module returns CRYPTO_OPE_ERROR in case of errors. */ tor_assert_nonfatal(rev_counter < CRYPTO_OPE_ERROR); - log_info(LD_REND, "Encrypted revision counter %d to %ld", - (int) seconds_since_start_of_srv, (long int) rev_counter); + log_info(LD_REND, "Encrypted revision counter %d to %" PRIu64, + (int) seconds_since_start_of_srv, rev_counter); hs_desc->desc->plaintext_data.revision_counter = rev_counter; } -/* Encode and sign the service descriptor desc and upload it to the +/** Encode and sign the service descriptor desc and upload it to the * responsible hidden service directories. If for_next_period is true, the set * of directories are selected using the next hsdir_index. This does nothing * if PublishHidServDescriptors is false. */ @@ -3061,7 +3044,7 @@ service_desc_hsdirs_changed(const hs_service_t *service, return should_reupload; } -/* Return 1 if the given descriptor from the given service can be uploaded +/** Return 1 if the given descriptor from the given service can be uploaded * else return 0 if it can not. */ static int should_service_upload_descriptor(const hs_service_t *service, @@ -3119,7 +3102,7 @@ should_service_upload_descriptor(const hs_service_t *service, return 0; } -/* Refresh the given service descriptor meaning this will update every mutable +/** Refresh the given service descriptor meaning this will update every mutable * field that needs to be updated before we upload. * * This should ONLY be called before uploading a descriptor. It assumes that @@ -3150,7 +3133,7 @@ refresh_service_descriptor(const hs_service_t *service, set_descriptor_revision_counter(desc, now, service->desc_current == desc); } -/* Scheduled event run from the main loop. Try to upload the descriptor for +/** Scheduled event run from the main loop. Try to upload the descriptor for * each service. */ STATIC void run_upload_descriptor_event(time_t now) @@ -3199,7 +3182,7 @@ run_upload_descriptor_event(time_t now) consider_republishing_hs_descriptors = 0; } -/* Called when the introduction point circuit is done building and ready to be +/** Called when the introduction point circuit is done building and ready to be * used. */ static void service_intro_circ_has_opened(origin_circuit_t *circ) @@ -3257,7 +3240,7 @@ service_intro_circ_has_opened(origin_circuit_t *circ) return; } -/* Called when a rendezvous circuit is done building and ready to be used. */ +/** Called when a rendezvous circuit is done building and ready to be used. */ static void service_rendezvous_circ_has_opened(origin_circuit_t *circ) { @@ -3298,7 +3281,7 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ) return; } -/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just +/** We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given * introduction circuit. Return 0 on success else a negative value. */ static int @@ -3341,11 +3324,6 @@ service_handle_intro_established(origin_circuit_t *circ, goto err; } - /* Flag that we have an established circuit for this intro point. This value - * is what indicates the upload scheduled event if we are ready to build the - * intro point into the descriptor and upload. */ - ip->circuit_established = 1; - log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " "on circuit %u for service %s", TO_CIRCUIT(circ)->n_circ_id, @@ -3356,7 +3334,7 @@ service_handle_intro_established(origin_circuit_t *circ, return -1; } -/* We just received an INTRODUCE2 cell on the established introduction circuit +/** We just received an INTRODUCE2 cell on the established introduction circuit * circ. Handle the cell and return 0 on success else a negative value. */ static int service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, @@ -3404,7 +3382,7 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, return -1; } -/* Add to list every filename used by service. This is used by the sandbox +/** Add to list every filename used by service. This is used by the sandbox * subsystem. */ static void service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) @@ -3426,7 +3404,7 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) smartlist_add(list, hs_path_from_filename(s_dir, fname)); } -/* Return true iff the given service identity key is present on disk. */ +/** Return true iff the given service identity key is present on disk. */ static int service_key_on_disk(const char *directory_path) { @@ -3450,7 +3428,7 @@ service_key_on_disk(const char *directory_path) return ret; } -/* This is a proxy function before actually calling hs_desc_encode_descriptor +/** This is a proxy function before actually calling hs_desc_encode_descriptor * because we need some preprocessing here */ static int service_encode_descriptor(const hs_service_t *service, @@ -3481,7 +3459,7 @@ service_encode_descriptor(const hs_service_t *service, /* Public API */ /* ========== */ -/* This is called everytime the service map (v2 or v3) changes that is if an +/** This is called everytime the service map (v2 or v3) changes that is if an * element is added or removed. */ void hs_service_map_has_changed(void) @@ -3492,7 +3470,7 @@ hs_service_map_has_changed(void) rescan_periodic_events(get_options()); } -/* Upload an encoded descriptor in encoded_desc of the given version. This +/** Upload an encoded descriptor in encoded_desc of the given version. This * descriptor is for the service identity_pk and blinded_pk used to setup the * directory connection identifier. It is uploaded to the directory hsdir_rs * routerstatus_t object. @@ -3540,7 +3518,7 @@ hs_service_upload_desc_to_dir(const char *encoded_desc, directory_request_free(dir_req); } -/* Add the ephemeral service using the secret key sk and ports. Both max +/** Add the ephemeral service using the secret key sk and ports. Both max * streams parameter will be set in the newly created service. * * Ownership of sk and ports is passed to this routine. Regardless of @@ -3626,7 +3604,7 @@ hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports, return ret; } -/* For the given onion address, delete the ephemeral service. Return 0 on +/** For the given onion address, delete the ephemeral service. Return 0 on * success else -1 on error. */ int hs_service_del_ephemeral(const char *address) @@ -3676,7 +3654,7 @@ hs_service_del_ephemeral(const char *address) return -1; } -/* Using the ed25519 public key pk, find a service for that key and return the +/** Using the ed25519 public key pk, find a service for that key and return the * current encoded descriptor as a newly allocated string or NULL if not * found. This is used by the control port subsystem. */ char * @@ -3702,9 +3680,9 @@ hs_service_lookup_current_desc(const ed25519_public_key_t *pk) return NULL; } -/* Return the number of service we have configured and usable. */ -unsigned int -hs_service_get_num_services(void) +/** Return the number of service we have configured and usable. */ +MOCK_IMPL(unsigned int, +hs_service_get_num_services,(void)) { if (hs_service_map == NULL) { return 0; @@ -3712,49 +3690,7 @@ 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); - - /* Circuit disappeared so make sure the intro point is updated. By - * keeping the object in the descriptor, we'll be able to retry. */ - ip->circuit_established = 0; - - end: - return; -} - -/* Given conn, a rendezvous edge connection acting as an exit stream, look up +/** 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. * @@ -3852,7 +3788,7 @@ hs_service_exports_circuit_id(const ed25519_public_key_t *pk) return service->config.circuit_id_protocol; } -/* Add to file_list every filename used by a configured hidden service, and to +/** Add to file_list every filename used by a configured hidden service, and to * dir_list every directory path used by a configured hidden service. This is * used by the sandbox subsystem to whitelist those. */ void @@ -3877,7 +3813,7 @@ hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, } FOR_EACH_DESCRIPTOR_END; } -/* Called when our internal view of the directory has changed. We might have +/** Called when our internal view of the directory has changed. We might have * received a new batch of descriptors which might affect the shape of the * HSDir hash ring. Signal that we should reexamine the hash ring and * re-upload our HS descriptors if needed. */ @@ -3894,7 +3830,7 @@ hs_service_dir_info_changed(void) } } -/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and +/** Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and * launch a circuit to the rendezvous point. */ int hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, @@ -3925,7 +3861,7 @@ hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, return ret; } -/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an +/** Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an * established introduction point. Return 0 on success else a negative value * and the circuit is closed. */ int @@ -3962,7 +3898,7 @@ hs_service_receive_intro_established(origin_circuit_t *circ, return -1; } -/* Called when any kind of hidden service circuit is done building thus +/** Called when any kind of hidden service circuit is done building thus * opened. This is the entry point from the circuit subsystem. */ void hs_service_circuit_has_opened(origin_circuit_t *circ) @@ -3991,7 +3927,7 @@ hs_service_circuit_has_opened(origin_circuit_t *circ) } } -/* Return the service version by looking at the key in the service directory. +/** Return the service version by looking at the key in the service directory. * If the key is not found or unrecognized, -1 is returned. Else, the service * version is returned. */ int @@ -4021,7 +3957,7 @@ hs_service_get_version_from_key(const hs_service_t *service) return version; } -/* Load and/or generate keys for all onion services including the client +/** Load and/or generate keys for all onion services including the client * authorization if any. Return 0 on success, -1 on failure. */ int hs_service_load_all_keys(void) @@ -4057,7 +3993,7 @@ hs_service_load_all_keys(void) return -1; } -/* Put all service object in the given service list. After this, the caller +/** Put all service object in the given service list. After this, the caller * looses ownership of every elements in the list and responsible to free the * list pointer. */ void @@ -4074,7 +4010,7 @@ hs_service_stage_services(const smartlist_t *service_list) smartlist_add_all(hs_service_staging_list, service_list); } -/* Allocate and initilize a service object. The service configuration will +/** Allocate and initilize a service object. The service configuration will * contain the default values. Return the newly allocated object pointer. This * function can't fail. */ hs_service_t * @@ -4092,7 +4028,7 @@ hs_service_new(const or_options_t *options) return service; } -/* Free the given <b>service</b> object and all its content. This function +/** Free the given <b>service</b> object and all its content. This function * also takes care of wiping service keys from memory. It is safe to pass a * NULL pointer. */ void @@ -4121,7 +4057,7 @@ hs_service_free_(hs_service_t *service) tor_free(service); } -/* Periodic callback. Entry point from the main loop to the HS service +/** Periodic callback. Entry point from the main loop to the HS service * subsystem. This is call every second. This is skipped if tor can't build a * circuit or the network is disabled. */ void @@ -4144,7 +4080,7 @@ hs_service_run_scheduled_events(time_t now) run_upload_descriptor_event(now); } -/* Initialize the service HS subsystem. */ +/** Initialize the service HS subsystem. */ void hs_service_init(void) { @@ -4161,7 +4097,7 @@ hs_service_init(void) hs_service_staging_list = smartlist_new(); } -/* Release all global storage of the hidden service subsystem. */ +/** Release all global storage of the hidden service subsystem. */ void hs_service_free_all(void) { @@ -4171,14 +4107,14 @@ hs_service_free_all(void) #ifdef TOR_UNIT_TESTS -/* Return the global service map size. Only used by unit test. */ +/** Return the global service map size. Only used by unit test. */ STATIC unsigned int get_hs_service_map_size(void) { return HT_SIZE(hs_service_map); } -/* Return the staging list size. Only used by unit test. */ +/** Return the staging list size. Only used by unit test. */ STATIC int get_hs_service_staging_list_size(void) { diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 5f43233ea1..8809411e01 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -21,88 +21,92 @@ /* Trunnel */ #include "trunnel/hs/cell_establish_intro.h" -/* When loading and configuring a service, this is the default version it will +#include "ext/ht.h" + +/** When loading and configuring a service, this is the default version it will * be configured for as it is possible that no HiddenServiceVersion is * present. */ #define HS_SERVICE_DEFAULT_VERSION HS_VERSION_THREE -/* As described in the specification, service publishes their next descriptor +/** As described in the specification, service publishes their next descriptor * at a random time between those two values (in seconds). */ #define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60) +/** Maximum interval for uploading next descriptor (in seconds). */ #define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60) -/* Service side introduction point. */ +/** Service side introduction point. */ typedef struct hs_service_intro_point_t { - /* Top level intropoint "shared" data between client/service. */ + /** Top level intropoint "shared" data between client/service. */ hs_intropoint_t base; - /* Onion key of the introduction point used to extend to it for the ntor + /** Onion key of the introduction point used to extend to it for the ntor * handshake. */ curve25519_public_key_t onion_key; - /* Authentication keypair used to create the authentication certificate + /** Authentication keypair used to create the authentication certificate * which is published in the descriptor. */ ed25519_keypair_t auth_key_kp; - /* Encryption keypair for the "ntor" type. */ + /** Encryption keypair for the "ntor" type. */ curve25519_keypair_t enc_key_kp; - /* Legacy key if that intro point doesn't support v3. This should be used if + /** Legacy key if that intro point doesn't support v3. This should be used if * the base object legacy flag is set. */ crypto_pk_t *legacy_key; - /* Legacy key SHA1 public key digest. This should be used only if the base + /** Legacy key SHA1 public key digest. This should be used only if the base * object legacy flag is set. */ uint8_t legacy_key_digest[DIGEST_LEN]; - /* Amount of INTRODUCE2 cell accepted from this intro point. */ + /** Amount of INTRODUCE2 cell accepted from this intro point. */ uint64_t introduce2_count; - /* Maximum number of INTRODUCE2 cell this intro point should accept. */ + /** Maximum number of INTRODUCE2 cell this intro point should accept. */ uint64_t introduce2_max; - /* The time at which this intro point should expire and stop being used. */ + /** The time at which this intro point should expire and stop being used. */ time_t time_to_expire; - /* The amount of circuit creation we've made to this intro point. This is + /** The amount of circuit creation we've made to this intro point. This is * incremented every time we do a circuit relaunch on this intro point which * is triggered when the circuit dies but the node is still in the * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */ uint32_t circuit_retries; - /* Set if this intro point has an established circuit. */ - unsigned int circuit_established : 1; - - /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the + /** Replay cache recording the encrypted part of an INTRODUCE2 cell that the * circuit associated with this intro point has received. This is used to * prevent replay attacks. */ replaycache_t *replay_cache; + + /** Support the INTRO2 DoS defense. If set, the DoS extension described by + * proposal 305 is sent. */ + unsigned int support_intro2_dos_defense : 1; } hs_service_intro_point_t; -/* Object handling introduction points of a service. */ +/** Object handling introduction points of a service. */ typedef struct hs_service_intropoints_t { - /* The time at which we've started our retry period to build circuits. We + /** The time at which we've started our retry period to build circuits. We * don't want to stress circuit creation so we can only retry for a certain * time and then after we stop and wait. */ time_t retry_period_started; - /* Number of circuit we've launched during a single retry period. */ + /** Number of circuit we've launched during a single retry period. */ unsigned int num_circuits_launched; - /* Contains the current hs_service_intro_point_t objects indexed by + /** Contains the current hs_service_intro_point_t objects indexed by * authentication public key. */ digest256map_t *map; - /* Contains node's identity key digest that were introduction point for this + /** Contains node's identity key digest that were introduction point for this * descriptor but were retried to many times. We keep those so we avoid * re-picking them over and over for a circuit retry period. * XXX: Once we have #22173, change this to only use ed25519 identity. */ digestmap_t *failed_id; } hs_service_intropoints_t; -/* Representation of a service descriptor. +/** Representation of a service descriptor. * * Some elements of the descriptor are mutable whereas others are immutable: - + * * Immutable elements are initialized once when the descriptor is built (when * service descriptors gets rotated). This means that these elements are * initialized once and then they don't change for the lifetime of the @@ -117,40 +121,42 @@ typedef struct hs_service_intropoints_t { * update_service_descriptor_intro_points(). */ typedef struct hs_service_descriptor_t { - /* Immutable: Client authorization ephemeral keypair. */ + /** Immutable: Client authorization ephemeral keypair. */ curve25519_keypair_t auth_ephemeral_kp; - /* Immutable: Descriptor cookie used to encrypt the descriptor, when the + /** Immutable: Descriptor cookie used to encrypt the descriptor, when the * client authorization is enabled */ uint8_t descriptor_cookie[HS_DESC_DESCRIPTOR_COOKIE_LEN]; - /* Immutable: Descriptor signing keypair. */ + /** Immutable: Descriptor signing keypair. */ ed25519_keypair_t signing_kp; - /* Immutable: Blinded keypair derived from the master identity public key. */ + /** Immutable: Blinded keypair derived from the master identity public + * key. */ ed25519_keypair_t blinded_kp; - /* Immutable: The time period number this descriptor has been created for. */ + /** Immutable: The time period number this descriptor has been created + * for. */ uint64_t time_period_num; /** Immutable: The OPE cipher for encrypting revision counters for this * descriptor. Tied to the descriptor blinded key. */ struct crypto_ope_t *ope_cipher; - /* Mutable: Decoded descriptor. This object is used for encoding when the + /** Mutable: Decoded descriptor. This object is used for encoding when the * service publishes the descriptor. */ hs_descriptor_t *desc; - /* Mutable: When is the next time when we should upload the descriptor. */ + /** Mutable: When is the next time when we should upload the descriptor. */ time_t next_upload_time; - /* Mutable: Introduction points assign to this descriptor which contains + /** Mutable: Introduction points assign to this descriptor which contains * hs_service_intropoints_t object indexed by authentication key (the RSA key * if the node is legacy). */ hs_service_intropoints_t intro_points; - /* Mutable: True iff we have missing intro points for this descriptor because - * we couldn't pick any nodes. */ + /** Mutable: True iff we have missing intro points for this descriptor + * because we couldn't pick any nodes. */ unsigned int missing_intro_points : 1; /** Mutable: List of the responsible HSDirs (their b64ed identity digest) @@ -160,20 +166,20 @@ typedef struct hs_service_descriptor_t { smartlist_t *previous_hsdirs; } hs_service_descriptor_t; -/* Service key material. */ +/** Service key material. */ typedef struct hs_service_keys_t { - /* Master identify public key. */ + /** Master identify public key. */ ed25519_public_key_t identity_pk; - /* Master identity private key. */ + /** Master identity private key. */ ed25519_secret_key_t identity_sk; - /* True iff the key is kept offline which means the identity_sk MUST not be + /** True iff the key is kept offline which means the identity_sk MUST not be * used in that case. */ unsigned int is_identify_key_offline : 1; } hs_service_keys_t; /** Service side configuration of client authorization. */ typedef struct hs_service_authorized_client_t { - /* The client auth public key used to encrypt the descriptor cookie. */ + /** The client auth public key used to encrypt the descriptor cookie. */ curve25519_public_key_t client_pk; } hs_service_authorized_client_t; @@ -186,115 +192,120 @@ typedef enum { HS_CIRCUIT_ID_PROTOCOL_HAPROXY } hs_circuit_id_protocol_t; -/* Service configuration. The following are set from the torrc options either +/** Service configuration. The following are set from the torrc options either * set by the configuration file or by the control port. Nothing else should * change those values. */ typedef struct hs_service_config_t { - /* Protocol version of the service. Specified by HiddenServiceVersion + /** Protocol version of the service. Specified by HiddenServiceVersion * option. */ uint32_t version; - /* Have we explicitly set HiddenServiceVersion? */ + /** Have we explicitly set HiddenServiceVersion? */ unsigned int hs_version_explicitly_set : 1; - /* List of rend_service_port_config_t */ + /** List of rend_service_port_config_t */ smartlist_t *ports; - /* Path on the filesystem where the service persistent data is stored. NULL + /** Path on the filesystem where the service persistent data is stored. NULL * if the service is ephemeral. Specified by HiddenServiceDir option. */ char *directory_path; - /* The maximum number of simultaneous streams per rendezvous circuit that + /** The maximum number of simultaneous streams per rendezvous circuit that * are allowed to be created. No limit if 0. Specified by * HiddenServiceMaxStreams option. */ uint64_t max_streams_per_rdv_circuit; - /* If true, we close circuits that exceed the max_streams_per_rdv_circuit + /** If true, we close circuits that exceed the max_streams_per_rdv_circuit * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */ unsigned int max_streams_close_circuit : 1; - /* How many introduction points this service has. Specified by + /** How many introduction points this service has. Specified by * HiddenServiceNumIntroductionPoints option. */ unsigned int num_intro_points; - /* True iff the client auth is enabled. */ + /** True iff the client auth is enabled. */ unsigned int is_client_auth_enabled : 1; - /* List of hs_service_authorized_client_t's of clients that may access this + /** List of hs_service_authorized_client_t's of clients that may access this * service. Specified by HiddenServiceAuthorizeClient option. */ smartlist_t *clients; - /* True iff we allow request made on unknown ports. Specified by + /** True iff we allow request made on unknown ports. Specified by * HiddenServiceAllowUnknownPorts option. */ unsigned int allow_unknown_ports : 1; - /* If true, this service is a Single Onion Service. Specified by + /** If true, this service is a Single Onion Service. Specified by * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */ unsigned int is_single_onion : 1; - /* If true, allow group read permissions on the directory_path. Specified by + /** If true, allow group read permissions on the directory_path. Specified by * HiddenServiceDirGroupReadable option. */ unsigned int dir_group_readable : 1; - /* Is this service ephemeral? */ + /** Is this service ephemeral? */ unsigned int is_ephemeral : 1; - /* Does this service export the circuit ID of its clients? */ + /** Does this service export the circuit ID of its clients? */ hs_circuit_id_protocol_t circuit_id_protocol; + + /* DoS defenses. For the ESTABLISH_INTRO cell extension. */ + unsigned int has_dos_defense_enabled : 1; + uint32_t intro_dos_rate_per_sec; + uint32_t intro_dos_burst_per_sec; } hs_service_config_t; -/* Service state. */ +/** Service state. */ typedef struct hs_service_state_t { - /* The time at which we've started our retry period to build circuits. We + /** The time at which we've started our retry period to build circuits. We * don't want to stress circuit creation so we can only retry for a certain * time and then after we stop and wait. */ time_t intro_circ_retry_started_time; - /* Number of circuit we've launched during a single retry period. This + /** Number of circuit we've launched during a single retry period. This * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ unsigned int num_intro_circ_launched; - /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + /** Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point * through two or more different introduction points; when they do, this * keeps us from launching multiple simultaneous attempts to connect to the * same rend point. */ replaycache_t *replay_cache_rend_cookie; - /* When is the next time we should rotate our descriptors. This is has to be + /** When is the next time we should rotate our descriptors. This is has to be * done at the start time of the next SRV protocol run. */ time_t next_rotation_time; } hs_service_state_t; -/* Representation of a service running on this tor instance. */ +/** Representation of a service running on this tor instance. */ typedef struct hs_service_t { - /* Onion address base32 encoded and NUL terminated. We keep it for logging + /** Onion address base32 encoded and NUL terminated. We keep it for logging * purposes so we don't have to build it everytime. */ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; - /* Hashtable node: use to look up the service by its master public identity + /** Hashtable node: use to look up the service by its master public identity * key in the service global map. */ HT_ENTRY(hs_service_t) hs_service_node; - /* Service state which contains various flags and counters. */ + /** Service state which contains various flags and counters. */ hs_service_state_t state; - /* Key material of the service. */ + /** Key material of the service. */ hs_service_keys_t keys; - /* Configuration of the service. */ + /** Configuration of the service. */ hs_service_config_t config; - /* Current descriptor. */ + /** Current descriptor. */ hs_service_descriptor_t *desc_current; - /* Next descriptor. */ + /** Next descriptor. */ hs_service_descriptor_t *desc_next; /* XXX: Credential (client auth.) #20700. */ } hs_service_t; -/* For the service global hash map, we define a specific type for it which +/** For the service global hash map, we define a specific type for it which * will make it safe to use and specific to some controlled parameters such as * the hashing function and how to compare services. */ typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht; @@ -308,9 +319,14 @@ void hs_service_free_all(void); /* Service new/free functions. */ hs_service_t *hs_service_new(const or_options_t *options); void hs_service_free_(hs_service_t *service); +/** + * @copydoc hs_service_free_ + * + * Additionally, set the pointer <b>s</b> to NULL. + **/ #define hs_service_free(s) FREE_AND_NULL(hs_service_t, hs_service_free_, (s)) -unsigned int hs_service_get_num_services(void); +MOCK_DECL(unsigned int, hs_service_get_num_services,(void)); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); int hs_service_get_version_from_key(const hs_service_t *service); @@ -330,8 +346,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 @@ -361,7 +375,7 @@ STATIC hs_service_t *get_first_service(void); STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( const hs_service_t *service, const hs_ident_circuit_t *ident); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /* Service accessors. */ STATIC hs_service_t *find_service(hs_service_ht *map, @@ -369,10 +383,7 @@ STATIC hs_service_t *find_service(hs_service_ht *map, STATIC void remove_service(hs_service_ht *map, hs_service_t *service); STATIC int register_service(hs_service_ht *map, hs_service_t *service); /* Service introduction point functions. */ -STATIC hs_service_intro_point_t *service_intro_point_new( - const extend_info_t *ei, - unsigned int is_legacy, - unsigned int supports_ed25519_link_handshake_any); +STATIC hs_service_intro_point_t *service_intro_point_new(const node_t *node); STATIC void service_intro_point_free_(hs_service_intro_point_t *ip); #define service_intro_point_free(ip) \ FREE_AND_NULL(hs_service_intro_point_t, \ diff --git a/src/feature/hs/hs_stats.c b/src/feature/hs/hs_stats.c index f24b731328..f9d458d630 100644 --- a/src/feature/hs/hs_stats.c +++ b/src/feature/hs/hs_stats.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/hs/hs_stats.h b/src/feature/hs/hs_stats.h index d89440faca..aea2ccf5c2 100644 --- a/src/feature/hs/hs_stats.h +++ b/src/feature/hs/hs_stats.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* Copyright (c) 2016-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -6,9 +6,13 @@ * \brief Header file for hs_stats.c **/ +#ifndef TOR_HS_STATS_H +#define TOR_HS_STATS_H + void hs_stats_note_introduce2_cell(int is_hsv3); uint32_t hs_stats_get_n_introduce2_v3_cells(void); uint32_t hs_stats_get_n_introduce2_v2_cells(void); void hs_stats_note_service_rendezvous_launch(void); uint32_t hs_stats_get_n_rendezvous_launches(void); +#endif /* !defined(TOR_HS_STATS_H) */ diff --git a/src/feature/hs/hsdir_index_st.h b/src/feature/hs/hsdir_index_st.h index 7d4116d8bb..6ce0bf5c69 100644 --- a/src/feature/hs/hsdir_index_st.h +++ b/src/feature/hs/hsdir_index_st.h @@ -1,24 +1,29 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file hsdir_index_st.h + * @brief HS directory index structure + **/ + #ifndef HSDIR_INDEX_ST_H #define HSDIR_INDEX_ST_H -/* Hidden service directory index used in a node_t which is set once we set +/** Hidden service directory index used in a node_t which is set once we set * the consensus. */ struct hsdir_index_t { - /* HSDir index to use when fetching a descriptor. */ + /** HSDir index to use when fetching a descriptor. */ uint8_t fetch[DIGEST256_LEN]; - /* HSDir index used by services to store their first and second + /** HSDir index used by services to store their first and second * descriptor. The first descriptor is chronologically older than the second * one and uses older TP and SRV values. */ uint8_t store_first[DIGEST256_LEN]; + /** Newer index, for second descriptor. */ uint8_t store_second[DIGEST256_LEN]; }; -#endif - +#endif /* !defined(HSDIR_INDEX_ST_H) */ diff --git a/src/feature/hs/include.am b/src/feature/hs/include.am new file mode 100644 index 0000000000..5e69607e59 --- /dev/null +++ b/src/feature/hs/include.am @@ -0,0 +1,35 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/hs/hs_cache.c \ + src/feature/hs/hs_cell.c \ + src/feature/hs/hs_circuit.c \ + src/feature/hs/hs_circuitmap.c \ + src/feature/hs/hs_client.c \ + src/feature/hs/hs_common.c \ + src/feature/hs/hs_config.c \ + src/feature/hs/hs_control.c \ + src/feature/hs/hs_descriptor.c \ + src/feature/hs/hs_dos.c \ + src/feature/hs/hs_ident.c \ + src/feature/hs/hs_intropoint.c \ + src/feature/hs/hs_service.c \ + src/feature/hs/hs_stats.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/hs/hs_cache.h \ + src/feature/hs/hs_cell.h \ + src/feature/hs/hs_circuit.h \ + src/feature/hs/hs_circuitmap.h \ + src/feature/hs/hs_client.h \ + src/feature/hs/hs_common.h \ + src/feature/hs/hs_config.h \ + src/feature/hs/hs_control.h \ + src/feature/hs/hs_descriptor.h \ + src/feature/hs/hs_dos.h \ + src/feature/hs/hs_ident.h \ + src/feature/hs/hs_intropoint.h \ + src/feature/hs/hs_service.h \ + src/feature/hs/hs_stats.h \ + src/feature/hs/hsdir_index_st.h diff --git a/src/feature/hs_common/.may_include b/src/feature/hs_common/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/hs_common/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/hs_common/feature_hs_common.md b/src/feature/hs_common/feature_hs_common.md new file mode 100644 index 0000000000..3a5e351a0a --- /dev/null +++ b/src/feature/hs_common/feature_hs_common.md @@ -0,0 +1,3 @@ +@dir /feature/hs_common +@brief feature/hs_common: Common to v2 (old) and v3 (current) onion services + diff --git a/src/feature/hs_common/include.am b/src/feature/hs_common/include.am new file mode 100644 index 0000000000..3bb9225c12 --- /dev/null +++ b/src/feature/hs_common/include.am @@ -0,0 +1,10 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/hs_common/replaycache.c \ + src/feature/hs_common/shared_random_client.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/hs_common/replaycache.h \ + src/feature/hs_common/shared_random_client.h diff --git a/src/feature/hs_common/replaycache.c b/src/feature/hs_common/replaycache.c index 9e8c13b1c5..ab058ce759 100644 --- a/src/feature/hs_common/replaycache.c +++ b/src/feature/hs_common/replaycache.c @@ -1,4 +1,4 @@ - /* Copyright (c) 2012-2019, The Tor Project, Inc. */ + /* Copyright (c) 2012-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/hs_common/replaycache.h b/src/feature/hs_common/replaycache.h index 01f5e600c2..3a3eed29c0 100644 --- a/src/feature/hs_common/replaycache.h +++ b/src/feature/hs_common/replaycache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2019, The Tor Project, Inc. */ +/* Copyright (c) 2012-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,16 +14,16 @@ typedef struct replaycache_t replaycache_t; #ifdef REPLAYCACHE_PRIVATE struct replaycache_t { - /* Scrub interval */ + /** Scrub interval */ time_t scrub_interval; - /* Last scrubbed */ + /** Last scrubbed */ time_t scrubbed; - /* + /** * Horizon * (don't return true on digests in the cache but older than this) */ time_t horizon; - /* + /** * Digest map: keys are digests, values are times the digest was last seen */ digest256map_t *digests_seen; @@ -34,6 +34,11 @@ struct replaycache_t { /* replaycache_t free/new */ void replaycache_free_(replaycache_t *r); +/** + * @copydoc replaycache_free_ + * + * Additionally, set the pointer <b>r</b> to NULL. + **/ #define replaycache_free(r) \ FREE_AND_NULL(replaycache_t, replaycache_free_, (r)) replaycache_t * replaycache_new(time_t horizon, time_t interval); diff --git a/src/feature/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c index ead5d681a9..b4b2f2c9d3 100644 --- a/src/feature/hs_common/shared_random_client.c +++ b/src/feature/hs_common/shared_random_client.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -8,7 +8,6 @@ * as part of the dirauth module. **/ -#define SHARED_RANDOM_CLIENT_PRIVATE #include "feature/hs_common/shared_random_client.h" #include "app/config/config.h" @@ -19,7 +18,7 @@ #include "feature/nodelist/networkstatus_st.h" -/* Convert a given srv object to a string for the control port. This doesn't +/** Convert a given srv object to a string for the control port. This doesn't * fail and the srv object MUST be valid. */ static char * srv_to_control_string(const sr_srv_t *srv) @@ -33,7 +32,7 @@ srv_to_control_string(const sr_srv_t *srv) return srv_str; } -/* Return the voting interval of the tor vote subsystem. */ +/** Return the voting interval of the tor vote subsystem. */ int get_voting_interval(void) { @@ -54,7 +53,7 @@ get_voting_interval(void) return interval; } -/* Given the current consensus, return the start time of the current round of +/** Given the current consensus, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. * @@ -81,7 +80,7 @@ get_start_time_of_current_round(void) * Public API */ -/* Encode the given shared random value and put it in dst. Destination +/** Encode the given shared random value and put it in dst. Destination * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */ void sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv) @@ -102,7 +101,7 @@ sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv) strlcpy(dst, buf, dst_len); } -/* Return the current SRV string representation for the control port. Return a +/** Return the current SRV string representation for the control port. Return a * newly allocated string on success containing the value else "" if not found * or if we don't have a valid consensus yet. */ char * @@ -118,7 +117,7 @@ sr_get_current_for_control(void) return srv_str; } -/* Return the previous SRV string representation for the control port. Return +/** Return the previous SRV string representation for the control port. Return * a newly allocated string on success containing the value else "" if not * found or if we don't have a valid consensus yet. */ char * @@ -134,7 +133,7 @@ sr_get_previous_for_control(void) return srv_str; } -/* Return current shared random value from the latest consensus. Caller can +/** Return current shared random value from the latest consensus. Caller can * NOT keep a reference to the returned pointer. Return NULL if none. */ const sr_srv_t * sr_get_current(const networkstatus_t *ns) @@ -158,7 +157,7 @@ sr_get_current(const networkstatus_t *ns) return NULL; } -/* Return previous shared random value from the latest consensus. Caller can +/** Return previous shared random value from the latest consensus. Caller can * NOT keep a reference to the returned pointer. Return NULL if none. */ const sr_srv_t * sr_get_previous(const networkstatus_t *ns) @@ -182,7 +181,7 @@ sr_get_previous(const networkstatus_t *ns) return NULL; } -/* Parse a list of arguments from a SRV value either from a vote, consensus +/** Parse a list of arguments from a SRV value either from a vote, consensus * or from our disk state and return a newly allocated srv object. NULL is * returned on error. * @@ -295,4 +294,3 @@ sr_state_get_protocol_run_duration(void) int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; return total_protocol_rounds * get_voting_interval(); } - diff --git a/src/feature/hs_common/shared_random_client.h b/src/feature/hs_common/shared_random_client.h index 95fe2c65ab..3031a2bb9a 100644 --- a/src/feature/hs_common/shared_random_client.h +++ b/src/feature/hs_common/shared_random_client.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -44,5 +44,5 @@ time_t get_start_time_of_current_round(void); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_SHARED_RANDOM_CLIENT_H */ +#endif /* !defined(TOR_SHARED_RANDOM_CLIENT_H) */ diff --git a/src/feature/keymgt/.may_include b/src/feature/keymgt/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/keymgt/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/keymgt/feature_keymgt.md b/src/feature/keymgt/feature_keymgt.md new file mode 100644 index 0000000000..1eac7cca50 --- /dev/null +++ b/src/feature/keymgt/feature_keymgt.md @@ -0,0 +1,3 @@ +@dir /feature/keymgt +@brief feature/keymgt: Store keys for relays, authorities, etc. + diff --git a/src/feature/keymgt/include.am b/src/feature/keymgt/include.am new file mode 100644 index 0000000000..bc9beaa523 --- /dev/null +++ b/src/feature/keymgt/include.am @@ -0,0 +1,8 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/keymgt/loadkey.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/keymgt/loadkey.h diff --git a/src/feature/keymgt/loadkey.c b/src/feature/keymgt/loadkey.c index a8cbf0e582..7958bd964f 100644 --- a/src/feature/keymgt/loadkey.c +++ b/src/feature/keymgt/loadkey.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -33,7 +33,7 @@ /** 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>created_out/b> is non-NULL and a + * errors at level <b>severity</b>. If <b>created_out</b> is non-NULL and a * new key was created, set *<b>created_out</b> to true. */ crypto_pk_t * diff --git a/src/feature/keymgt/loadkey.h b/src/feature/keymgt/loadkey.h index 8beee57a20..5a8ca32dea 100644 --- a/src/feature/keymgt/loadkey.h +++ b/src/feature/keymgt/loadkey.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -52,4 +52,4 @@ int read_encrypted_secret_key(ed25519_secret_key_t *out, int write_encrypted_secret_key(const ed25519_secret_key_t *out, const char *fname); -#endif +#endif /* !defined(TOR_LOADKEY_H) */ diff --git a/src/feature/nodelist/.may_include b/src/feature/nodelist/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/nodelist/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/nodelist/authcert.c b/src/feature/nodelist/authcert.c index 7a065662a7..7bdfabaeab 100644 --- a/src/feature/nodelist/authcert.c +++ b/src/feature/nodelist/authcert.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -380,7 +380,8 @@ trusted_dirs_load_certs_from_string(const char *contents, int source, int added_trusted_cert = 0; for (s = contents; *s; s = eos) { - authority_cert_t *cert = authority_cert_parse_from_string(s, &eos); + authority_cert_t *cert = authority_cert_parse_from_string(s, strlen(s), + &eos); cert_list_t *cl; if (!cert) { failure_code = -1; diff --git a/src/feature/nodelist/authcert.h b/src/feature/nodelist/authcert.h index 2effdb06e6..33065589ba 100644 --- a/src/feature/nodelist/authcert.h +++ b/src/feature/nodelist/authcert.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -57,4 +57,4 @@ MOCK_DECL(download_status_t *, download_status_for_authority_id_and_sk, void authcert_free_all(void); -#endif +#endif /* !defined(TOR_AUTHCERT_H) */ diff --git a/src/feature/nodelist/authority_cert_st.h b/src/feature/nodelist/authority_cert_st.h index 68a84bc452..9145b12bbf 100644 --- a/src/feature/nodelist/authority_cert_st.h +++ b/src/feature/nodelist/authority_cert_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file authority_cert_st.h + * @brief Authority certificate structure. + **/ + #ifndef AUTHORITY_CERT_ST_H #define AUTHORITY_CERT_ST_H @@ -28,5 +33,4 @@ struct authority_cert_t { uint16_t dir_port; }; -#endif - +#endif /* !defined(AUTHORITY_CERT_ST_H) */ diff --git a/src/feature/nodelist/desc_store_st.h b/src/feature/nodelist/desc_store_st.h index b04a1abc7d..5f35a490a5 100644 --- a/src/feature/nodelist/desc_store_st.h +++ b/src/feature/nodelist/desc_store_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file desc_store_st.h + * @brief Routerinfo/extrainfo storage structure. + **/ + #ifndef DESC_STORE_ST_H #define DESC_STORE_ST_H @@ -36,4 +41,4 @@ struct desc_store_t { size_t bytes_dropped; }; -#endif +#endif /* !defined(DESC_STORE_ST_H) */ diff --git a/src/feature/nodelist/describe.c b/src/feature/nodelist/describe.c index 5c376408c0..00896d5a44 100644 --- a/src/feature/nodelist/describe.c +++ b/src/feature/nodelist/describe.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -9,66 +9,108 @@ * \brief Format short descriptions of relays. */ +#define DESCRIBE_PRIVATE + #include "core/or/or.h" #include "feature/nodelist/describe.h" -#include "feature/nodelist/routerinfo.h" #include "core/or/extend_info_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" - -/** - * 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) +#include "feature/nodelist/microdesc_st.h" /** 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>. + * <b>id_digest</b>, nickname <b>nickname</b>, and addresses <b>addr32h</b> and + * <b>addr</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. + * NULL or the null address. The <b>addr32h</b> field is optional and may be + * set to 0. * * Return a pointer to the front of <b>buf</b>. + * If buf is NULL, return a string constant describing the error. */ -static const char * +STATIC 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; + size_t rv = 0; + bool has_addr = addr && !tor_addr_is_null(addr); 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; + memset(buf, 0, NODE_DESC_BUF_LEN); + + if (!id_digest) { + /* strlcpy() returns the length of the source string it attempted to copy, + * ignoring any required truncation due to the buffer length. */ + rv = strlcpy(buf, "<NULL ID DIGEST>", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + return buf; + } + + /* strlcat() returns the length of the concatenated string it attempted to + * create, ignoring any required truncation due to the buffer length. */ + rv = strlcat(buf, "$", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + + { + char hex_digest[HEX_DIGEST_LEN+1]; + memset(hex_digest, 0, sizeof(hex_digest)); + + base16_encode(hex_digest, sizeof(hex_digest), + id_digest, DIGEST_LEN); + rv = strlcat(buf, hex_digest, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_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); + rv = strlcat(buf, "~", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + rv = strlcat(buf, nickname, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); } - 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); - } + if (addr32h || has_addr) { + rv = strlcat(buf, " at ", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); } + if (addr32h) { + int ntoa_rv = 0; + char ipv4_addr_str[INET_NTOA_BUF_LEN]; + memset(ipv4_addr_str, 0, sizeof(ipv4_addr_str)); + struct in_addr in; + memset(&in, 0, sizeof(in)); + + in.s_addr = htonl(addr32h); + ntoa_rv = tor_inet_ntoa(&in, ipv4_addr_str, sizeof(ipv4_addr_str)); + tor_assert_nonfatal(ntoa_rv >= 0); + + rv = strlcat(buf, ipv4_addr_str, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + /* Both addresses are valid */ + if (addr32h && has_addr) { + rv = strlcat(buf, " and ", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + if (has_addr) { + const char *str_rv = NULL; + char addr_str[TOR_ADDR_BUF_LEN]; + memset(addr_str, 0, sizeof(addr_str)); + + str_rv = tor_addr_to_str(addr_str, addr, sizeof(addr_str), 1); + tor_assert_nonfatal(str_rv == addr_str); + + rv = strlcat(buf, addr_str, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + return buf; } @@ -84,11 +126,11 @@ router_describe(const routerinfo_t *ri) if (!ri) return "<null>"; + return format_node_description(buf, ri->cache_info.identity_digest, - 0, ri->nickname, - NULL, + &ri->ipv6_addr, ri->addr); } @@ -103,25 +145,33 @@ 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; + const tor_addr_t *ipv6_addr = NULL; if (!node) return "<null>"; if (node->rs) { nickname = node->rs->nickname; - is_named = node->rs->is_named; addr32h = node->rs->addr; + ipv6_addr = &node->rs->ipv6_addr; + /* Support consensus versions less than 28, when IPv6 addresses were in + * microdescs. This code can be removed when 0.2.9 is no longer supported, + * and the MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC macro is removed. */ + if (node->md && tor_addr_is_null(ipv6_addr)) { + ipv6_addr = &node->md->ipv6_addr; + } } else if (node->ri) { nickname = node->ri->nickname; addr32h = node->ri->addr; + ipv6_addr = &node->ri->ipv6_addr; + } else { + return "<null rs and ri>"; } return format_node_description(buf, node->identity, - is_named, nickname, - NULL, + ipv6_addr, addr32h); } @@ -137,11 +187,11 @@ routerstatus_describe(const routerstatus_t *rs) if (!rs) return "<null>"; + return format_node_description(buf, rs->identity_digest, - rs->is_named, rs->nickname, - NULL, + &rs->ipv6_addr, rs->addr); } @@ -157,9 +207,9 @@ extend_info_describe(const extend_info_t *ei) if (!ei) return "<null>"; + return format_node_description(buf, ei->identity_digest, - 0, ei->nickname, &ei->addr, 0); @@ -175,9 +225,39 @@ extend_info_describe(const extend_info_t *ei) 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); + size_t rv = 0; + + if (!buf) + return; + + memset(buf, 0, MAX_VERBOSE_NICKNAME_LEN+1); + + if (!router) { + /* strlcpy() returns the length of the source string it attempted to copy, + * ignoring any required truncation due to the buffer length. */ + rv = strlcpy(buf, "<null>", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + return; + } + + /* strlcat() returns the length of the concatenated string it attempted to + * create, ignoring any required truncation due to the buffer length. */ + rv = strlcat(buf, "$", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + + { + char hex_digest[HEX_DIGEST_LEN+1]; + memset(hex_digest, 0, sizeof(hex_digest)); + + base16_encode(hex_digest, sizeof(hex_digest), + router->cache_info.identity_digest, DIGEST_LEN); + rv = strlcat(buf, hex_digest, MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + } + + rv = strlcat(buf, "~", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + + rv = strlcat(buf, router->nickname, MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); } diff --git a/src/feature/nodelist/describe.h b/src/feature/nodelist/describe.h index 018af6470e..d0fa1af263 100644 --- a/src/feature/nodelist/describe.h +++ b/src/feature/nodelist/describe.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -22,4 +22,36 @@ const char *node_describe(const struct node_t *node); const char *router_describe(const struct routerinfo_t *ri); const char *routerstatus_describe(const struct routerstatus_t *ri); -#endif +void router_get_verbose_nickname(char *buf, const routerinfo_t *router); + +#if defined(DESCRIBE_PRIVATE) || defined(TOR_UNIT_TESTS) + +/** + * Longest allowed output for an IPv4 address "255.255.255.255", with NO + * terminating NUL. + */ +#define IPV4_BUF_LEN_NO_NUL 15 + +/** + * Longest allowed output of format_node_description, plus 1 character for + * NUL. This allows space for: + * "$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF~xxxxxxxxxxxxxxxxxxx at" + * " 255.255.255.255 and [ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]" + * plus a terminating NUL. + */ +#define NODE_DESC_BUF_LEN \ + (MAX_VERBOSE_NICKNAME_LEN+4+IPV4_BUF_LEN_NO_NUL+5+TOR_ADDR_BUF_LEN) + +#endif /* defined(DESCRIBE_PRIVATE) || defined(TOR_UNIT_TESTS) */ + +#ifdef TOR_UNIT_TESTS + +STATIC const char *format_node_description(char *buf, + const char *id_digest, + const char *nickname, + const tor_addr_t *addr, + uint32_t addr32h); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* !defined(TOR_DESCRIBE_H) */ diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index b4abffad67..883564e080 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -28,7 +28,7 @@ #include "app/config/config.h" #include "core/or/policies.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/dirlist.h" @@ -70,7 +70,7 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir) } /** Go over the trusted directory server list and add their address(es) to the - * nodelist address set. This is called every time a new consensus is set. */ + * nodelist address set. This is called everytime a new consensus is set. */ MOCK_IMPL(void, dirlist_add_trusted_dir_addresses, (void)) { diff --git a/src/feature/nodelist/dirlist.h b/src/feature/nodelist/dirlist.h index 527af35427..9201e76a9c 100644 --- a/src/feature/nodelist/dirlist.h +++ b/src/feature/nodelist/dirlist.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -46,4 +46,4 @@ void dirlist_free_all(void); MOCK_DECL(void, dirlist_add_trusted_dir_addresses, (void)); -#endif +#endif /* !defined(TOR_DIRLIST_H) */ diff --git a/src/feature/nodelist/document_signature_st.h b/src/feature/nodelist/document_signature_st.h index 66e32c422f..4bde9d89ec 100644 --- a/src/feature/nodelist/document_signature_st.h +++ b/src/feature/nodelist/document_signature_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file document_signature_st.h + * @brief Authority signature structure + **/ + #ifndef DOCUMENT_SIGNATURE_ST_H #define DOCUMENT_SIGNATURE_ST_H @@ -25,5 +30,4 @@ struct document_signature_t { * as good. */ }; -#endif - +#endif /* !defined(DOCUMENT_SIGNATURE_ST_H) */ diff --git a/src/feature/nodelist/extrainfo_st.h b/src/feature/nodelist/extrainfo_st.h index c54277b05e..6bd6232cd8 100644 --- a/src/feature/nodelist/extrainfo_st.h +++ b/src/feature/nodelist/extrainfo_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file extrainfo_st.h + * @brief A relay's extra-info structure. + **/ + #ifndef EXTRAINFO_ST_H #define EXTRAINFO_ST_H @@ -26,5 +31,4 @@ struct extrainfo_t { size_t pending_sig_len; }; -#endif - +#endif /* !defined(EXTRAINFO_ST_H) */ diff --git a/src/feature/nodelist/feature_nodelist.md b/src/feature/nodelist/feature_nodelist.md new file mode 100644 index 0000000000..9d715308c2 --- /dev/null +++ b/src/feature/nodelist/feature_nodelist.md @@ -0,0 +1,2 @@ +@dir /feature/nodelist +@brief feature/nodelist: Download and manage a list of relays diff --git a/src/feature/nodelist/fmt_routerstatus.c b/src/feature/nodelist/fmt_routerstatus.c index 75cab7a0af..0cf4a6eeab 100644 --- a/src/feature/nodelist/fmt_routerstatus.c +++ b/src/feature/nodelist/fmt_routerstatus.c @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -14,64 +14,19 @@ #include "core/or/or.h" #include "feature/nodelist/fmt_routerstatus.h" -/* #include "lib/container/buffers.h" */ -/* #include "app/config/config.h" */ -/* #include "app/config/confparse.h" */ -/* #include "core/or/channel.h" */ -/* #include "core/or/channeltls.h" */ -/* #include "core/or/command.h" */ -/* #include "core/mainloop/connection.h" */ -/* #include "core/or/connection_or.h" */ -/* #include "feature/dircache/conscache.h" */ -/* #include "feature/dircache/consdiffmgr.h" */ -/* #include "feature/control/control.h" */ -/* #include "feature/dircache/directory.h" */ -/* #include "feature/dircache/dirserv.h" */ -/* #include "feature/hibernate/hibernate.h" */ -/* #include "feature/dirauth/keypin.h" */ -/* #include "core/mainloop/mainloop.h" */ -/* #include "feature/nodelist/microdesc.h" */ -/* #include "feature/nodelist/networkstatus.h" */ -/* #include "feature/nodelist/nodelist.h" */ #include "core/or/policies.h" -/* #include "core/or/protover.h" */ -/* #include "feature/stats/rephist.h" */ -/* #include "feature/relay/router.h" */ -/* #include "feature/nodelist/dirlist.h" */ #include "feature/nodelist/routerlist.h" - -/* #include "feature/nodelist/routerparse.h" */ -/* #include "feature/nodelist/routerset.h" */ -/* #include "feature/nodelist/torcert.h" */ -/* #include "feature/dircommon/voting_schedule.h" */ - #include "feature/dirauth/dirvote.h" -/* #include "feature/dircache/cached_dir_st.h" */ -/* #include "feature/dircommon/dir_connection_st.h" */ -/* #include "feature/nodelist/extrainfo_st.h" */ -/* #include "feature/nodelist/microdesc_st.h" */ -/* #include "feature/nodelist/node_st.h" */ #include "feature/nodelist/routerinfo_st.h" -/* #include "feature/nodelist/routerlist_st.h" */ -/* #include "core/or/tor_version_st.h" */ #include "feature/nodelist/vote_routerstatus_st.h" -/* #include "lib/compress/compress.h" */ -/* #include "lib/container/order.h" */ #include "lib/crypt_ops/crypto_format.h" -/* #include "lib/encoding/confline.h" */ - -/* #include "lib/encoding/keyval.h" */ /** Helper: write the router-status information in <b>rs</b> into a newly * allocated character buffer. Use the same format as in network-status * documents. If <b>version</b> is non-NULL, add a "v" line for the platform. * - * consensus_method is the current consensus method when format is - * NS_V3_CONSENSUS or NS_V3_CONSENSUS_MICRODESC. It is ignored for other - * formats: pass ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD. - * * Return 0 on success, -1 on failure. * * The format argument has one of the following values: @@ -88,7 +43,6 @@ char * routerstatus_format_entry(const routerstatus_t *rs, const char *version, const char *protocols, routerstatus_format_type_t format, - int consensus_method, const vote_routerstatus_t *vrs) { char *summary; @@ -119,12 +73,6 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, * networkstatus_type_t values, with an additional control port value * added -MP */ - /* V3 microdesc consensuses only have "a" lines in later consensus methods - */ - if (format == NS_V3_CONSENSUS_MICRODESC && - consensus_method < MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS) - goto done; - /* Possible "a" line. At most one for now. */ if (!tor_addr_is_null(&rs->ipv6_addr)) { smartlist_add_asprintf(chunks, "a %s\n", @@ -135,7 +83,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, goto done; smartlist_add_asprintf(chunks, - "s%s%s%s%s%s%s%s%s%s%s\n", + "s%s%s%s%s%s%s%s%s%s%s%s\n", /* These must stay in alphabetical order. */ rs->is_authority?" Authority":"", rs->is_bad_exit?" BadExit":"", @@ -145,6 +93,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, rs->is_hs_dir?" HSDir":"", rs->is_flagged_running?" Running":"", rs->is_stable?" Stable":"", + rs->is_staledesc?" StaleDesc":"", rs->is_v2_dir?" V2Dir":"", rs->is_valid?" Valid":""); @@ -164,6 +113,8 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, if (format != NS_CONTROL_PORT) { /* Blow up more or less nicely if we didn't get anything or not the * thing we expected. + * This should be kept in sync with the function + * routerstatus_has_visibly_changed and the struct routerstatus_t */ if (!desc) { char id[HEX_DIGEST_LEN+1]; @@ -232,7 +183,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, } if (format == NS_V3_VOTE && vrs) { - if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) { + if (fast_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) { smartlist_add_strdup(chunks, "id ed25519 none\n"); } else { char ed_b64[BASE64_DIGEST256_LEN+1]; diff --git a/src/feature/nodelist/fmt_routerstatus.h b/src/feature/nodelist/fmt_routerstatus.h index ddd7a7cf37..a007989af3 100644 --- a/src/feature/nodelist/fmt_routerstatus.h +++ b/src/feature/nodelist/fmt_routerstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -35,7 +35,6 @@ char *routerstatus_format_entry( const char *version, const char *protocols, routerstatus_format_type_t format, - int consensus_method, const vote_routerstatus_t *vrs); #endif /* !defined(TOR_FMT_ROUTERSTATUS_H) */ diff --git a/src/feature/nodelist/include.am b/src/feature/nodelist/include.am new file mode 100644 index 0000000000..2f5d58ec1c --- /dev/null +++ b/src/feature/nodelist/include.am @@ -0,0 +1,49 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/nodelist/authcert.c \ + src/feature/nodelist/describe.c \ + src/feature/nodelist/dirlist.c \ + src/feature/nodelist/microdesc.c \ + src/feature/nodelist/networkstatus.c \ + src/feature/nodelist/nickname.c \ + src/feature/nodelist/nodefamily.c \ + src/feature/nodelist/nodelist.c \ + src/feature/nodelist/node_select.c \ + src/feature/nodelist/routerinfo.c \ + src/feature/nodelist/routerlist.c \ + src/feature/nodelist/routerset.c \ + src/feature/nodelist/fmt_routerstatus.c \ + src/feature/nodelist/torcert.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/nodelist/authcert.h \ + src/feature/nodelist/authority_cert_st.h \ + src/feature/nodelist/describe.h \ + src/feature/nodelist/desc_store_st.h \ + src/feature/nodelist/dirlist.h \ + src/feature/nodelist/document_signature_st.h \ + src/feature/nodelist/extrainfo_st.h \ + src/feature/nodelist/microdesc.h \ + src/feature/nodelist/microdesc_st.h \ + src/feature/nodelist/networkstatus.h \ + src/feature/nodelist/networkstatus_sr_info_st.h \ + src/feature/nodelist/networkstatus_st.h \ + src/feature/nodelist/networkstatus_voter_info_st.h \ + src/feature/nodelist/nickname.h \ + src/feature/nodelist/node_st.h \ + src/feature/nodelist/nodefamily.h \ + src/feature/nodelist/nodefamily_st.h \ + src/feature/nodelist/nodelist.h \ + src/feature/nodelist/node_select.h \ + src/feature/nodelist/routerinfo.h \ + src/feature/nodelist/routerinfo_st.h \ + src/feature/nodelist/routerlist.h \ + src/feature/nodelist/routerlist_st.h \ + src/feature/nodelist/routerset.h \ + src/feature/nodelist/fmt_routerstatus.h \ + src/feature/nodelist/routerstatus_st.h \ + src/feature/nodelist/signed_descriptor_st.h \ + src/feature/nodelist/torcert.h \ + src/feature/nodelist/vote_routerstatus_st.h diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c index dafaabb5e5..d32a4ea61e 100644 --- a/src/feature/nodelist/microdesc.c +++ b/src/feature/nodelist/microdesc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2019, The Tor Project, Inc. */ +/* Copyright (c) 2009-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,11 +18,13 @@ #include "feature/client/entrynodes.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dlstatus.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/dircommon/directory.h" #include "feature/dirparse/microdesc_parse.h" #include "feature/nodelist/dirlist.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodefamily.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/relay/router.h" @@ -69,6 +71,8 @@ struct microdesc_cache_t { }; static microdesc_cache_t *get_microdesc_cache_noload(void); +static void warn_if_nul_found(const char *inp, size_t len, int64_t offset, + const char *activity); /** Helper: computes a hash of <b>md</b> to place it in a hash table. */ static inline unsigned int @@ -110,8 +114,9 @@ microdesc_note_outdated_dirserver(const char *relay_digest) /* If we have a reasonably live consensus, then most of our dirservers should * still be caching all the microdescriptors in it. Reasonably live - * consensuses are up to a day old. But microdescriptors expire 7 days after - * the last consensus that referenced them. */ + * consensuses are up to a day old (or a day in the future). But + * microdescriptors expire 7 days after the last consensus that referenced + * them. */ if (!networkstatus_get_reasonably_live_consensus(approx_time(), FLAV_MICRODESC)) { return; @@ -221,6 +226,8 @@ dump_microdescriptor(int fd, microdesc_t *md, size_t *annotation_len_out) } md->off = tor_fd_getpos(fd); + warn_if_nul_found(md->body, md->bodylen, (int64_t) md->off, + "dumping a microdescriptor"); written = write_all_to_fd(fd, md->body, md->bodylen); if (written != (ssize_t)md->bodylen) { written = written < 0 ? 0 : written; @@ -480,6 +487,27 @@ microdesc_cache_clear(microdesc_cache_t *cache) cache->bytes_dropped = 0; } +static void +warn_if_nul_found(const char *inp, size_t len, int64_t offset, + const char *activity) +{ + const char *nul_found = memchr(inp, 0, len); + if (BUG(nul_found)) { + log_warn(LD_BUG, "Found unexpected NUL while %s, offset %"PRId64 + "at position %"TOR_PRIuSZ"/%"TOR_PRIuSZ".", + activity, offset, (nul_found - inp), len); + const char *start_excerpt_at, *eos = inp + len; + if ((nul_found - inp) >= 16) + start_excerpt_at = nul_found - 16; + else + start_excerpt_at = inp; + size_t excerpt_len = MIN(32, eos - start_excerpt_at); + char tmp[65]; + base16_encode(tmp, sizeof(tmp), start_excerpt_at, excerpt_len); + log_warn(LD_BUG, " surrounding string: %s", tmp); + } +} + /** Reload the contents of <b>cache</b> from disk. If it is empty, load it * for the first time. Return 0 on success, -1 on failure. */ int @@ -497,6 +525,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) mm = cache->cache_content = tor_mmap_file(cache->cache_fname); if (mm) { + warn_if_nul_found(mm->data, mm->size, 0, "scanning microdesc cache"); added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size, SAVED_IN_CACHE, 0, -1, NULL); if (added) { @@ -508,7 +537,9 @@ microdesc_cache_reload(microdesc_cache_t *cache) journal_content = read_file_to_str(cache->journal_fname, RFTS_IGNORE_MISSING, &st); if (journal_content) { - cache->journal_len = (size_t) st.st_size; + cache->journal_len = strlen(journal_content); + warn_if_nul_found(journal_content, (size_t)st.st_size, 0, + "reading microdesc journal"); added = microdescs_add_to_cache(cache, journal_content, journal_content+st.st_size, SAVED_IN_JOURNAL, 0, -1, NULL); @@ -544,8 +575,8 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) size_t bytes_dropped = 0; time_t now = time(NULL); - /* If we don't know a live consensus, don't believe last_listed values: we - * might be starting up after being down for a while. */ + /* If we don't know a reasonably live consensus, don't believe last_listed + * values: we might be starting up after being down for a while. */ if (! force && ! networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC)) return; @@ -884,10 +915,7 @@ microdesc_free_(microdesc_t *md, const char *fname, int lineno) if (md->body && md->saved_location != SAVED_IN_CACHE) tor_free(md->body); - if (md->family) { - SMARTLIST_FOREACH(md->family, char *, cp, tor_free(cp)); - smartlist_free(md->family); - } + nodefamily_free(md->family); short_policy_free(md->exit_policy); short_policy_free(md->ipv6_exit_policy); @@ -943,7 +971,7 @@ microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, continue; if (skip && digest256map_get(skip, (const uint8_t*)rs->descriptor_digest)) continue; - if (tor_mem_is_zero(rs->descriptor_digest, DIGEST256_LEN)) + if (fast_mem_is_zero(rs->descriptor_digest, DIGEST256_LEN)) continue; /* XXXX Also skip if we're a noncache and wouldn't use this router. * XXXX NM Microdesc @@ -970,9 +998,10 @@ update_microdesc_downloads(time_t now) if (should_delay_dir_fetches(options, NULL)) return; - if (directory_too_idle_to_fetch_descriptors(options, now)) + if (dirclient_too_idle_to_fetch_descriptors(options, now)) return; + /* Give up if we don't have a reasonably live consensus. */ consensus = networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC); if (!consensus) return; diff --git a/src/feature/nodelist/microdesc.h b/src/feature/nodelist/microdesc.h index c18099d540..b352f58e34 100644 --- a/src/feature/nodelist/microdesc.h +++ b/src/feature/nodelist/microdesc.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/nodelist/microdesc_st.h b/src/feature/nodelist/microdesc_st.h index bb8b23d664..410403e965 100644 --- a/src/feature/nodelist/microdesc_st.h +++ b/src/feature/nodelist/microdesc_st.h @@ -1,16 +1,24 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file microdesc_st.h + * @brief Microdescriptor structure + **/ + #ifndef MICRODESC_ST_H #define MICRODESC_ST_H struct curve25519_public_key_t; struct ed25519_public_key_t; +struct nodefamily_t; struct short_policy_t; +#include "ext/ht.h" + /** A microdescriptor is the smallest amount of information needed to build a * circuit through a router. They are generated by the directory authorities, * using information from the uploaded routerinfo documents. They are not @@ -32,6 +40,8 @@ struct microdesc_t { unsigned int no_save : 1; /** If true, this microdesc has an entry in the microdesc_map */ unsigned int held_in_map : 1; + /** True iff the exit policy for this router rejects everything. */ + unsigned int policy_is_reject_star : 1; /** Reference count: how many node_ts have a reference to this microdesc? */ unsigned int held_by_nodes; @@ -69,12 +79,12 @@ struct microdesc_t { tor_addr_t ipv6_addr; /** As routerinfo_t.ipv6_orport */ uint16_t ipv6_orport; - /** As routerinfo_t.family */ - smartlist_t *family; + /** As routerinfo_t.family, with readable members parsed. */ + struct nodefamily_t *family; /** IPv4 exit policy summary */ struct short_policy_t *exit_policy; /** IPv6 exit policy summary */ struct short_policy_t *ipv6_exit_policy; }; -#endif +#endif /* !defined(MICRODESC_ST_H) */ diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index c74acd8b74..cc4b8e1c34 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -44,6 +44,7 @@ #include "core/mainloop/netstatus.h" #include "core/or/channel.h" #include "core/or/channelpadding.h" +#include "core/or/circuitpadding.h" #include "core/or/circuitmux.h" #include "core/or/circuitmux_ewma.h" #include "core/or/circuitstats.h" @@ -57,16 +58,18 @@ #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" #include "feature/client/transports.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/reachability.h" #include "feature/dircache/consdiffmgr.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/directory.h" #include "feature/dircommon/voting_schedule.h" #include "feature/dirparse/ns_parse.h" #include "feature/hibernate/hibernate.h" +#include "feature/hs/hs_dos.h" #include "feature/nodelist/authcert.h" #include "feature/nodelist/dirlist.h" #include "feature/nodelist/fmt_routerstatus.h" @@ -81,6 +84,7 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" +#include "feature/dirauth/dirauth_periodic.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/shared_random.h" @@ -98,6 +102,7 @@ #include "feature/nodelist/routerlist_st.h" #include "feature/dirauth/vote_microdesc_hash_st.h" #include "feature/nodelist/vote_routerstatus_st.h" +#include "feature/nodelist/routerstatus_st.h" #ifdef HAVE_UNISTD_H #include <unistd.h> @@ -116,8 +121,6 @@ STATIC networkstatus_t *current_md_consensus = NULL; typedef struct consensus_waiting_for_certs_t { /** The consensus itself. */ networkstatus_t *consensus; - /** The encoded version of the consensus, nul-terminated. */ - char *body; /** When did we set the current value of consensus_waiting_for_certs? If * this is too recent, we shouldn't try to fetch a new consensus for a * little while, to give ourselves time to get certificates for this one. */ @@ -179,6 +182,10 @@ static void update_consensus_bootstrap_multiple_downloads( static int networkstatus_check_required_protocols(const networkstatus_t *ns, int client_mode, char **warning_out); +static int reload_consensus_from_file(const char *fname, + const char *flavor, + unsigned flags, + const char *source_dir); /** Forget that we've warned about anything networkstatus-related, so we will * give fresh warnings if the same behavior happens again. */ @@ -210,14 +217,11 @@ networkstatus_reset_download_failures(void) download_status_reset(&consensus_bootstrap_dl_status[i]); } -/** - * Read and and return the cached consensus of type <b>flavorname</b>. If - * <b>unverified</b> is true, get the one we haven't verified. Return NULL if - * the file isn't there. */ -static char * -networkstatus_read_cached_consensus_impl(int flav, - const char *flavorname, - int unverified_consensus) +/** Return the filename used to cache the consensus of a given flavor */ +MOCK_IMPL(char *, +networkstatus_get_cache_fname,(int flav, + const char *flavorname, + int unverified_consensus)) { char buf[128]; const char *prefix; @@ -232,21 +236,35 @@ networkstatus_read_cached_consensus_impl(int flav, tor_snprintf(buf, sizeof(buf), "%s-%s-consensus", prefix, flavorname); } - char *filename = get_cachedir_fname(buf); - char *result = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL); + return get_cachedir_fname(buf); +} + +/** + * Read and and return the cached consensus of type <b>flavorname</b>. If + * <b>unverified</b> is false, get the one we haven't verified. Return NULL if + * the file isn't there. */ +static tor_mmap_t * +networkstatus_map_cached_consensus_impl(int flav, + const char *flavorname, + int unverified_consensus) +{ + char *filename = networkstatus_get_cache_fname(flav, + flavorname, + unverified_consensus); + tor_mmap_t *result = tor_mmap_file(filename); tor_free(filename); return result; } -/** Return a new string containing the current cached consensus of flavor - * <b>flavorname</b>. */ -char * -networkstatus_read_cached_consensus(const char *flavorname) - { +/** Map the file containing the current cached consensus of flavor + * <b>flavorname</b> */ +tor_mmap_t * +networkstatus_map_cached_consensus(const char *flavorname) +{ int flav = networkstatus_parse_flavor_name(flavorname); if (flav < 0) return NULL; - return networkstatus_read_cached_consensus_impl(flav, flavorname, 0); + return networkstatus_map_cached_consensus_impl(flav, flavorname, 0); } /** Read every cached v3 consensus networkstatus from the disk. */ @@ -259,25 +277,15 @@ router_reload_consensus_networkstatus(void) /* FFFF Suppress warnings if cached consensus is bad? */ for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) { const char *flavor = networkstatus_get_flavor_name(flav); - char *s = networkstatus_read_cached_consensus_impl(flav, flavor, 0); - if (s) { - if (networkstatus_set_current_consensus(s, flavor, flags, NULL) < -1) { - log_warn(LD_FS, "Couldn't load consensus %s networkstatus from cache", - flavor); - } - tor_free(s); - } + char *fname = networkstatus_get_cache_fname(flav, flavor, 0); + reload_consensus_from_file(fname, flavor, flags, NULL); + tor_free(fname); - s = networkstatus_read_cached_consensus_impl(flav, flavor, 1); - if (s) { - if (networkstatus_set_current_consensus(s, flavor, - flags | NSSET_WAS_WAITING_FOR_CERTS, - NULL)) { - log_info(LD_FS, "Couldn't load unverified consensus %s networkstatus " - "from cache", flavor); - } - tor_free(s); - } + fname = networkstatus_get_cache_fname(flav, flavor, 1); + reload_consensus_from_file(fname, flavor, + flags | NSSET_WAS_WAITING_FOR_CERTS, + NULL); + tor_free(fname); } update_certificate_downloads(time(NULL)); @@ -713,8 +721,8 @@ networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest) /** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or * NULL if none was found. */ -const routerstatus_t * -networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest) +MOCK_IMPL(const routerstatus_t *, +networkstatus_vote_find_entry,(networkstatus_t *ns, const char *digest)) { return networkstatus_vote_find_mutable_entry(ns, digest); } @@ -1156,7 +1164,7 @@ update_consensus_networkstatus_fetch_time_impl(time_t now, int flav) } } - if (directory_fetches_dir_info_early(options)) { + if (dirclient_fetches_dir_info_early(options)) { /* We want to cache the next one at some point after this one * is no longer fresh... */ start = (time_t)(c->fresh_until + min_sec_before_caching); @@ -1178,7 +1186,7 @@ update_consensus_networkstatus_fetch_time_impl(time_t now, int flav) /* If we're a bridge user, make use of the numbers we just computed * to choose the rest of the interval *after* them. */ - if (directory_fetches_dir_info_later(options)) { + if (dirclient_fetches_dir_info_later(options)) { /* Give all the *clients* enough time to download the consensus. */ start = (time_t)(start + dl_interval + min_sec_before_caching); /* But try to get it before ours actually expires. */ @@ -1377,7 +1385,7 @@ networkstatus_get_dl_status_by_flavor_running,(consensus_flavor_t flavor)) } /** Return the most recent consensus that we have downloaded, or NULL if we - * don't have one. */ + * don't have one. May return future or expired consensuses. */ MOCK_IMPL(networkstatus_t *, networkstatus_get_latest_consensus,(void)) { @@ -1388,7 +1396,7 @@ networkstatus_get_latest_consensus,(void)) } /** Return the latest consensus we have whose flavor matches <b>f</b>, or NULL - * if we don't have one. */ + * if we don't have one. May return future or expired consensuses. */ MOCK_IMPL(networkstatus_t *, networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) { @@ -1422,10 +1430,11 @@ networkstatus_is_live(const networkstatus_t *ns, time_t now) return (ns->valid_after <= now && now <= ns->valid_until); } -/** Determine if <b>consensus</b> is valid or expired recently enough that - * we can still use it. +/** Determine if <b>consensus</b> is valid, or expired recently enough, or not + * too far in the future, so that we can still use it. * - * Return 1 if the consensus is reasonably live, or 0 if it is too old. + * Return 1 if the consensus is reasonably live, or 0 if it is too old or + * too new. */ int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, @@ -1434,29 +1443,42 @@ networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, if (BUG(!consensus)) return 0; - return networkstatus_valid_until_is_reasonably_live(consensus->valid_until, + return networkstatus_valid_after_is_reasonably_live(consensus->valid_after, + now) && + networkstatus_valid_until_is_reasonably_live(consensus->valid_until, now); } +#define REASONABLY_LIVE_TIME (24*60*60) + +/** As networkstatus_consensus_reasonably_live, but takes a valid_after + * time, and checks to see if it is in the past, or not too far in the future. + */ +int +networkstatus_valid_after_is_reasonably_live(time_t valid_after, + time_t now) +{ + return (now >= valid_after - REASONABLY_LIVE_TIME); +} + /** As networkstatus_consensus_reasonably_live, but takes a valid_until - * time rather than an entire consensus. */ + * time, and checks to see if it is in the future, or not too far in the past. + */ int networkstatus_valid_until_is_reasonably_live(time_t valid_until, time_t now) { -#define REASONABLY_LIVE_TIME (24*60*60) return (now <= valid_until + REASONABLY_LIVE_TIME); } /** As networkstatus_get_live_consensus(), but is way more tolerant of expired - * consensuses. */ + * and future consensuses. */ MOCK_IMPL(networkstatus_t *, networkstatus_get_reasonably_live_consensus,(time_t now, int flavor)) { networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(flavor); if (consensus && - consensus->valid_after <= now && networkstatus_consensus_reasonably_live(consensus, now)) return consensus; else @@ -1517,7 +1539,7 @@ networkstatus_consensus_can_use_extra_fallbacks,(const or_options_t *options)) >= smartlist_len(router_get_trusted_dir_servers())); /* If we don't fetch from the authorities, and we have additional mirrors, * we can use them. */ - return (!directory_fetches_from_authorities(options) + return (!dirclient_fetches_from_authorities(options) && (smartlist_len(router_get_fallback_dir_servers()) > smartlist_len(router_get_trusted_dir_servers()))); } @@ -1557,36 +1579,16 @@ networkstatus_consensus_is_already_downloading(const char *resource) return answer; } -/* Does the current, reasonably live consensus have IPv6 addresses? - * Returns 1 if there is a reasonably live consensus and its consensus method - * includes IPv6 addresses in the consensus. - * Otherwise, if there is no consensus, or the method does not include IPv6 - * addresses, returns 0. */ -int -networkstatus_consensus_has_ipv6(const or_options_t* options) -{ - const networkstatus_t *cons = networkstatus_get_reasonably_live_consensus( - approx_time(), - usable_consensus_flavor()); - - /* If we have no consensus, we have no IPv6 in it */ - if (!cons) { - return 0; - } - - /* Different flavours of consensus gained IPv6 at different times */ - if (we_use_microdescriptors_for_circuits(options)) { - return - cons->consensus_method >= MIN_METHOD_FOR_A_LINES_IN_MICRODESC_CONSENSUS; - } else { - return 1; - } -} - -/** Given two router status entries for the same router identity, return 1 if - * if the contents have changed between them. Otherwise, return 0. */ -static int -routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) +/** Given two router status entries for the same router identity, return 1 + * if the contents have changed between them. Otherwise, return 0. + * It only checks for fields that are output by control port. + * This should be kept in sync with the struct routerstatus_t + * and the printing function routerstatus_format_entry in + * NS_CONTROL_PORT mode. + **/ +STATIC int +routerstatus_has_visibly_changed(const routerstatus_t *a, + const routerstatus_t *b) { tor_assert(tor_memeq(a->identity_digest, b->identity_digest, DIGEST_LEN)); @@ -1605,9 +1607,14 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) a->is_valid != b->is_valid || a->is_possible_guard != b->is_possible_guard || a->is_bad_exit != b->is_bad_exit || - a->is_hs_dir != b->is_hs_dir; - // XXXX this function needs a huge refactoring; it has gotten out - // XXXX of sync with routerstatus_t, and it will do so again. + a->is_hs_dir != b->is_hs_dir || + a->is_staledesc != b->is_staledesc || + a->has_bandwidth != b->has_bandwidth || + a->published_on != b->published_on || + a->ipv6_orport != b->ipv6_orport || + a->is_v2_dir != b->is_v2_dir || + a->bandwidth_kb != b->bandwidth_kb || + tor_addr_compare(&a->ipv6_addr, &b->ipv6_addr, CMP_EXACT); } /** Notify controllers of any router status entries that changed between @@ -1639,7 +1646,7 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, tor_memcmp(rs_old->identity_digest, rs_new->identity_digest, DIGEST_LEN), smartlist_add(changed, (void*) rs_new)) { - if (routerstatus_has_changed(rs_old, rs_new)) + if (routerstatus_has_visibly_changed(rs_old, rs_new)) smartlist_add(changed, (void*)rs_new); } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new); @@ -1655,6 +1662,7 @@ notify_before_networkstatus_changes(const networkstatus_t *old_c, notify_control_networkstatus_changed(old_c, new_c); dos_consensus_has_changed(new_c); relay_consensus_has_changed(new_c); + hs_dos_consensus_has_changed(new_c); } /* Called after a new consensus has been put in the global state. It is safe @@ -1725,6 +1733,44 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c, #endif /* defined(TOR_UNIT_TESTS) */ /** + * Helper: Read the current consensus of type <b>flavor</b> from + * <b>fname</b>. Flags and return values are as for + * networkstatus_set_current_consensus(). + **/ +static int +reload_consensus_from_file(const char *fname, + const char *flavor, + unsigned flags, + const char *source_dir) +{ + tor_mmap_t *map = tor_mmap_file(fname); + if (!map) + return 0; + + int rv = networkstatus_set_current_consensus(map->data, map->size, + flavor, flags, source_dir); +#ifdef _WIN32 + if (rv < 0 && tor_memstr(map->data, map->size, "\r\n")) { + log_notice(LD_GENERAL, "Looks like the above failures are probably " + "because of a CRLF in consensus file %s; falling back to " + "read_file_to_string. Nothing to worry about: this file " + "was probably saved by an earlier version of Tor.", + escaped(fname)); + char *content = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL); + rv = networkstatus_set_current_consensus(content, strlen(content), + flavor, flags, source_dir); + tor_free(content); + } +#endif /* defined(_WIN32) */ + if (rv < -1) { + log_warn(LD_GENERAL, "Couldn't set consensus from cache file %s", + escaped(fname)); + } + tor_munmap_file(map); + return rv; +} + +/** * Helper for handle_missing_protocol_warning: handles either the * client case (if <b>is_client</b> is set) or the server case otherwise. */ @@ -1841,6 +1887,7 @@ warn_early_consensus(const networkstatus_t *c, const char *flavor, */ int networkstatus_set_current_consensus(const char *consensus, + size_t consensus_len, const char *flavor, unsigned flags, const char *source_dir) @@ -1869,7 +1916,9 @@ networkstatus_set_current_consensus(const char *consensus, } /* Make sure it's parseable. */ - c = networkstatus_parse_vote_from_string(consensus, NULL, NS_TYPE_CONSENSUS); + c = networkstatus_parse_vote_from_string(consensus, + consensus_len, + NULL, NS_TYPE_CONSENSUS); if (!c) { log_warn(LD_DIR, "Unable to parse networkstatus consensus"); result = -2; @@ -1957,14 +2006,12 @@ networkstatus_set_current_consensus(const char *consensus, c->valid_after > current_valid_after) { waiting = &consensus_waiting_for_certs[flav]; networkstatus_vote_free(waiting->consensus); - tor_free(waiting->body); waiting->consensus = c; free_consensus = 0; - waiting->body = tor_strdup(consensus); waiting->set_at = now; waiting->dl_failed = 0; if (!from_cache) { - write_str_to_file(unverified_fname, consensus, 0); + write_bytes_to_file(unverified_fname, consensus, consensus_len, 1); } if (dl_certs) authority_certs_fetch_missing(c, now, source_dir); @@ -1976,9 +2023,9 @@ networkstatus_set_current_consensus(const char *consensus, * latest consensus. */ if (was_waiting_for_certs && from_cache) if (unlink(unverified_fname) != 0) { - log_warn(LD_FS, - "Failed to unlink %s: %s", - unverified_fname, strerror(errno)); + log_debug(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); } } goto done; @@ -1991,9 +2038,9 @@ networkstatus_set_current_consensus(const char *consensus, } if (was_waiting_for_certs && (r < -1) && from_cache) { if (unlink(unverified_fname) != 0) { - log_warn(LD_FS, - "Failed to unlink %s: %s", - unverified_fname, strerror(errno)); + log_debug(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); } } goto done; @@ -2055,16 +2102,12 @@ networkstatus_set_current_consensus(const char *consensus, waiting->consensus->valid_after <= c->valid_after) { networkstatus_vote_free(waiting->consensus); waiting->consensus = NULL; - if (consensus != waiting->body) - tor_free(waiting->body); - else - waiting->body = NULL; waiting->set_at = 0; waiting->dl_failed = 0; if (unlink(unverified_fname) != 0) { - log_warn(LD_FS, - "Failed to unlink %s: %s", - unverified_fname, strerror(errno)); + log_debug(LD_FS, + "Failed to unlink %s: %s", + unverified_fname, strerror(errno)); } } @@ -2082,7 +2125,6 @@ networkstatus_set_current_consensus(const char *consensus, nodelist_set_consensus(c); - /* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/ update_consensus_networkstatus_fetch_time(now); /* Change the cell EWMA settings */ @@ -2095,6 +2137,7 @@ networkstatus_set_current_consensus(const char *consensus, circuit_build_times_new_consensus_params( get_circuit_build_times_mutable(), c); channelpadding_new_consensus_params(c); + circpad_new_consensus_params(c); } /* Reset the failure count only if this consensus is actually valid. */ @@ -2108,17 +2151,18 @@ networkstatus_set_current_consensus(const char *consensus, if (we_want_to_fetch_flavor(options, flav)) { if (dir_server_mode(get_options())) { dirserv_set_cached_consensus_networkstatus(consensus, + consensus_len, flavor, &c->digests, c->digest_sha3_as_signed, c->valid_after); - consdiffmgr_add_consensus(consensus, c); + consdiffmgr_add_consensus(consensus, consensus_len, c); } } if (!from_cache) { - write_str_to_file(consensus_fname, consensus, 0); + write_bytes_to_file(consensus_fname, consensus, consensus_len, 1); } warn_early_consensus(c, flavor, now); @@ -2154,14 +2198,10 @@ networkstatus_note_certs_arrived(const char *source_dir) if (!waiting->consensus) continue; if (networkstatus_check_consensus_signature(waiting->consensus, 0)>=0) { - char *waiting_body = waiting->body; - if (!networkstatus_set_current_consensus( - waiting_body, - flavor_name, - NSSET_WAS_WAITING_FOR_CERTS, - source_dir)) { - tor_free(waiting_body); - } + char *fname = networkstatus_get_cache_fname(i, flavor_name, 1); + reload_consensus_from_file(fname, flavor_name, + NSSET_WAS_WAITING_FOR_CERTS, source_dir); + tor_free(fname); } } } @@ -2311,10 +2351,52 @@ char * networkstatus_getinfo_helper_single(const routerstatus_t *rs) { return routerstatus_format_entry(rs, NULL, NULL, NS_CONTROL_PORT, - ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD, NULL); } +/** + * Extract status information from <b>ri</b> and from other authority + * functions and store it in <b>rs</b>. <b>rs</b> is zeroed out before it is + * set. + * + * We assume that node-\>is_running has already been set, e.g. by + * dirserv_set_router_is_running(ri, now); + */ +void +set_routerstatus_from_routerinfo(routerstatus_t *rs, + const node_t *node, + const routerinfo_t *ri) +{ + memset(rs, 0, sizeof(routerstatus_t)); + + rs->is_authority = + router_digest_is_trusted_dir(ri->cache_info.identity_digest); + + /* Set by compute_performance_thresholds or from consensus */ + rs->is_exit = node->is_exit; + rs->is_stable = node->is_stable; + rs->is_fast = node->is_fast; + rs->is_flagged_running = node->is_running; + rs->is_valid = node->is_valid; + rs->is_possible_guard = node->is_possible_guard; + rs->is_bad_exit = node->is_bad_exit; + rs->is_hs_dir = node->is_hs_dir; + rs->is_named = rs->is_unnamed = 0; + + rs->published_on = ri->cache_info.published_on; + memcpy(rs->identity_digest, node->identity, DIGEST_LEN); + memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest, + DIGEST_LEN); + rs->addr = ri->addr; + strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname)); + rs->or_port = ri->or_port; + rs->dir_port = ri->dir_port; + rs->is_v2_dir = ri->supports_tunnelled_dir_requests; + + tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr); + rs->ipv6_orport = ri->ipv6_orport; +} + /** Alloc and return a string describing routerstatuses for the most * recent info of each router we know about that is of purpose * <b>purpose_string</b>. Return NULL if unrecognized purpose. @@ -2331,7 +2413,6 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) smartlist_t *statuses; const uint8_t purpose = router_purpose_from_string(purpose_string); routerstatus_t rs; - const int bridge_auth = authdir_mode_bridge(get_options()); if (purpose == ROUTER_PURPOSE_UNKNOWN) { log_info(LD_DIR, "Unrecognized purpose '%s' when listing router statuses.", @@ -2348,11 +2429,7 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) continue; if (ri->purpose != purpose) continue; - /* TODO: modifying the running flag in a getinfo is a bad idea */ - if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE) - dirserv_set_router_is_running(ri, now); - /* then generate and write out status lines for each of them */ - set_routerstatus_from_routerinfo(&rs, node, ri, now, 0); + set_routerstatus_from_routerinfo(&rs, node, ri); smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs)); } SMARTLIST_FOREACH_END(ri); @@ -2362,41 +2439,6 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) return answer; } -/** Write out router status entries for all our bridge descriptors. */ -void -networkstatus_dump_bridge_status_to_file(time_t now) -{ - char *status = networkstatus_getinfo_by_purpose("bridge", now); - char *fname = NULL; - char *thresholds = NULL; - char *published_thresholds_and_status = NULL; - char published[ISO_TIME_LEN+1]; - const routerinfo_t *me = router_get_my_routerinfo(); - char fingerprint[FINGERPRINT_LEN+1]; - char *fingerprint_line = NULL; - - if (me && crypto_pk_get_fingerprint(me->identity_pkey, - fingerprint, 0) >= 0) { - tor_asprintf(&fingerprint_line, "fingerprint %s\n", fingerprint); - } else { - log_warn(LD_BUG, "Error computing fingerprint for bridge status."); - } - format_iso_time(published, now); - dirserv_compute_bridge_flag_thresholds(); - thresholds = dirserv_get_flag_thresholds_line(); - tor_asprintf(&published_thresholds_and_status, - "published %s\nflag-thresholds %s\n%s%s", - published, thresholds, fingerprint_line ? fingerprint_line : "", - status); - fname = get_datadir_fname("networkstatus-bridges"); - write_str_to_file(fname,published_thresholds_and_status,0); - tor_free(thresholds); - tor_free(published_thresholds_and_status); - tor_free(fname); - tor_free(status); - tor_free(fingerprint_line); -} - /* DOCDOC get_net_param_from_list */ static int32_t get_net_param_from_list(smartlist_t *net_params, const char *param_name, @@ -2668,6 +2710,9 @@ networkstatus_check_required_protocols(const networkstatus_t *ns, const char *required, *recommended; char *missing = NULL; + const bool consensus_postdates_this_release = + ns->valid_after >= tor_get_approx_release_date(); + tor_assert(warning_out); if (client_mode) { @@ -2685,7 +2730,7 @@ networkstatus_check_required_protocols(const networkstatus_t *ns, "%s on the Tor network. The missing protocols are: %s", func, missing); tor_free(missing); - return 1; + return consensus_postdates_this_release ? 1 : 0; } if (! protover_all_supported(recommended, &missing)) { @@ -2718,6 +2763,5 @@ networkstatus_free_all(void) networkstatus_vote_free(waiting->consensus); waiting->consensus = NULL; } - tor_free(waiting->body); } } diff --git a/src/feature/nodelist/networkstatus.h b/src/feature/nodelist/networkstatus.h index 9e7b0f1bb0..5e8c8a9e57 100644 --- a/src/feature/nodelist/networkstatus.h +++ b/src/feature/nodelist/networkstatus.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,7 +16,10 @@ void networkstatus_reset_warnings(void); void networkstatus_reset_download_failures(void); -char *networkstatus_read_cached_consensus(const char *flavorname); +MOCK_DECL(char *,networkstatus_get_cache_fname,(int flav, + const char *flavorname, + int unverified_consensus)); +tor_mmap_t *networkstatus_map_cached_consensus(const char *flavorname); int router_reload_consensus_networkstatus(void); void routerstatus_free_(routerstatus_t *rs); #define routerstatus_free(rs) \ @@ -40,8 +43,9 @@ int compare_digest_to_routerstatus_entry(const void *_key, const void **_member); int compare_digest_to_vote_routerstatus_entry(const void *_key, const void **_member); -const routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, - const char *digest); +MOCK_DECL(const routerstatus_t *,networkstatus_vote_find_entry,( + networkstatus_t *ns, + const char *digest)); routerstatus_t *networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest); int networkstatus_vote_find_entry_idx(networkstatus_t *ns, @@ -87,6 +91,8 @@ MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); int networkstatus_is_live(const networkstatus_t *ns, time_t now); int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); +int networkstatus_valid_after_is_reasonably_live(time_t valid_after, + time_t now); int networkstatus_valid_until_is_reasonably_live(time_t valid_until, time_t now); MOCK_DECL(networkstatus_t *,networkstatus_get_reasonably_live_consensus, @@ -98,7 +104,6 @@ int networkstatus_consensus_can_use_multiple_directories( MOCK_DECL(int, networkstatus_consensus_can_use_extra_fallbacks,( const or_options_t *options)); int networkstatus_consensus_is_already_downloading(const char *resource); -int networkstatus_consensus_has_ipv6(const or_options_t* options); #define NSSET_FROM_CACHE 1 #define NSSET_WAS_WAITING_FOR_CERTS 2 @@ -106,6 +111,7 @@ int networkstatus_consensus_has_ipv6(const or_options_t* options); #define NSSET_ACCEPT_OBSOLETE 8 #define NSSET_REQUIRE_FLAVOR 16 int networkstatus_set_current_consensus(const char *consensus, + size_t consensus_len, const char *flavor, unsigned flags, const char *source_dir); @@ -118,7 +124,6 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); -void networkstatus_dump_bridge_status_to_file(time_t now); MOCK_DECL(int32_t, networkstatus_get_param, (const networkstatus_t *ns, const char *param_name, int32_t default_val, int32_t min_val, int32_t max_val)); @@ -145,6 +150,10 @@ void vote_routerstatus_free_(vote_routerstatus_t *rs); #define vote_routerstatus_free(rs) \ FREE_AND_NULL(vote_routerstatus_t, vote_routerstatus_free_, (rs)) +void set_routerstatus_from_routerinfo(routerstatus_t *rs, + const node_t *node, + const routerinfo_t *ri); + #ifdef NETWORKSTATUS_PRIVATE #ifdef TOR_UNIT_TESTS STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c, @@ -154,7 +163,8 @@ STATIC void warn_early_consensus(const networkstatus_t *c, const char *flavor, extern networkstatus_t *current_ns_consensus; extern networkstatus_t *current_md_consensus; #endif /* defined(TOR_UNIT_TESTS) */ +STATIC int routerstatus_has_visibly_changed(const routerstatus_t *a, + const routerstatus_t *b); #endif /* defined(NETWORKSTATUS_PRIVATE) */ #endif /* !defined(TOR_NETWORKSTATUS_H) */ - diff --git a/src/feature/nodelist/networkstatus_sr_info_st.h b/src/feature/nodelist/networkstatus_sr_info_st.h index 677d8ed811..04d0dfe8f6 100644 --- a/src/feature/nodelist/networkstatus_sr_info_st.h +++ b/src/feature/nodelist/networkstatus_sr_info_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file networkstatus_sr_info_st.h + * @brief Shared-randomness structure. + **/ + #ifndef NETWORKSTATUS_SR_INFO_ST_H #define NETWORKSTATUS_SR_INFO_ST_H @@ -19,5 +24,4 @@ struct networkstatus_sr_info_t { smartlist_t *commits; }; -#endif - +#endif /* !defined(NETWORKSTATUS_SR_INFO_ST_H) */ diff --git a/src/feature/nodelist/networkstatus_st.h b/src/feature/nodelist/networkstatus_st.h index 6160f12361..021168d3ca 100644 --- a/src/feature/nodelist/networkstatus_st.h +++ b/src/feature/nodelist/networkstatus_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file networkstatus_st.h + * @brief Networkstatus consensus/vote structure. + **/ + #ifndef NETWORKSTATUS_ST_H #define NETWORKSTATUS_ST_H @@ -99,6 +104,9 @@ struct networkstatus_t { /** List of key=value strings from the headers of the bandwidth list file */ smartlist_t *bw_file_headers; + + /** A SHA256 digest of the bandwidth file used in a vote. */ + uint8_t bw_file_digest256[DIGEST256_LEN]; }; -#endif +#endif /* !defined(NETWORKSTATUS_ST_H) */ diff --git a/src/feature/nodelist/networkstatus_voter_info_st.h b/src/feature/nodelist/networkstatus_voter_info_st.h index 4037fcdeca..b4d0b1dd17 100644 --- a/src/feature/nodelist/networkstatus_voter_info_st.h +++ b/src/feature/nodelist/networkstatus_voter_info_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file networkstatus_voter_info_st.h + * @brief Single consensus voter structure. + **/ + #ifndef NETWORKSTATUS_VOTER_INFO_ST_H #define NETWORKSTATUS_VOTER_INFO_ST_H @@ -27,4 +32,4 @@ struct networkstatus_voter_info_t { smartlist_t *sigs; }; -#endif +#endif /* !defined(NETWORKSTATUS_VOTER_INFO_ST_H) */ diff --git a/src/feature/nodelist/nickname.c b/src/feature/nodelist/nickname.c index 5378b749ca..c022dd6bc4 100644 --- a/src/feature/nodelist/nickname.c +++ b/src/feature/nodelist/nickname.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/nodelist/nickname.h b/src/feature/nodelist/nickname.h index 9bdc6b50e8..11c6416f3a 100644 --- a/src/feature/nodelist/nickname.h +++ b/src/feature/nodelist/nickname.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -16,4 +16,4 @@ int is_legal_nickname(const char *s); int is_legal_nickname_or_hexdigest(const char *s); int is_legal_hexdigest(const char *s); -#endif +#endif /* !defined(TOR_NICKNAME_H) */ diff --git a/src/feature/nodelist/node_select.c b/src/feature/nodelist/node_select.c index 7b9e241e5b..e831248413 100644 --- a/src/feature/nodelist/node_select.c +++ b/src/feature/nodelist/node_select.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,6 +19,7 @@ #include "core/or/reasons.h" #include "feature/client/entrynodes.h" #include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/describe.h" #include "feature/nodelist/dirlist.h" @@ -30,6 +31,7 @@ #include "feature/nodelist/routerset.h" #include "feature/relay/router.h" #include "feature/relay/routermode.h" +#include "lib/container/bitarray.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/math/fp.h" @@ -146,7 +148,7 @@ router_pick_dirserver_generic(smartlist_t *sourcelist, try_ip_pref = 0; \ goto retry_label; \ } \ - STMT_END \ + STMT_END /* Common retry code for router_pick_directory_server_impl and * router_pick_trusteddirserver_impl. Retry without excluding nodes, but with @@ -321,7 +323,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, const int skip_or_fw = router_skip_or_reachability(options, try_ip_pref); const int skip_dir_fw = router_skip_dir_reachability(options, try_ip_pref); - const int must_have_or = directory_must_use_begindir(options); + const int must_have_or = dirclient_must_use_begindir(options); /* Find all the running dirservers we know about. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { @@ -630,6 +632,7 @@ compute_weighted_bandwidths(const smartlist_t *sl, } weight_scale = networkstatus_get_weight_scale_param(NULL); + tor_assert(weight_scale >= 1); if (rule == WEIGHT_FOR_GUARD) { Wg = networkstatus_get_bw_weight(NULL, "Wgg", -1); @@ -871,6 +874,58 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) nodelist_add_node_and_family(sl, node); } +/** + * Remove every node_t that appears in <b>excluded</b> from <b>sl</b>. + * + * Behaves like smartlist_subtract, but uses nodelist_idx values to deliver + * linear performance when smartlist_subtract would be quadratic. + **/ +static void +nodelist_subtract(smartlist_t *sl, const smartlist_t *excluded) +{ + const smartlist_t *nodelist = nodelist_get_list(); + const int nodelist_len = smartlist_len(nodelist); + bitarray_t *excluded_idx = bitarray_init_zero(nodelist_len); + + /* We haven't used nodelist_idx in this way previously, so I'm going to be + * paranoid in this code, and check that nodelist_idx is correct for every + * node before we use it. If we fail, we fall back to smartlist_subtract(). + */ + + /* Set the excluded_idx bit corresponding to every excluded node... + */ + SMARTLIST_FOREACH_BEGIN(excluded, const node_t *, node) { + const int idx = node->nodelist_idx; + if (BUG(idx < 0) || BUG(idx >= nodelist_len) || + BUG(node != smartlist_get(nodelist, idx))) { + goto internal_error; + } + bitarray_set(excluded_idx, idx); + } SMARTLIST_FOREACH_END(node); + + /* Then remove them from sl. + */ + SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { + const int idx = node->nodelist_idx; + if (BUG(idx < 0) || BUG(idx >= nodelist_len) || + BUG(node != smartlist_get(nodelist, idx))) { + goto internal_error; + } + if (bitarray_is_set(excluded_idx, idx)) { + SMARTLIST_DEL_CURRENT(sl, node); + } + } SMARTLIST_FOREACH_END(node); + + bitarray_free(excluded_idx); + return; + + internal_error: + log_warn(LD_BUG, "Internal error prevented us from using the fast method " + "for subtracting nodelists. Falling back to the quadratic way."); + smartlist_subtract(sl, excluded); + bitarray_free(excluded_idx); +} + /** Return a random running node from the nodelist. Never * pick a node that is in * <b>excludedsmartlist</b>, or which matches <b>excludedset</b>, @@ -905,6 +960,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; + const smartlist_t *node_list = nodelist_get_list(); smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); const node_t *choice = NULL; @@ -915,17 +971,17 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + SMARTLIST_FOREACH_BEGIN(node_list, const node_t *, node) { if (node_allows_single_hop_exits(node)) { /* Exclude relays that allow single hop exit circuits. This is an * obsolete option since 0.2.9.2-alpha and done by default in * 0.3.1.0-alpha. */ - smartlist_add(excludednodes, node); + smartlist_add(excludednodes, (node_t*)node); } else if (rendezvous_v3 && !node_supports_v3_rendezvous_point(node)) { /* Exclude relays that do not support to rendezvous for a hidden service * version 3. */ - smartlist_add(excludednodes, node); + smartlist_add(excludednodes, (node_t*)node); } } SMARTLIST_FOREACH_END(node); @@ -942,19 +998,11 @@ router_choose_random_node(smartlist_t *excludedsmartlist, "We found %d running nodes.", smartlist_len(sl)); - smartlist_subtract(sl,excludednodes); - log_debug(LD_CIRC, - "We removed %d excludednodes, leaving %d nodes.", - smartlist_len(excludednodes), - smartlist_len(sl)); - if (excludedsmartlist) { - smartlist_subtract(sl,excludedsmartlist); - log_debug(LD_CIRC, - "We removed %d excludedsmartlist, leaving %d nodes.", - smartlist_len(excludedsmartlist), - smartlist_len(sl)); + smartlist_add_all(excludednodes, excludedsmartlist); } + nodelist_subtract(sl, excludednodes); + if (excludedset) { routerset_subtract_nodes(sl,excludedset); log_debug(LD_CIRC, @@ -1074,7 +1122,7 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, const int skip_or_fw = router_skip_or_reachability(options, try_ip_pref); const int skip_dir_fw = router_skip_dir_reachability(options, try_ip_pref); - const int must_have_or = directory_must_use_begindir(options); + const int must_have_or = dirclient_must_use_begindir(options); SMARTLIST_FOREACH_BEGIN(sourcelist, const dir_server_t *, d) { diff --git a/src/feature/nodelist/node_select.h b/src/feature/nodelist/node_select.h index ed7450b92c..2e67f990f6 100644 --- a/src/feature/nodelist/node_select.h +++ b/src/feature/nodelist/node_select.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -97,6 +97,6 @@ STATIC const routerstatus_t *router_pick_directory_server_impl( int *n_busy_out); STATIC int router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc, int microdesc); -#endif +#endif /* defined(NODE_SELECT_PRIVATE) */ -#endif +#endif /* !defined(TOR_NODE_SELECT_H) */ diff --git a/src/feature/nodelist/node_st.h b/src/feature/nodelist/node_st.h index 53ffde29e4..b1ec4db202 100644 --- a/src/feature/nodelist/node_st.h +++ b/src/feature/nodelist/node_st.h @@ -1,14 +1,20 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file node_st.h + * @brief Node information structure. + **/ + #ifndef NODE_ST_H #define NODE_ST_H #include "feature/hs/hsdir_index_st.h" #include "lib/crypt_ops/crypto_ed25519.h" +#include "ext/ht.h" /** A node_t represents a Tor router. * @@ -99,4 +105,4 @@ struct node_t { struct hsdir_index_t hsdir_index; }; -#endif +#endif /* !defined(NODE_ST_H) */ diff --git a/src/feature/nodelist/nodefamily.c b/src/feature/nodelist/nodefamily.c new file mode 100644 index 0000000000..7ae8620749 --- /dev/null +++ b/src/feature/nodelist/nodefamily.c @@ -0,0 +1,416 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file nodefamily.c + * \brief Code to manipulate encoded, reference-counted node families. We + * use these tricks to save space, since these families would otherwise + * require a large number of tiny allocations. + **/ + +#include "core/or/or.h" +#include "feature/nodelist/nickname.h" +#include "feature/nodelist/nodefamily.h" +#include "feature/nodelist/nodefamily_st.h" +#include "feature/nodelist/nodelist.h" +#include "feature/relay/router.h" +#include "feature/nodelist/routerlist.h" + +#include "ht.h" +#include "siphash.h" + +#include "lib/container/smartlist.h" +#include "lib/ctime/di_ops.h" +#include "lib/defs/digest_sizes.h" +#include "lib/log/util_bug.h" + +#include <stdlib.h> +#include <string.h> + +/** + * Allocate and return a blank node family with space to hold <b>n_members</b> + * members. + */ +static nodefamily_t * +nodefamily_alloc(int n_members) +{ + size_t alloc_len = offsetof(nodefamily_t, family_members) + + NODEFAMILY_ARRAY_SIZE(n_members); + nodefamily_t *nf = tor_malloc_zero(alloc_len); + nf->n_members = n_members; + return nf; +} + +/** + * Hashtable hash implementation. + */ +static inline unsigned int +nodefamily_hash(const nodefamily_t *nf) +{ + return (unsigned) siphash24g(nf->family_members, + NODEFAMILY_ARRAY_SIZE(nf->n_members)); +} + +/** + * Hashtable equality implementation. + */ +static inline unsigned int +nodefamily_eq(const nodefamily_t *a, const nodefamily_t *b) +{ + return (a->n_members == b->n_members) && + fast_memeq(a->family_members, b->family_members, + NODEFAMILY_ARRAY_SIZE(a->n_members)); +} + +static HT_HEAD(nodefamily_map, nodefamily_t) the_node_families + = HT_INITIALIZER(); + +HT_PROTOTYPE(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash, + nodefamily_eq) +HT_GENERATE2(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash, + node_family_eq, 0.6, tor_reallocarray_, tor_free_) + +/** + * Parse the family declaration in <b>s</b>, returning the canonical + * <b>nodefamily_t</b> for its members. Return NULL on error. + * + * If <b>rsa_id_self</b> is provided, it is a DIGEST_LEN-byte digest + * for the router that declared this family: insert it into the + * family declaration if it is not there already. + * + * If NF_WARN_MALFORMED is set in <b>flags</b>, warn about any + * elements that we can't parse. (By default, we log at info.) + * + * If NF_REJECT_MALFORMED is set in <b>flags</b>, treat any unparseable + * elements as an error. (By default, we simply omit them.) + **/ +nodefamily_t * +nodefamily_parse(const char *s, const uint8_t *rsa_id_self, + unsigned flags) +{ + smartlist_t *sl = smartlist_new(); + smartlist_split_string(sl, s, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + nodefamily_t *result = nodefamily_from_members(sl, rsa_id_self, flags, NULL); + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + return result; +} + +/** + * Canonicalize the family list <b>s</b>, returning a newly allocated string. + * + * The canonicalization rules are fully specified in dir-spec.txt, but, + * briefly: $hexid entries are put in caps, $hexid[=~]foo entries are + * truncated, nicknames are put into lowercase, unrecognized entries are left + * alone, and everything is sorted. + **/ +char * +nodefamily_canonicalize(const char *s, const uint8_t *rsa_id_self, + unsigned flags) +{ + smartlist_t *sl = smartlist_new(); + smartlist_t *result_members = smartlist_new(); + smartlist_split_string(sl, s, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + nodefamily_t *nf = nodefamily_from_members(sl, rsa_id_self, flags, + result_members); + + char *formatted = nodefamily_format(nf); + smartlist_split_string(result_members, formatted, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + smartlist_sort_strings(result_members); + char *combined = smartlist_join_strings(result_members, " ", 0, NULL); + + nodefamily_free(nf); + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + SMARTLIST_FOREACH(result_members, char *, cp, tor_free(cp)); + smartlist_free(result_members); + tor_free(formatted); + + return combined; +} + +/** + * qsort helper for encoded nodefamily elements. + **/ +static int +compare_members(const void *a, const void *b) +{ + return fast_memcmp(a, b, NODEFAMILY_MEMBER_LEN); +} + +/** + * Parse the member strings in <b>members</b>, returning their canonical + * <b>nodefamily_t</b>. Return NULL on error. + * + * If <b>rsa_id_self</b> is provided, it is a DIGEST_LEN-byte digest + * for the router that declared this family: insert it into the + * family declaration if it is not there already. + * + * The <b>flags</b> element is interpreted as in nodefamily_parse(). + * + * If <b>unrecognized</b> is provided, fill it copies of any unrecognized + * members. (Note that malformed $hexids are not considered unrecognized.) + **/ +nodefamily_t * +nodefamily_from_members(const smartlist_t *members, + const uint8_t *rsa_id_self, + unsigned flags, + smartlist_t *unrecognized_out) +{ + const int n_self = rsa_id_self ? 1 : 0; + int n_bad_elements = 0; + int n_members = smartlist_len(members) + n_self; + nodefamily_t *tmp = nodefamily_alloc(n_members); + uint8_t *ptr = NODEFAMILY_MEMBER_PTR(tmp, 0); + + SMARTLIST_FOREACH_BEGIN(members, const char *, cp) { + bool bad_element = true; + if (is_legal_nickname(cp)) { + ptr[0] = NODEFAMILY_BY_NICKNAME; + tor_assert(strlen(cp) < DIGEST_LEN); // guaranteed by is_legal_nickname + memcpy(ptr+1, cp, strlen(cp)); + tor_strlower((char*) ptr+1); + bad_element = false; + } else if (is_legal_hexdigest(cp)) { + char digest_buf[DIGEST_LEN]; + char nn_buf[MAX_NICKNAME_LEN+1]; + char nn_char=0; + if (hex_digest_nickname_decode(cp, digest_buf, &nn_char, nn_buf)==0) { + bad_element = false; + ptr[0] = NODEFAMILY_BY_RSA_ID; + memcpy(ptr+1, digest_buf, DIGEST_LEN); + } + } else { + if (unrecognized_out) + smartlist_add_strdup(unrecognized_out, cp); + } + + if (bad_element) { + const int severity = (flags & NF_WARN_MALFORMED) ? LOG_WARN : LOG_INFO; + log_fn(severity, LD_GENERAL, + "Bad element %s while parsing a node family.", + escaped(cp)); + ++n_bad_elements; + } else { + ptr += NODEFAMILY_MEMBER_LEN; + } + } SMARTLIST_FOREACH_END(cp); + + if (n_bad_elements && (flags & NF_REJECT_MALFORMED)) + goto err; + + if (rsa_id_self) { + /* Add self. */ + ptr[0] = NODEFAMILY_BY_RSA_ID; + memcpy(ptr+1, rsa_id_self, DIGEST_LEN); + } + + n_members -= n_bad_elements; + + /* Sort tmp into canonical order. */ + qsort(tmp->family_members, n_members, NODEFAMILY_MEMBER_LEN, + compare_members); + + /* Remove duplicates. */ + int i; + for (i = 0; i < n_members-1; ++i) { + uint8_t *thisptr = NODEFAMILY_MEMBER_PTR(tmp, i); + uint8_t *nextptr = NODEFAMILY_MEMBER_PTR(tmp, i+1); + if (fast_memeq(thisptr, nextptr, NODEFAMILY_MEMBER_LEN)) { + memmove(thisptr, nextptr, (n_members-i-1)*NODEFAMILY_MEMBER_LEN); + --n_members; + --i; + } + } + int n_members_alloc = tmp->n_members; + tmp->n_members = n_members; + + /* See if we already allocated this family. */ + nodefamily_t *found = HT_FIND(nodefamily_map, &the_node_families, tmp); + if (found) { + /* If we did, great: incref it and return it. */ + ++found->refcnt; + tor_free(tmp); + return found; + } else { + /* If not, insert it into the hashtable. */ + if (n_members_alloc != n_members) { + /* Compact the family if needed */ + nodefamily_t *tmp2 = nodefamily_alloc(n_members); + memcpy(tmp2->family_members, tmp->family_members, + n_members * NODEFAMILY_MEMBER_LEN); + tor_free(tmp); + tmp = tmp2; + } + + tmp->refcnt = 1; + HT_INSERT(nodefamily_map, &the_node_families, tmp); + return tmp; + } + + err: + tor_free(tmp); + return NULL; +} + +/** + * Drop our reference to <b>family</b>, freeing it if there are no more + * references. + */ +void +nodefamily_free_(nodefamily_t *family) +{ + if (family == NULL) + return; + + --family->refcnt; + + if (family->refcnt == 0) { + HT_REMOVE(nodefamily_map, &the_node_families, family); + tor_free(family); + } +} + +/** + * Return true iff <b>family</b> contains the SHA1 RSA1024 identity + * <b>rsa_id</b>. + */ +bool +nodefamily_contains_rsa_id(const nodefamily_t *family, + const uint8_t *rsa_id) +{ + if (family == NULL) + return false; + + unsigned i; + for (i = 0; i < family->n_members; ++i) { + const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i); + if (ptr[0] == NODEFAMILY_BY_RSA_ID && + fast_memeq(ptr+1, rsa_id, DIGEST_LEN)) { + return true; + } + } + return false; +} + +/** + * Return true iff <b>family</b> contains the nickname <b>name</b>. + */ +bool +nodefamily_contains_nickname(const nodefamily_t *family, + const char *name) +{ + if (family == NULL) + return false; + + unsigned i; + for (i = 0; i < family->n_members; ++i) { + const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i); + // note that the strcasecmp() is safe because there is always at least one + // NUL in the encoded nickname, because all legal nicknames are less than + // DIGEST_LEN bytes long. + if (ptr[0] == NODEFAMILY_BY_NICKNAME && !strcasecmp((char*)ptr+1, name)) { + return true; + } + } + return false; +} + +/** + * Return true if <b>family</b> contains the nickname or the RSA ID for + * <b>node</b> + **/ +bool +nodefamily_contains_node(const nodefamily_t *family, + const node_t *node) +{ + return + nodefamily_contains_nickname(family, node_get_nickname(node)) + || + nodefamily_contains_rsa_id(family, node_get_rsa_id_digest(node)); +} + +/** + * Look up every entry in <b>family</b>, and add add the corresponding + * node_t to <b>out</b>. + **/ +void +nodefamily_add_nodes_to_smartlist(const nodefamily_t *family, + smartlist_t *out) +{ + if (!family) + return; + + unsigned i; + for (i = 0; i < family->n_members; ++i) { + const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i); + const node_t *node = NULL; + switch (ptr[0]) { + case NODEFAMILY_BY_NICKNAME: + node = node_get_by_nickname((char*)ptr+1, NNF_NO_WARN_UNNAMED); + break; + case NODEFAMILY_BY_RSA_ID: + node = node_get_by_id((char*)ptr+1); + break; + default: + /* LCOV_EXCL_START */ + tor_assert_nonfatal_unreached(); + break; + /* LCOV_EXCL_STOP */ + } + if (node) + smartlist_add(out, (void *)node); + } +} + +/** + * Encode <b>family</b> as a space-separated string. + */ +char * +nodefamily_format(const nodefamily_t *family) +{ + if (!family) + return tor_strdup(""); + + unsigned i; + smartlist_t *sl = smartlist_new(); + for (i = 0; i < family->n_members; ++i) { + const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i); + switch (ptr[0]) { + case NODEFAMILY_BY_NICKNAME: + smartlist_add_strdup(sl, (char*)ptr+1); + break; + case NODEFAMILY_BY_RSA_ID: { + char buf[HEX_DIGEST_LEN+2]; + buf[0]='$'; + base16_encode(buf+1, sizeof(buf)-1, (char*)ptr+1, DIGEST_LEN); + tor_strupper(buf); + smartlist_add_strdup(sl, buf); + break; + } + default: + /* LCOV_EXCL_START */ + tor_assert_nonfatal_unreached(); + break; + /* LCOV_EXCL_STOP */ + } + } + + char *result = smartlist_join_strings(sl, " ", 0, NULL); + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + return result; +} + +/** + * Free all storage held in the nodefamily map. + **/ +void +nodefamily_free_all(void) +{ + HT_CLEAR(nodefamily_map, &the_node_families); +} diff --git a/src/feature/nodelist/nodefamily.h b/src/feature/nodelist/nodefamily.h new file mode 100644 index 0000000000..16e161ba82 --- /dev/null +++ b/src/feature/nodelist/nodefamily.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file nodefamily.h + * \brief Header file for nodefamily.c. + **/ + +#ifndef TOR_NODEFAMILY_H +#define TOR_NODEFAMILY_H + +#include "lib/malloc/malloc.h" +#include <stdbool.h> + +typedef struct nodefamily_t nodefamily_t; +struct node_t; +struct smartlist_t; + +#define NF_WARN_MALFORMED (1u<<0) +#define NF_REJECT_MALFORMED (1u<<1) + +nodefamily_t *nodefamily_parse(const char *s, + const uint8_t *rsa_id_self, + unsigned flags); +nodefamily_t *nodefamily_from_members(const struct smartlist_t *members, + const uint8_t *rsa_id_self, + unsigned flags, + smartlist_t *unrecognized_out); +void nodefamily_free_(nodefamily_t *family); +#define nodefamily_free(family) \ + FREE_AND_NULL(nodefamily_t, nodefamily_free_, (family)) + +bool nodefamily_contains_rsa_id(const nodefamily_t *family, + const uint8_t *rsa_id); +bool nodefamily_contains_nickname(const nodefamily_t *family, + const char *name); +bool nodefamily_contains_node(const nodefamily_t *family, + const struct node_t *node); +void nodefamily_add_nodes_to_smartlist(const nodefamily_t *family, + struct smartlist_t *out); +char *nodefamily_format(const nodefamily_t *family); +char *nodefamily_canonicalize(const char *s, const uint8_t *rsa_id_self, + unsigned flags); + +void nodefamily_free_all(void); + +#endif /* !defined(TOR_NODEFAMILY_H) */ diff --git a/src/feature/nodelist/nodefamily_st.h b/src/feature/nodelist/nodefamily_st.h new file mode 100644 index 0000000000..c581c917a9 --- /dev/null +++ b/src/feature/nodelist/nodefamily_st.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file nodefamily_st.h + * @brief Compact node-family structure + **/ + +#ifndef TOR_NODEFAMILY_ST_H +#define TOR_NODEFAMILY_ST_H + +#include "orconfig.h" +#include "ht.h" + +struct nodefamily_t { + /** Entry for this nodefamily_t within the hashtable. */ + HT_ENTRY(nodefamily_t) ht_ent; + /** Reference count. (The hashtable is not treated as a reference */ + uint32_t refcnt; + /** Number of items encoded in <b>family_members</b>. */ + uint32_t n_members; + /* A byte-array encoding the members of this family. We encode each member + * as one byte to indicate whether it's a nickname or a fingerprint, plus + * DIGEST_LEN bytes of data. The entries are lexically sorted. + */ + uint8_t family_members[FLEXIBLE_ARRAY_MEMBER]; +}; + +#define NODEFAMILY_MEMBER_LEN (1+DIGEST_LEN) + +/** Tag byte, indicates that the following bytes are a RSA1024 SHA1 ID. + */ +#define NODEFAMILY_BY_RSA_ID 0 +/** Tag byte, indicates that the following bytes are a NUL-padded nickname. + */ +#define NODEFAMILY_BY_NICKNAME 1 + +/** + * Number of bytes to allocate in the array for a nodefamily_t with N members. + **/ +#define NODEFAMILY_ARRAY_SIZE(n) \ + ((n) * NODEFAMILY_MEMBER_LEN) + +/** + * Pointer to the i'th member of <b>nf</b>, as encoded. + */ +#define NODEFAMILY_MEMBER_PTR(nf, i) \ + (&((nf)->family_members[(i) * NODEFAMILY_MEMBER_LEN])) + +#endif /* !defined(TOR_NODEFAMILY_ST_H) */ diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 9d56f2c3aa..3c218a5931 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -49,9 +49,9 @@ #include "core/or/protover.h" #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/process_descs.h" -#include "feature/dircache/dirserv.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/hs/hs_client.h" #include "feature/hs/hs_common.h" #include "feature/nodelist/describe.h" @@ -59,6 +59,7 @@ #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/node_select.h" +#include "feature/nodelist/nodefamily.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/nodelist/routerset.h" @@ -702,7 +703,8 @@ nodelist_set_consensus(networkstatus_t *ns) SMARTLIST_FOREACH(the_nodelist->nodes, node_t *, node, node->rs = NULL); - /* Conservatively estimate that every node will have 2 addresses. */ + /* Conservatively estimate that every node will have 2 addresses (v4 and + * v6). Then we add the number of configured trusted authorities we have. */ int estimated_addresses = smartlist_len(ns->routerstatus_list) * get_estimated_address_per_node(); estimated_addresses += (get_n_authorities(V3_DIRINFO | BRIDGE_DIRINFO) * @@ -1045,7 +1047,7 @@ nodelist_ensure_freshness(networkstatus_t *ns) /** Return a list of a node_t * for every node we know about. The caller * MUST NOT modify the list. (You can set and clear flags in the nodes if * you must, but you must not add or remove nodes.) */ -MOCK_IMPL(smartlist_t *, +MOCK_IMPL(const smartlist_t *, nodelist_get_list,(void)) { init_nodelist(); @@ -1207,7 +1209,7 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id) /** Dummy object that should be unreturnable. Used to ensure that * node_get_protover_summary_flags() always returns non-NULL. */ static const protover_summary_flags_t zero_protover_flags = { - 0,0,0,0,0,0,0 + 0,0,0,0,0,0,0,0,0 }; /** Return the protover_summary_flags for a given node. */ @@ -1267,6 +1269,17 @@ node_supports_ed25519_hs_intro(const node_t *node) return node_get_protover_summary_flags(node)->supports_ed25519_hs_intro; } +/** Return true iff <b>node</b> supports the DoS ESTABLISH_INTRO cell + * extenstion. */ +int +node_supports_establish_intro_dos_extension(const node_t *node) +{ + tor_assert(node); + + return node_get_protover_summary_flags(node)-> + supports_establish_intro_dos_extension; +} + /** Return true iff <b>node</b> supports to be a rendezvous point for hidden * service version 3 (HSRend=2). */ int @@ -1290,6 +1303,102 @@ node_get_rsa_id_digest(const node_t *node) return (const uint8_t*)node->identity; } +/* Returns a new smartlist with all possible link specifiers from node: + * - legacy ID is mandatory thus MUST be present in node; + * - include ed25519 link specifier if present in the node, and the node + * supports ed25519 link authentication, and: + * - if direct_conn is true, its link versions are compatible with us, + * - if direct_conn is false, regardless of its link versions; + * - include IPv4 link specifier, if the primary address is not IPv4, log a + * BUG() warning, and return an empty smartlist; + * - include IPv6 link specifier if present in the node. + * + * If node is NULL, returns an empty smartlist. + * + * The smartlist must be freed using link_specifier_smartlist_free(). */ +smartlist_t * +node_get_link_specifier_smartlist(const node_t *node, bool direct_conn) +{ + link_specifier_t *ls; + tor_addr_port_t ap; + smartlist_t *lspecs = smartlist_new(); + + if (!node) + return lspecs; + + /* Get the relay's IPv4 address. */ + node_get_prim_orport(node, &ap); + + /* We expect the node's primary address to be a valid IPv4 address. + * This conforms to the protocol, which requires either an IPv4 or IPv6 + * address (or both). */ + if (BUG(!tor_addr_is_v4(&ap.addr)) || + BUG(!tor_addr_port_is_valid_ap(&ap, 0))) { + return lspecs; + } + + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ap.addr)); + link_specifier_set_un_ipv4_port(ls, ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(ap.addr.addr.in_addr) + + sizeof(ap.port)); + smartlist_add(lspecs, ls); + + /* Legacy ID is mandatory and will always be present in node. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memcpy(link_specifier_getarray_un_legacy_id(ls), node->identity, + link_specifier_getlen_un_legacy_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); + smartlist_add(lspecs, ls); + + /* ed25519 ID is only included if the node has it, and the node declares a + protocol version that supports ed25519 link authentication. + If direct_conn is true, we also require that the node's link version is + compatible with us. (Otherwise, we will be sending the ed25519 key + to another tor, which may support different link versions.) */ + if (!ed25519_public_key_is_zero(&node->ed25519_id) && + node_supports_ed25519_link_authentication(node, direct_conn)) { + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &node->ed25519_id, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + } + + /* Check for IPv6. If so, include it as well. */ + if (node_has_ipv6_orport(node)) { + ls = link_specifier_new(); + node_get_pref_ipv6_orport(node, &ap); + link_specifier_set_ls_type(ls, LS_IPV6); + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(ap.port)); + smartlist_add(lspecs, ls); + } + + return lspecs; +} + +/* Free a link specifier list. */ +void +link_specifier_smartlist_free_(smartlist_t *ls_list) +{ + if (!ls_list) + return; + + SMARTLIST_FOREACH(ls_list, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(ls_list); +} + /** Return the nickname of <b>node</b>, or NULL if we can't find one. */ const char * node_get_nickname(const node_t *node) @@ -1429,8 +1538,7 @@ node_exit_policy_rejects_all(const node_t *node) if (node->ri) return node->ri->policy_is_reject_star; else if (node->md) - return node->md->exit_policy == NULL || - short_policy_is_reject_star(node->md->exit_policy); + return node->md->policy_is_reject_star; else return 1; } @@ -1605,19 +1713,6 @@ node_is_me(const node_t *node) return router_digest_is_me(node->identity); } -/** Return <b>node</b> declared family (as a list of names), or NULL if - * the node didn't declare a family. */ -const smartlist_t * -node_get_declared_family(const node_t *node) -{ - if (node->ri && node->ri->declared_family) - return node->ri->declared_family; - else if (node->md && node->md->family) - return node->md->family; - else - return NULL; -} - /* Does this node have a valid IPv6 address? * Prefer node_has_ipv6_orport() or node_has_ipv6_dirport() for * checking specific ports. */ @@ -1877,7 +1972,7 @@ microdesc_has_curve25519_onion_key(const microdesc_t *md) return 0; } - if (tor_mem_is_zero((const char*)md->onion_curve25519_pkey->public_key, + if (fast_mem_is_zero((const char*)md->onion_curve25519_pkey->public_key, CURVE25519_PUBKEY_LEN)) { return 0; } @@ -1957,7 +2052,7 @@ node_set_country(node_t *node) void nodelist_refresh_countries(void) { - smartlist_t *nodes = nodelist_get_list(); + const smartlist_t *nodes = nodelist_get_list(); SMARTLIST_FOREACH(nodes, node_t *, node, node_set_country(node)); } @@ -1968,6 +2063,9 @@ int addrs_in_same_network_family(const tor_addr_t *a1, const tor_addr_t *a2) { + if (tor_addr_is_null(a1) || tor_addr_is_null(a2)) + return 0; + switch (tor_addr_family(a1)) { case AF_INET: return 0 == tor_addr_compare_masked(a1, a2, 16, CMP_SEMANTIC); @@ -1983,7 +2081,7 @@ addrs_in_same_network_family(const tor_addr_t *a1, * (case-insensitive), or if <b>node's</b> identity key digest * matches a hexadecimal value stored in <b>nickname</b>. Return * false otherwise. */ -static int +STATIC int node_nickname_matches(const node_t *node, const char *nickname) { const char *n = node_get_nickname(node); @@ -1995,7 +2093,7 @@ node_nickname_matches(const node_t *node, const char *nickname) } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ -static inline int +STATIC int node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) { if (!lst) return 0; @@ -2006,6 +2104,61 @@ node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) return 0; } +/** Return true iff n1's declared family contains n2. */ +STATIC int +node_family_contains(const node_t *n1, const node_t *n2) +{ + if (n1->ri && n1->ri->declared_family) { + return node_in_nickname_smartlist(n1->ri->declared_family, n2); + } else if (n1->md) { + return nodefamily_contains_node(n1->md->family, n2); + } else { + return 0; + } +} + +/** + * Return true iff <b>node</b> has declared a nonempty family. + **/ +STATIC bool +node_has_declared_family(const node_t *node) +{ + if (node->ri && node->ri->declared_family && + smartlist_len(node->ri->declared_family)) { + return true; + } + + if (node->md && node->md->family) { + return true; + } + + return false; +} + +/** + * Add to <b>out</b> every node_t that is listed by <b>node</b> as being in + * its family. (Note that these nodes are not in node's family unless they + * also agree that node is in their family.) + **/ +STATIC void +node_lookup_declared_family(smartlist_t *out, const node_t *node) +{ + if (node->ri && node->ri->declared_family && + smartlist_len(node->ri->declared_family)) { + SMARTLIST_FOREACH_BEGIN(node->ri->declared_family, const char *, name) { + const node_t *n2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED); + if (n2) { + smartlist_add(out, (node_t *)n2); + } + } SMARTLIST_FOREACH_END(name); + return; + } + + if (node->md && node->md->family) { + nodefamily_add_nodes_to_smartlist(node->md->family, out); + } +} + /** Return true iff r1 and r2 are in the same family, but not the same * router. */ int @@ -2018,19 +2171,20 @@ nodes_in_same_family(const node_t *node1, const node_t *node2) tor_addr_t a1, a2; node_get_addr(node1, &a1); node_get_addr(node2, &a2); - if (addrs_in_same_network_family(&a1, &a2)) + + tor_addr_port_t ap6_1, ap6_2; + node_get_pref_ipv6_orport(node1, &ap6_1); + node_get_pref_ipv6_orport(node2, &ap6_2); + + if (addrs_in_same_network_family(&a1, &a2) || + addrs_in_same_network_family(&ap6_1.addr, &ap6_2.addr)) return 1; } /* Are they in the same family because the agree they are? */ - { - const smartlist_t *f1, *f2; - f1 = node_get_declared_family(node1); - f2 = node_get_declared_family(node2); - if (f1 && f2 && - node_in_nickname_smartlist(f1, node2) && - node_in_nickname_smartlist(f2, node1)) - return 1; + if (node_family_contains(node1, node2) && + node_family_contains(node2, node1)) { + return 1; } /* Are they in the same family because the user says they are? */ @@ -2058,13 +2212,10 @@ void nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) { const smartlist_t *all_nodes = nodelist_get_list(); - const smartlist_t *declared_family; const or_options_t *options = get_options(); tor_assert(node); - declared_family = node_get_declared_family(node); - /* Let's make sure that we have the node itself, if it's a real node. */ { const node_t *real_node = node_get_by_id(node->identity); @@ -2075,35 +2226,35 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) /* First, add any nodes with similar network addresses. */ if (options->EnforceDistinctSubnets) { tor_addr_t node_addr; + tor_addr_port_t node_ap6; node_get_addr(node, &node_addr); + node_get_pref_ipv6_orport(node, &node_ap6); SMARTLIST_FOREACH_BEGIN(all_nodes, const node_t *, node2) { tor_addr_t a; + tor_addr_port_t ap6; node_get_addr(node2, &a); - if (addrs_in_same_network_family(&a, &node_addr)) + node_get_pref_ipv6_orport(node2, &ap6); + if (addrs_in_same_network_family(&a, &node_addr) || + addrs_in_same_network_family(&ap6.addr, &node_ap6.addr)) smartlist_add(sl, (void*)node2); } SMARTLIST_FOREACH_END(node2); } - /* Now, add all nodes in the declared_family of this node, if they + /* Now, add all nodes in the declared family of this node, if they * also declare this node to be in their family. */ - if (declared_family) { + if (node_has_declared_family(node)) { + smartlist_t *declared_family = smartlist_new(); + node_lookup_declared_family(declared_family, node); + /* Add every r such that router declares familyness with node, and node * declares familyhood with router. */ - SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { - const node_t *node2; - const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED))) - continue; - if (!(family2 = node_get_declared_family(node2))) - continue; - SMARTLIST_FOREACH_BEGIN(family2, const char *, name2) { - if (node_nickname_matches(node, name2)) { - smartlist_add(sl, (void*)node2); - break; - } - } SMARTLIST_FOREACH_END(name2); - } SMARTLIST_FOREACH_END(name); + SMARTLIST_FOREACH_BEGIN(declared_family, const node_t *, node2) { + if (node_family_contains(node2, node)) { + smartlist_add(sl, (void*)node2); + } + } SMARTLIST_FOREACH_END(node2); + smartlist_free(declared_family); } /* If the user declared any families locally, honor those too. */ @@ -2408,7 +2559,7 @@ compute_frac_paths_available(const networkstatus_t *consensus, const int authdir = authdir_mode_v3(options); count_usable_descriptors(num_present_out, num_usable_out, - mid, consensus, now, NULL, + mid, consensus, now, options->MiddleNodes, USABLE_DESCRIPTOR_ALL); log_debug(LD_NET, "%s: %d present, %d usable", @@ -2610,7 +2761,7 @@ count_loading_descriptors_progress(void) if (fraction > 1.0) return 0; /* it's not the number of descriptors holding us back */ return BOOTSTRAP_STATUS_LOADING_DESCRIPTORS + (int) - (fraction*(BOOTSTRAP_STATUS_CONN_OR-1 - + (fraction*(BOOTSTRAP_STATUS_ENOUGH_DIRINFO-1 - BOOTSTRAP_STATUS_LOADING_DESCRIPTORS)); } @@ -2697,14 +2848,14 @@ update_router_have_minimum_dir_info(void) /* If paths have just become available in this update. */ if (res && !have_min_dir_info) { control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO"); - control_event_boot_dir(BOOTSTRAP_STATUS_CONN_OR, 0); + control_event_boot_dir(BOOTSTRAP_STATUS_ENOUGH_DIRINFO, 0); log_info(LD_DIR, "We now have enough directory information to build circuits."); } /* If paths have just become unavailable in this update. */ if (!res && have_min_dir_info) { - int quiet = directory_too_idle_to_fetch_descriptors(options, now); + int quiet = dirclient_too_idle_to_fetch_descriptors(options, now); tor_log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR, "Our directory information is no longer up-to-date " "enough to build circuits: %s", dir_info_status); diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index bc09731ce2..14d631f39a 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -71,7 +71,6 @@ const char *node_get_platform(const node_t *node); uint32_t node_get_prim_addr_ipv4h(const node_t *node); void node_get_address_string(const node_t *node, char *cp, size_t len); long node_get_declared_uptime(const node_t *node); -const smartlist_t *node_get_declared_family(const node_t *node); const struct ed25519_public_key_t *node_get_ed25519_id(const node_t *node); int node_ed25519_id_matches(const node_t *node, const struct ed25519_public_key_t *id); @@ -80,7 +79,13 @@ int node_supports_ed25519_link_authentication(const node_t *node, int node_supports_v3_hsdir(const node_t *node); int node_supports_ed25519_hs_intro(const node_t *node); int node_supports_v3_rendezvous_point(const node_t *node); +int node_supports_establish_intro_dos_extension(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); +smartlist_t *node_get_link_specifier_smartlist(const node_t *node, + bool direct_conn); +void link_specifier_smartlist_free_(smartlist_t *ls_list); +#define link_specifier_smartlist_free(ls_list) \ + FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list)) int node_has_ipv6_addr(const node_t *node); int node_has_ipv6_orport(const node_t *node); @@ -100,7 +105,7 @@ const struct curve25519_public_key_t *node_get_curve25519_onion_key( const node_t *node); crypto_pk_t *node_get_rsa_onion_key(const node_t *node); -MOCK_DECL(smartlist_t *, nodelist_get_list, (void)); +MOCK_DECL(const smartlist_t *, nodelist_get_list, (void)); /* Temporary during transition to multiple addresses. */ void node_get_addr(const node_t *node, tor_addr_t *addr_out); @@ -158,10 +163,16 @@ int count_loading_descriptors_progress(void); #ifdef NODELIST_PRIVATE +STATIC int node_nickname_matches(const node_t *node, const char *nickname); +STATIC int node_in_nickname_smartlist(const smartlist_t *lst, + const node_t *node); +STATIC int node_family_contains(const node_t *n1, const node_t *n2); +STATIC bool node_has_declared_family(const node_t *node); +STATIC void node_lookup_declared_family(smartlist_t *out, const node_t *node); + #ifdef TOR_UNIT_TESTS -STATIC void -node_set_hsdir_index(node_t *node, const networkstatus_t *ns); +STATIC void node_set_hsdir_index(node_t *node, const networkstatus_t *ns); #endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/nodelist/routerinfo.c b/src/feature/nodelist/routerinfo.c index 975b503615..0bf2a977f5 100644 --- a/src/feature/nodelist/routerinfo.c +++ b/src/feature/nodelist/routerinfo.c @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routerinfo.c + * @brief Manipulate full router descriptors. + **/ + #include "core/or/or.h" #include "feature/nodelist/nodelist.h" diff --git a/src/feature/nodelist/routerinfo.h b/src/feature/nodelist/routerinfo.h index bfa28c7754..604e478999 100644 --- a/src/feature/nodelist/routerinfo.h +++ b/src/feature/nodelist/routerinfo.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,11 +17,9 @@ void router_get_prim_orport(const routerinfo_t *router, int router_has_orport(const routerinfo_t *router, const tor_addr_port_t *orport); -void router_get_verbose_nickname(char *buf, const routerinfo_t *router); - smartlist_t *router_get_all_orports(const routerinfo_t *ri); const char *router_purpose_to_string(uint8_t p); uint8_t router_purpose_from_string(const char *s); -#endif +#endif /* !defined(TOR_ROUTERINFO_H) */ diff --git a/src/feature/nodelist/routerinfo_st.h b/src/feature/nodelist/routerinfo_st.h index 59656818c1..e54a444ec4 100644 --- a/src/feature/nodelist/routerinfo_st.h +++ b/src/feature/nodelist/routerinfo_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routerinfo_st.h + * @brief Router descriptor structure. + **/ + #ifndef ROUTERINFO_ST_H #define ROUTERINFO_ST_H @@ -112,4 +117,4 @@ struct routerinfo_t { uint8_t purpose; }; -#endif +#endif /* !defined(ROUTERINFO_ST_H) */ diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c index c7fa868929..a0c9db8438 100644 --- a/src/feature/nodelist/routerlist.c +++ b/src/feature/nodelist/routerlist.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -67,12 +67,13 @@ #include "core/mainloop/mainloop.h" #include "core/or/policies.h" #include "feature/client/bridges.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/process_descs.h" #include "feature/dirauth/reachability.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" +#include "feature/dirclient/dirclient_modes.h" #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/authcert.h" @@ -160,7 +161,7 @@ static time_t last_descriptor_download_attempted = 0; * * From time to time, we replace "cached-descriptors" with a new file * containing only the live, non-superseded descriptors, and clear - * cached-routers.new. + * cached-descriptors.new. * * On startup, we read both files. */ @@ -1463,12 +1464,13 @@ router_descriptor_is_older_than,(const routerinfo_t *router, int seconds)) } /** Add <b>router</b> to the routerlist, if we don't already have it. Replace - * older entries (if any) with the same key. Note: Callers should not hold - * their pointers to <b>router</b> if this function fails; <b>router</b> - * will either be inserted into the routerlist or freed. Similarly, even - * if this call succeeds, they should not hold their pointers to - * <b>router</b> after subsequent calls with other routerinfo's -- they - * might cause the original routerinfo to get freed. + * older entries (if any) with the same key. + * + * Note: Callers should not hold their pointers to <b>router</b> if this + * function fails; <b>router</b> will either be inserted into the routerlist or + * freed. Similarly, even if this call succeeds, they should not hold their + * pointers to <b>router</b> after subsequent calls with other routerinfo's -- + * they might cause the original routerinfo to get freed. * * Returns the status for the operation. Might set *<b>msg</b> if it wants * the poster of the router to know something. @@ -1930,6 +1932,8 @@ routerlist_remove_old_routers(void) void routerlist_descriptors_added(smartlist_t *sl, int from_cache) { + // XXXX use pubsub mechanism here. + tor_assert(sl); control_event_descriptors_changed(sl); SMARTLIST_FOREACH_BEGIN(sl, routerinfo_t *, ri) { @@ -2405,7 +2409,7 @@ max_dl_per_request(const or_options_t *options, int purpose) } /* If we're going to tunnel our connections, we can ask for a lot more * in a request. */ - if (directory_must_use_begindir(options)) { + if (dirclient_must_use_begindir(options)) { max = 500; } return max; @@ -2448,7 +2452,7 @@ launch_descriptor_downloads(int purpose, if (!n_downloadable) return; - if (!directory_fetches_dir_info_early(options)) { + if (!dirclient_fetches_dir_info_early(options)) { if (n_downloadable >= MAX_DL_TO_DELAY) { log_debug(LD_DIR, "There are enough downloadable %ss to launch requests.", @@ -2539,7 +2543,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, int n_delayed=0, n_have=0, n_would_reject=0, n_wouldnt_use=0, n_inprogress=0, n_in_oldrouters=0; - if (directory_too_idle_to_fetch_descriptors(options, now)) + if (dirclient_too_idle_to_fetch_descriptors(options, now)) goto done; if (!consensus) goto done; @@ -2559,8 +2563,15 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, map = digestmap_new(); list_pending_descriptor_downloads(map, 0); SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, void *, rsp) { - routerstatus_t *rs = - is_vote ? &(((vote_routerstatus_t *)rsp)->status) : rsp; + routerstatus_t *rs; + vote_routerstatus_t *vrs; + if (is_vote) { + rs = &(((vote_routerstatus_t *)rsp)->status); + vrs = rsp; + } else { + rs = rsp; + vrs = NULL; + } signed_descriptor_t *sd; if ((sd = router_get_by_descriptor_digest(rs->descriptor_digest))) { const routerinfo_t *ri; @@ -2585,7 +2596,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, ++n_delayed; /* Not ready for retry. */ continue; } - if (authdir && dirserv_would_reject_router(rs)) { + if (authdir && is_vote && dirserv_would_reject_router(rs, vrs)) { ++n_would_reject; continue; /* We would throw it out immediately. */ } @@ -2975,7 +2986,7 @@ routerinfo_incompatible_with_extrainfo(const crypto_pk_t *identity_pkey, digest256_matches = tor_memeq(ei->digest256, sd->extra_info_digest256, DIGEST256_LEN); digest256_matches |= - tor_mem_is_zero(sd->extra_info_digest256, DIGEST256_LEN); + fast_mem_is_zero(sd->extra_info_digest256, DIGEST256_LEN); /* The identity must match exactly to have been generated at the same time * by the same router. */ @@ -3059,7 +3070,7 @@ routerinfo_has_curve25519_onion_key(const routerinfo_t *ri) return 0; } - if (tor_mem_is_zero((const char*)ri->onion_curve25519_pkey->public_key, + if (fast_mem_is_zero((const char*)ri->onion_curve25519_pkey->public_key, CURVE25519_PUBKEY_LEN)) { return 0; } @@ -3227,6 +3238,8 @@ refresh_all_country_info(void) routerset_refresh_countries(options->EntryNodes); if (options->ExitNodes) routerset_refresh_countries(options->ExitNodes); + if (options->MiddleNodes) + routerset_refresh_countries(options->MiddleNodes); if (options->ExcludeNodes) routerset_refresh_countries(options->ExcludeNodes); if (options->ExcludeExitNodes) diff --git a/src/feature/nodelist/routerlist.h b/src/feature/nodelist/routerlist.h index 5771ebb1ab..81a2343540 100644 --- a/src/feature/nodelist/routerlist.h +++ b/src/feature/nodelist/routerlist.h @@ -1,6 +1,6 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -37,9 +37,12 @@ typedef enum was_router_added_t { ROUTER_WAS_NOT_WANTED = -6, /* Router descriptor was rejected because it was older than * OLD_ROUTER_DESC_MAX_AGE. */ - ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */ - /* DOCDOC */ - ROUTER_CERTS_EXPIRED = -8 + ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'ROUTER_IS_ALREADY_KNOWN' */ + /* Some certs on this router are expired. */ + ROUTER_CERTS_EXPIRED = -8, + /* We couldn't format the annotations for this router. This is a directory + * authority bug. */ + ROUTER_AUTHDIR_BUG_ANNOTATIONS = -10 } was_router_added_t; /** How long do we avoid using a directory server after it's given us a 503? */ diff --git a/src/feature/nodelist/routerlist_st.h b/src/feature/nodelist/routerlist_st.h index 7446ead3cb..ec8933c7cb 100644 --- a/src/feature/nodelist/routerlist_st.h +++ b/src/feature/nodelist/routerlist_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routerlist_st.h + * @brief Router descriptor list structure. + **/ + #ifndef ROUTERLIST_ST_H #define ROUTERLIST_ST_H @@ -36,5 +41,4 @@ struct routerlist_t { desc_store_t extrainfo_store; }; -#endif - +#endif /* !defined(ROUTERLIST_ST_H) */ diff --git a/src/feature/nodelist/routerset.c b/src/feature/nodelist/routerset.c index 55e2756959..2e06ecbf04 100644 --- a/src/feature/nodelist/routerset.c +++ b/src/feature/nodelist/routerset.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. -n * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,7 +17,7 @@ n * Copyright (c) 2001-2004, Roger Dingledine. * * Routersets are typically used for user-specified restrictions, and * are created by invoking routerset_new and routerset_parse from - * config.c and confparse.c. To use a routerset, invoke one of + * config.c and confmgt.c. To use a routerset, invoke one of * routerset_contains_...() functions , or use * routerstatus_get_all_nodes() / routerstatus_subtract_nodes() to * manipulate a smartlist of node_t pointers. @@ -34,6 +34,9 @@ n * Copyright (c) 2001-2004, Roger Dingledine. #include "feature/nodelist/nickname.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerset.h" +#include "lib/conf/conftypes.h" +#include "lib/confmgt/typedvar.h" +#include "lib/encoding/confline.h" #include "lib/geoip/geoip.h" #include "core/or/addr_policy_st.h" @@ -41,6 +44,7 @@ n * Copyright (c) 2001-2004, Roger Dingledine. #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" +#include "lib/confmgt/var_type_def_st.h" /** Return a new empty routerset. */ routerset_t * @@ -378,7 +382,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, } else { /* We need to iterate over the routerlist to get all the ones of the * right kind. */ - smartlist_t *nodes = nodelist_get_list(); + const smartlist_t *nodes = nodelist_get_list(); SMARTLIST_FOREACH(nodes, const node_t *, node, { if (running_only && !node->is_running) continue; @@ -461,3 +465,111 @@ routerset_free_(routerset_t *routerset) bitarray_free(routerset->countries); tor_free(routerset); } + +/** + * config helper: parse a routerset-typed variable. + * + * Takes as input as a single line in <b>line</b>; writes its results into a + * routerset_t** passed as <b>target</b>. On success return 0; on failure + * return -1 and store an error message into *<b>errmsg</b>. + **/ +/* + * Warning: For this type, the default value (NULL) and "" are sometimes + * considered different values. That is generally risky, and best avoided for + * other types in the future. For cases where we want the default to be "all + * routers" (like EntryNodes) we should add a new routerset value indicating + * "all routers" (see #31908) + */ +static int +routerset_kv_parse(void *target, const config_line_t *line, char **errmsg, + const void *params) +{ + (void)params; + routerset_t **p = (routerset_t**)target; + routerset_free(*p); // clear the old value, if any. + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, line->value, line->key) < 0) { + routerset_free(rs); + *errmsg = tor_strdup("Invalid router list."); + return -1; + } else { + if (routerset_is_empty(rs)) { + /* Represent empty sets as NULL. */ + routerset_free(rs); + } + *p = rs; + return 0; + } +} + +/** + * config helper: encode a routerset-typed variable. + * + * Return a newly allocated string containing the value of the + * routerset_t** passed as <b>value</b>. + */ +static char * +routerset_encode(const void *value, const void *params) +{ + (void)params; + const routerset_t **p = (const routerset_t**)value; + return routerset_to_string(*p); +} + +/** + * config helper: free and clear a routerset-typed variable. + * + * Clear the routerset_t** passed as <b>value</b>. + */ +static void +routerset_clear(void *value, const void *params) +{ + (void)params; + routerset_t **p = (routerset_t**)value; + routerset_free(*p); // sets *p to NULL. +} + +/** + * config helper: copy a routerset-typed variable. + * + * Takes it input from a routerset_t** in <b>src</b>; writes its output to a + * routerset_t** in <b>dest</b>. Returns 0 on success, -1 on (impossible) + * failure. + **/ +static int +routerset_copy(void *dest, const void *src, const void *params) +{ + (void)params; + routerset_t **output = (routerset_t**)dest; + const routerset_t *input = *(routerset_t**)src; + routerset_free(*output); // sets *output to NULL + if (! routerset_is_empty(input)) { + *output = routerset_new(); + routerset_union(*output, input); + } + return 0; +} + +/** + * Function table to implement a routerset_t-based configuration type. + **/ +static const var_type_fns_t routerset_type_fns = { + .kv_parse = routerset_kv_parse, + .encode = routerset_encode, + .clear = routerset_clear, + .copy = routerset_copy +}; + +/** + * Definition of a routerset_t-based configuration type. + * + * Values are mapped to and from strings using the format defined in + * routerset_parse(): nicknames, IP address patterns, and fingerprints--with + * optional space, separated by commas. + * + * Empty sets are represented as NULL. + **/ +const var_type_def_t ROUTERSET_type_defn = { + .name = "RouterList", + .fns = &routerset_type_fns +}; diff --git a/src/feature/nodelist/routerset.h b/src/feature/nodelist/routerset.h index ca8b6fed93..0e4fedf64e 100644 --- a/src/feature/nodelist/routerset.h +++ b/src/feature/nodelist/routerset.h @@ -1,10 +1,10 @@ /* Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** - * \file routerlist.h + * \file routerset.h * \brief Header file for routerset.c **/ @@ -44,6 +44,10 @@ void routerset_free_(routerset_t *routerset); #define routerset_free(rs) FREE_AND_NULL(routerset_t, routerset_free_, (rs)) int routerset_len(const routerset_t *set); +struct var_type_def_t; +extern const struct var_type_def_t ROUTERSET_type_defn; +typedef routerset_t *config_decl_ROUTERSET; + #ifdef ROUTERSET_PRIVATE #include "lib/container/bitarray.h" diff --git a/src/feature/nodelist/routerstatus_st.h b/src/feature/nodelist/routerstatus_st.h index 288edf5982..735c754b31 100644 --- a/src/feature/nodelist/routerstatus_st.h +++ b/src/feature/nodelist/routerstatus_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routerstatus_st.h + * @brief Routerstatus (consensus entry) structure + **/ + #ifndef ROUTERSTATUS_ST_H #define ROUTERSTATUS_ST_H @@ -12,6 +17,10 @@ /** Contents of a single router entry in a network status object. */ struct routerstatus_t { + /* This should be kept in sync with the function + * routerstatus_has_visibly_changed and the printing function + * routerstatus_format_entry in NS_CONTROL_PORT mode. + */ time_t published_on; /**< When was this router published? */ char nickname[MAX_NICKNAME_LEN+1]; /**< The nickname this router says it * has. */ @@ -47,6 +56,8 @@ struct routerstatus_t { unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort * or it claims to accept tunnelled dir requests. */ + unsigned int is_staledesc:1; /** True iff the authorities think this router + * should upload a new descriptor soon. */ unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ @@ -76,5 +87,4 @@ struct routerstatus_t { }; -#endif - +#endif /* !defined(ROUTERSTATUS_ST_H) */ diff --git a/src/feature/nodelist/signed_descriptor_st.h b/src/feature/nodelist/signed_descriptor_st.h index bdcebf184a..068f2a733c 100644 --- a/src/feature/nodelist/signed_descriptor_st.h +++ b/src/feature/nodelist/signed_descriptor_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file signed_descriptor_st.h + * @brief Descriptor/extrainfo signature structure + **/ + #ifndef SIGNED_DESCRIPTOR_ST_H #define SIGNED_DESCRIPTOR_ST_H @@ -57,5 +62,4 @@ struct signed_descriptor_t { unsigned int send_unencrypted : 1; }; -#endif - +#endif /* !defined(SIGNED_DESCRIPTOR_ST_H) */ diff --git a/src/feature/nodelist/torcert.c b/src/feature/nodelist/torcert.c index b0197e9f13..89cc9c88fb 100644 --- a/src/feature/nodelist/torcert.c +++ b/src/feature/nodelist/torcert.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -74,7 +74,7 @@ tor_cert_sign_impl(const ed25519_keypair_t *signing_key, tor_assert(real_len == alloc_len); tor_assert(real_len > ED25519_SIG_LEN); uint8_t *sig = encoded + (real_len - ED25519_SIG_LEN); - tor_assert(tor_mem_is_zero((char*)sig, ED25519_SIG_LEN)); + tor_assert(fast_mem_is_zero((char*)sig, ED25519_SIG_LEN)); ed25519_signature_t signature; if (ed25519_sign(&signature, encoded, @@ -290,8 +290,8 @@ tor_cert_describe_signature_status(const tor_cert_t *cert) } /** Return a new copy of <b>cert</b> */ -tor_cert_t * -tor_cert_dup(const tor_cert_t *cert) +MOCK_IMPL(tor_cert_t *, +tor_cert_dup,(const tor_cert_t *cert)) { tor_cert_t *newcert = tor_memdup(cert, sizeof(tor_cert_t)); if (cert->encoded) diff --git a/src/feature/nodelist/torcert.h b/src/feature/nodelist/torcert.h index 492275b514..f8fba2b794 100644 --- a/src/feature/nodelist/torcert.h +++ b/src/feature/nodelist/torcert.h @@ -1,6 +1,11 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file torcert.h + * @brief Header for torcert.c + **/ + #ifndef TORCERT_H_INCLUDED #define TORCERT_H_INCLUDED @@ -71,7 +76,7 @@ int tor_cert_checksig(tor_cert_t *cert, const ed25519_public_key_t *pubkey, time_t now); const char *tor_cert_describe_signature_status(const tor_cert_t *cert); -tor_cert_t *tor_cert_dup(const tor_cert_t *cert); +MOCK_DECL(tor_cert_t *,tor_cert_dup,(const tor_cert_t *cert)); int tor_cert_eq(const tor_cert_t *cert1, const tor_cert_t *cert2); int tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2); diff --git a/src/feature/nodelist/vote_routerstatus_st.h b/src/feature/nodelist/vote_routerstatus_st.h index 366754c166..ad0ee3f23b 100644 --- a/src/feature/nodelist/vote_routerstatus_st.h +++ b/src/feature/nodelist/vote_routerstatus_st.h @@ -1,9 +1,13 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file vote_routerstatus_st.h + * @brief Routerstatus (vote entry) structure + **/ #ifndef VOTE_ROUTERSTATUS_ST_H #define VOTE_ROUTERSTATUS_ST_H @@ -38,4 +42,4 @@ struct vote_routerstatus_t { uint8_t ed25519_id[ED25519_PUBKEY_LEN]; }; -#endif +#endif /* !defined(VOTE_ROUTERSTATUS_ST_H) */ diff --git a/src/feature/relay/.may_include b/src/feature/relay/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/relay/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c index e20a39482f..7ab4ca0f45 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -59,7 +59,7 @@ #include "core/or/connection_edge.h" #include "core/or/policies.h" #include "core/or/relay.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/relay/dns.h" #include "feature/relay/router.h" #include "feature/relay/routermode.h" @@ -1360,6 +1360,42 @@ evdns_err_is_transient(int err) } } +/** + * Return number of configured nameservers in <b>the_evdns_base</b>. + */ +size_t +number_of_configured_nameservers(void) +{ + return evdns_base_count_nameservers(the_evdns_base); +} + +#ifdef HAVE_EVDNS_BASE_GET_NAMESERVER_ADDR +/** + * Return address of configured nameserver in <b>the_evdns_base</b> + * at index <b>idx</b>. + */ +tor_addr_t * +configured_nameserver_address(const size_t idx) +{ + struct sockaddr_storage sa; + ev_socklen_t sa_len = sizeof(sa); + + if (evdns_base_get_nameserver_addr(the_evdns_base, (int)idx, + (struct sockaddr *)&sa, + sa_len) > 0) { + tor_addr_t *tor_addr = tor_malloc(sizeof(tor_addr_t)); + if (tor_addr_from_sockaddr(tor_addr, + (const struct sockaddr *)&sa, + NULL) == 0) { + return tor_addr; + } + tor_free(tor_addr); + } + + return NULL; +} +#endif /* defined(HAVE_EVDNS_BASE_GET_NAMESERVER_ADDR) */ + /** 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 @@ -1391,16 +1427,23 @@ configure_nameservers(int force) 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)) { + int missing_resolv_conf = 0; + int stat_res = stat(sandbox_intern_string(conf_fname), &st); + + if (stat_res) { 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) + missing_resolv_conf = 1; + } else 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 (stat_res == 0 && st.st_size == 0) + missing_resolv_conf = 1; + if (nameservers_configured) { evdns_base_search_clear(the_evdns_base); evdns_base_clear_nameservers_and_suspend(the_evdns_base); @@ -1413,20 +1456,34 @@ configure_nameservers(int force) 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; + + if (!missing_resolv_conf) { + 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); + + if (r != 6) // "r = 6" means "no DNS servers were in resolv.conf" - + goto err; // in which case we expect libevent to add 127.0.0.1 as + // fallback. + } + if (evdns_base_count_nameservers(the_evdns_base) == 0) { + log_warn(LD_EXIT, "Unable to find any nameservers in '%s'.", + conf_fname); + } + + tor_free(resolv_conf_fname); + resolv_conf_fname = tor_strdup(conf_fname); + resolv_conf_mtime = st.st_mtime; + } else { + log_warn(LD_EXIT, "Could not read your DNS config from '%s' - " + "please investigate your DNS configuration. " + "This is possibly a problem. Meanwhile, falling" + " back to local DNS at 127.0.0.1.", conf_fname); + evdns_base_nameserver_ip_add(the_evdns_base, "127.0.0.1"); } - 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); } diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h index e4474cdf43..2b1da8d126 100644 --- a/src/feature/relay/dns.h +++ b/src/feature/relay/dns.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -45,6 +45,11 @@ size_t dns_cache_handle_oom(time_t now, size_t min_remove_bytes); #ifdef DNS_PRIVATE #include "feature/relay/dns_structs.h" +size_t number_of_configured_nameservers(void); +#ifdef HAVE_EVDNS_BASE_GET_NAMESERVER_ADDR +tor_addr_t *configured_nameserver_address(const size_t idx); +#endif + 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)); diff --git a/src/feature/relay/dns_structs.h b/src/feature/relay/dns_structs.h index e128746f81..27a791b9b3 100644 --- a/src/feature/relay/dns_structs.h +++ b/src/feature/relay/dns_structs.h @@ -1,6 +1,6 @@ /* Copyright (c) 2003-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,6 +13,8 @@ #ifndef TOR_DNS_STRUCTS_H #define TOR_DNS_STRUCTS_H +#include "ext/ht.h" + /** Longest hostname we're willing to resolve. */ #define MAX_ADDRESSLEN 256 @@ -99,4 +101,3 @@ typedef struct cached_resolve_t { } 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 index 136aee3084..3a9e4abfd0 100644 --- a/src/feature/relay/ext_orport.c +++ b/src/feature/relay/ext_orport.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2019, The Tor Project, Inc. */ +/* Copyright (c) 2012-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -20,7 +20,7 @@ #include "core/or/or.h" #include "core/mainloop/connection.h" #include "core/or/connection_or.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "app/config/config.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" @@ -90,7 +90,7 @@ connection_ext_or_transition(or_connection_t *conn) 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_or_event_status(conn, OR_CONN_EVENT_NEW, 0); connection_tls_start_handshake(conn, 1); } diff --git a/src/feature/relay/ext_orport.h b/src/feature/relay/ext_orport.h index 7313ebd03d..dbe89fce18 100644 --- a/src/feature/relay/ext_orport.h +++ b/src/feature/relay/ext_orport.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file ext_orport.h + * @brief Header for ext_orport.c + **/ + #ifndef EXT_ORPORT_H #define EXT_ORPORT_H diff --git a/src/feature/relay/feature_relay.md b/src/feature/relay/feature_relay.md new file mode 100644 index 0000000000..a7f0c2153a --- /dev/null +++ b/src/feature/relay/feature_relay.md @@ -0,0 +1,4 @@ +@dir /feature/relay +@brief feature/relay: Relay-specific code + +(There is also a bunch of relay-specific code in other modules.) diff --git a/src/feature/relay/include.am b/src/feature/relay/include.am new file mode 100644 index 0000000000..a4c025ae12 --- /dev/null +++ b/src/feature/relay/include.am @@ -0,0 +1,40 @@ + +# Legacy shared relay code: migrate to the relay module over time +LIBTOR_APP_A_SOURCES += \ + src/feature/relay/dns.c \ + src/feature/relay/ext_orport.c \ + src/feature/relay/onion_queue.c \ + src/feature/relay/router.c \ + src/feature/relay/routerkeys.c \ + src/feature/relay/selftest.c + +# The Relay module. + +# ADD_C_FILE: INSERT SOURCES HERE. +MODULE_RELAY_SOURCES = \ + src/feature/relay/routermode.c \ + src/feature/relay/relay_config.c \ + src/feature/relay/relay_periodic.c \ + src/feature/relay/relay_sys.c \ + src/feature/relay/transport_config.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/relay/dns.h \ + src/feature/relay/dns_structs.h \ + src/feature/relay/ext_orport.h \ + src/feature/relay/onion_queue.h \ + src/feature/relay/relay_config.h \ + src/feature/relay/relay_periodic.h \ + src/feature/relay/relay_sys.h \ + src/feature/relay/router.h \ + src/feature/relay/routerkeys.h \ + src/feature/relay/routermode.h \ + src/feature/relay/selftest.h \ + src/feature/relay/transport_config.h + +if BUILD_MODULE_RELAY +LIBTOR_APP_A_SOURCES += $(MODULE_RELAY_SOURCES) +else +LIBTOR_APP_A_STUB_SOURCES += src/feature/relay/relay_stub.c +endif diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c index 696905cf5e..ce2d41b7e1 100644 --- a/src/feature/relay/onion_queue.c +++ b/src/feature/relay/onion_queue.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -212,10 +212,12 @@ num_ntors_per_tap(void) #define MIN_NUM_NTORS_PER_TAP 1 #define MAX_NUM_NTORS_PER_TAP 100000 - return networkstatus_get_param(NULL, "NumNTorsPerTAP", - DEFAULT_NUM_NTORS_PER_TAP, - MIN_NUM_NTORS_PER_TAP, - MAX_NUM_NTORS_PER_TAP); + int result = networkstatus_get_param(NULL, "NumNTorsPerTAP", + DEFAULT_NUM_NTORS_PER_TAP, + MIN_NUM_NTORS_PER_TAP, + MAX_NUM_NTORS_PER_TAP); + tor_assert(result > 0); + return result; } /** Choose which onion queue we'll pull from next. If one is empty choose diff --git a/src/feature/relay/onion_queue.h b/src/feature/relay/onion_queue.h index 0df921e057..08379b2c00 100644 --- a/src/feature/relay/onion_queue.h +++ b/src/feature/relay/onion_queue.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -20,4 +20,4 @@ int onion_num_pending(uint16_t handshake_type); void onion_pending_remove(or_circuit_t *circ); void clear_pending_onions(void); -#endif +#endif /* !defined(TOR_ONION_QUEUE_H) */ diff --git a/src/feature/relay/relay_config.c b/src/feature/relay/relay_config.c new file mode 100644 index 0000000000..3e9961f47e --- /dev/null +++ b/src/feature/relay/relay_config.c @@ -0,0 +1,1444 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_config.c + * @brief Code to interpret the user's configuration of Tor's relay module. + **/ + +#include "orconfig.h" +#define RELAY_CONFIG_PRIVATE +#include "feature/relay/relay_config.h" + +#include "lib/encoding/confline.h" +#include "lib/confmgt/confmgt.h" + +#include "lib/container/smartlist.h" +#include "lib/geoip/geoip.h" +#include "lib/meminfo/meminfo.h" +#include "lib/osinfo/uname.h" +#include "lib/process/setuid.h" + +/* Required for dirinfo_type_t in or_options_t */ +#include "core/or/or.h" +#include "app/config/config.h" + +#include "core/mainloop/connection.h" +#include "core/mainloop/cpuworker.h" +#include "core/mainloop/mainloop.h" +#include "core/or/circuitbuild.h" +#include "core/or/connection_or.h" +#include "core/or/port_cfg_st.h" + +#include "feature/hibernate/hibernate.h" +#include "feature/nodelist/nickname.h" +#include "feature/stats/geoip_stats.h" +#include "feature/stats/predict_ports.h" +#include "feature/stats/rephist.h" + +#include "feature/dirauth/authmode.h" + +#include "feature/dircache/consdiffmgr.h" +#include "feature/relay/dns.h" +#include "feature/relay/routermode.h" + +/** Contents of most recently read DirPortFrontPage file. */ +static char *global_dirfrontpagecontents = NULL; + +/* Copied from config.c, we will refactor later in 29211. */ +#define REJECT(arg) \ + STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END +#if defined(__GNUC__) && __GNUC__ <= 3 +#define COMPLAIN(args...) \ + STMT_BEGIN log_warn(LD_CONFIG, args); STMT_END +#else +#define COMPLAIN(args, ...) \ + STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ + +/* Used in the various options_transition_affects* functions. */ +#define YES_IF_CHANGED_BOOL(opt) \ + if (!CFG_EQ_BOOL(old_options, new_options, opt)) return 1; +#define YES_IF_CHANGED_INT(opt) \ + if (!CFG_EQ_INT(old_options, new_options, opt)) return 1; +#define YES_IF_CHANGED_STRING(opt) \ + if (!CFG_EQ_STRING(old_options, new_options, opt)) return 1; +#define YES_IF_CHANGED_LINELIST(opt) \ + if (!CFG_EQ_LINELIST(old_options, new_options, opt)) return 1; + +/** Return the contents of our frontpage string, or NULL if not configured. */ +MOCK_IMPL(const char*, +relay_get_dirportfrontpage, (void)) +{ + return global_dirfrontpagecontents; +} + +/** Release all memory and resources held by global relay configuration + * structures. + */ +void +relay_config_free_all(void) +{ + tor_free(global_dirfrontpagecontents); +} + +/** Return the bandwidthrate that we are going to report to the authorities + * based on the config options. */ +uint32_t +relay_get_effective_bwrate(const or_options_t *options) +{ + uint64_t bw = options->BandwidthRate; + if (bw > options->MaxAdvertisedBandwidth) + bw = options->MaxAdvertisedBandwidth; + if (options->RelayBandwidthRate > 0 && bw > options->RelayBandwidthRate) + bw = options->RelayBandwidthRate; + /* config_ensure_bandwidth_cap() makes sure that this cast can't overflow. */ + return (uint32_t)bw; +} + +/** Return the bandwidthburst that we are going to report to the authorities + * based on the config options. */ +uint32_t +relay_get_effective_bwburst(const or_options_t *options) +{ + uint64_t bw = options->BandwidthBurst; + if (options->RelayBandwidthBurst > 0 && bw > options->RelayBandwidthBurst) + bw = options->RelayBandwidthBurst; + /* config_ensure_bandwidth_cap() makes sure that this cast can't overflow. */ + return (uint32_t)bw; +} + +/** Warn for every Extended ORPort port in <b>ports</b> that is on a + * publicly routable address. */ +void +port_warn_nonlocal_ext_orports(const smartlist_t *ports, const char *portname) +{ + SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + if (port->type != CONN_TYPE_EXT_OR_LISTENER) + continue; + if (port->is_unix_addr) + continue; + /* XXX maybe warn even if address is RFC1918? */ + if (!tor_addr_is_internal(&port->addr, 1)) { + log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. " + "This is not advised; this address is supposed to only be " + "exposed on localhost so that your pluggable transport " + "proxies can connect to it.", + fmt_addrport(&port->addr, port->port), portname); + } + } SMARTLIST_FOREACH_END(port); +} + +/** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal + * consistency and warn as appropriate. On Unix-based OSes, set + * *<b>n_low_ports_out</b> to the number of sub-1024 ports we will be + * binding, and warn if we may be unable to re-bind after hibernation. */ +static int +check_server_ports(const smartlist_t *ports, + const or_options_t *options, + int *n_low_ports_out) +{ + if (BUG(!ports)) + return -1; + + if (BUG(!options)) + return -1; + + if (BUG(!n_low_ports_out)) + return -1; + + int n_orport_advertised = 0; + int n_orport_advertised_ipv4 = 0; + int n_orport_listeners = 0; + int n_dirport_advertised = 0; + int n_dirport_listeners = 0; + int n_low_port = 0; + int r = 0; + + SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + if (port->type == CONN_TYPE_DIR_LISTENER) { + if (! port->server_cfg.no_advertise) + ++n_dirport_advertised; + if (! port->server_cfg.no_listen) + ++n_dirport_listeners; + } else if (port->type == CONN_TYPE_OR_LISTENER) { + if (! port->server_cfg.no_advertise) { + ++n_orport_advertised; + if (port_binds_ipv4(port)) + ++n_orport_advertised_ipv4; + } + if (! port->server_cfg.no_listen) + ++n_orport_listeners; + } else { + continue; + } +#ifndef _WIN32 + if (!port->server_cfg.no_listen && port->port < 1024) + ++n_low_port; +#endif + } SMARTLIST_FOREACH_END(port); + + if (n_orport_advertised && !n_orport_listeners) { + log_warn(LD_CONFIG, "We are advertising an ORPort, but not actually " + "listening on one."); + r = -1; + } + if (n_orport_listeners && !n_orport_advertised) { + log_warn(LD_CONFIG, "We are listening on an ORPort, but not advertising " + "any ORPorts. This will keep us from building a %s " + "descriptor, and make us impossible to use.", + options->BridgeRelay ? "bridge" : "router"); + r = -1; + } + if (n_dirport_advertised && !n_dirport_listeners) { + log_warn(LD_CONFIG, "We are advertising a DirPort, but not actually " + "listening on one."); + r = -1; + } + if (n_dirport_advertised > 1) { + log_warn(LD_CONFIG, "Can't advertise more than one DirPort."); + r = -1; + } + if (n_orport_advertised && !n_orport_advertised_ipv4 && + !options->BridgeRelay) { + log_warn(LD_CONFIG, "Configured public relay to listen only on an IPv6 " + "address. Tor needs to listen on an IPv4 address too."); + r = -1; + } + + if (n_low_port && options->AccountingMax && + (!have_capability_support() || options->KeepBindCapabilities == 0)) { + const char *extra = ""; + if (options->KeepBindCapabilities == 0 && have_capability_support()) + extra = ", and you have disabled KeepBindCapabilities."; + log_warn(LD_CONFIG, + "You have set AccountingMax to use hibernation. You have also " + "chosen a low DirPort or OrPort%s." + "This combination can make Tor stop " + "working when it tries to re-attach the port after a period of " + "hibernation. Please choose a different port or turn off " + "hibernation unless you know this combination will work on your " + "platform.", extra); + } + + if (n_low_ports_out) + *n_low_ports_out = n_low_port; + + return r; +} + +/** Parse all relay ports from <b>options</b>. On success, add parsed ports to + * <b>ports</b>, and return 0. On failure, set *<b>msg</b> to a newly + * allocated string describing the problem, and return -1. + **/ +int +port_parse_ports_relay(or_options_t *options, + char **msg, + smartlist_t *ports_out, + int *have_low_ports_out) +{ + int retval = -1; + smartlist_t *ports = smartlist_new(); + int n_low_ports = 0; + + if (BUG(!options)) + goto err; + + if (BUG(!msg)) + goto err; + + if (BUG(!ports_out)) + goto err; + + if (BUG(!have_low_ports_out)) + goto err; + + if (options->ClientOnly) { + retval = 0; + goto err; + } + + if (port_parse_config(ports, + options->ORPort_lines, + "OR", CONN_TYPE_OR_LISTENER, + "0.0.0.0", 0, + CL_PORT_SERVER_OPTIONS) < 0) { + *msg = tor_strdup("Invalid ORPort configuration"); + goto err; + } + if (port_parse_config(ports, + options->ExtORPort_lines, + "ExtOR", CONN_TYPE_EXT_OR_LISTENER, + "127.0.0.1", 0, + CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid ExtORPort configuration"); + goto err; + } + if (port_parse_config(ports, + options->DirPort_lines, + "Dir", CONN_TYPE_DIR_LISTENER, + "0.0.0.0", 0, + CL_PORT_SERVER_OPTIONS) < 0) { + *msg = tor_strdup("Invalid DirPort configuration"); + goto err; + } + + if (check_server_ports(ports, options, &n_low_ports) < 0) { + *msg = tor_strdup("Misconfigured server ports"); + goto err; + } + + smartlist_add_all(ports_out, ports); + smartlist_free(ports); + ports = NULL; + retval = 0; + + err: + if (*have_low_ports_out < 0) + *have_low_ports_out = (n_low_ports > 0); + if (ports) { + SMARTLIST_FOREACH(ports, port_cfg_t *, p, port_cfg_free(p)); + smartlist_free(ports); + } + return retval; +} + +/** Update the relay *Port_set values in <b>options</b> from <b>ports</b>. */ +void +port_update_port_set_relay(or_options_t *options, + const smartlist_t *ports) +{ + if (BUG(!options)) + return; + + if (BUG(!ports)) + return; + + if (options->ClientOnly) + return; + + /* Update the relay *Port_set options. The !! here is to force a boolean + * out of an integer. */ + options->ORPort_set = + !! port_count_real_listeners(ports, CONN_TYPE_OR_LISTENER, 0); + options->DirPort_set = + !! port_count_real_listeners(ports, CONN_TYPE_DIR_LISTENER, 0); + options->ExtORPort_set = + !! port_count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER, 0); +} + +/** + * Legacy validation function, which checks that the current OS is usable in + * relay mode, if options is set to a relay mode. + * + * Warns about OSes with potential issues. Does not set *<b>msg</b>. + * Always returns 0. + */ +int +options_validate_relay_os(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (!server_mode(options)) + return 0; + + const char *uname = get_uname(); + + if (!strcmpstart(uname, "Windows 95") || + !strcmpstart(uname, "Windows 98") || + !strcmpstart(uname, "Windows Me")) { + log_warn(LD_CONFIG, "Tor is running as a server, but you are " + "running %s; this probably won't work. See " + "https://www.torproject.org/docs/faq.html#BestOSForRelay " + "for details.", uname); + } + + return 0; +} + +/** + * Legacy validation/normalization function for the relay info options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_info(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (options->Nickname == NULL) { + if (server_mode(options)) { + options->Nickname = tor_strdup(UNNAMED_ROUTER_NICKNAME); + } + } else { + if (!is_legal_nickname(options->Nickname)) { + tor_asprintf(msg, + "Nickname '%s', nicknames must be between 1 and 19 characters " + "inclusive, and must contain only the characters [a-zA-Z0-9].", + options->Nickname); + return -1; + } + } + + if (server_mode(options) && !options->ContactInfo) { + log_warn(LD_CONFIG, + "Your ContactInfo config option is not set. Please strongly " + "consider setting it, so we can contact you if your relay is " + "misconfigured, end-of-life, or something else goes wrong. " + "It is also possible that your relay might get rejected from " + "the network due to a missing valid contact address."); + } + + const char *ContactInfo = options->ContactInfo; + if (ContactInfo && !string_is_utf8(ContactInfo, strlen(ContactInfo))) + REJECT("ContactInfo config option must be UTF-8."); + + return 0; +} + +/** Parse an authority type from <b>options</b>-\>PublishServerDescriptor + * and write it to <b>options</b>-\>PublishServerDescriptor_. Treat "1" + * as "v3" unless BridgeRelay is 1, in which case treat it as "bridge". + * Treat "0" as "". + * Return 0 on success or -1 if not a recognized authority type (in which + * case the value of PublishServerDescriptor_ is undefined). */ +static int +compute_publishserverdescriptor(or_options_t *options) +{ + smartlist_t *list = options->PublishServerDescriptor; + dirinfo_type_t *auth = &options->PublishServerDescriptor_; + *auth = NO_DIRINFO; + if (!list) /* empty list, answer is none */ + return 0; + SMARTLIST_FOREACH_BEGIN(list, const char *, string) { + if (!strcasecmp(string, "v1")) + log_warn(LD_CONFIG, "PublishServerDescriptor v1 has no effect, because " + "there are no v1 directory authorities anymore."); + else if (!strcmp(string, "1")) + if (options->BridgeRelay) + *auth |= BRIDGE_DIRINFO; + else + *auth |= V3_DIRINFO; + else if (!strcasecmp(string, "v2")) + log_warn(LD_CONFIG, "PublishServerDescriptor v2 has no effect, because " + "there are no v2 directory authorities anymore."); + else if (!strcasecmp(string, "v3")) + *auth |= V3_DIRINFO; + else if (!strcasecmp(string, "bridge")) + *auth |= BRIDGE_DIRINFO; + else if (!strcasecmp(string, "hidserv")) + log_warn(LD_CONFIG, + "PublishServerDescriptor hidserv is invalid. See " + "PublishHidServDescriptors."); + else if (!strcasecmp(string, "") || !strcmp(string, "0")) + /* no authority */; + else + return -1; + } SMARTLIST_FOREACH_END(string); + return 0; +} + +/** + * Validate the configured bridge distribution method from a BridgeDistribution + * config line. + * + * The input <b>bd</b>, is a string taken from the BridgeDistribution config + * line (if present). If the option wasn't set, return 0 immediately. The + * BridgeDistribution option is then validated. Currently valid, recognised + * options are: + * + * - "none" + * - "any" + * - "https" + * - "email" + * - "moat" + * + * If the option string is unrecognised, a warning will be logged and 0 is + * returned. If the option string contains an invalid character, -1 is + * returned. + **/ +STATIC int +check_bridge_distribution_setting(const char *bd) +{ + if (bd == NULL) + return 0; + + const char *RECOGNIZED[] = { + "none", "any", "https", "email", "moat" + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) { + if (!strcasecmp(bd, RECOGNIZED[i])) + return 0; + } + + const char *cp = bd; + // Method = (KeywordChar | "_") + + while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_') + ++cp; + + if (*cp == 0) { + log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll " + "assume you know what you are doing...", escaped(bd)); + return 0; // we reached the end of the string; all is well + } else { + return -1; // we found a bad character in the string. + } +} + +/** + * Legacy validation/normalization function for the bridge relay options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_publish_server(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (compute_publishserverdescriptor(options) < 0) { + tor_asprintf(msg, "Unrecognized value in PublishServerDescriptor"); + return -1; + } + + if ((options->BridgeRelay + || options->PublishServerDescriptor_ & BRIDGE_DIRINFO) + && (options->PublishServerDescriptor_ & V3_DIRINFO)) { + REJECT("Bridges are not supposed to publish router descriptors to the " + "directory authorities. Please correct your " + "PublishServerDescriptor line."); + } + + if (options->BridgeDistribution) { + if (!options->BridgeRelay) { + REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!"); + } + if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) { + REJECT("Invalid BridgeDistribution value."); + } + } + + if (options->PublishServerDescriptor) + SMARTLIST_FOREACH(options->PublishServerDescriptor, const char *, pubdes, { + if (!strcmp(pubdes, "1") || !strcmp(pubdes, "0")) + if (smartlist_len(options->PublishServerDescriptor) > 1) { + COMPLAIN("You have passed a list of multiple arguments to the " + "PublishServerDescriptor option that includes 0 or 1. " + "0 or 1 should only be used as the sole argument. " + "This configuration will be rejected in a future release."); + break; + } + }); + + return 0; +} + +/** + * Legacy validation/normalization function for the relay padding options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_padding(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (!server_mode(options)) + return 0; + + if (options->ConnectionPadding != -1) { + REJECT("Relays must use 'auto' for the ConnectionPadding setting."); + } + + if (options->ReducedConnectionPadding != 0) { + REJECT("Relays cannot set ReducedConnectionPadding. "); + } + + if (options->CircuitPadding == 0) { + REJECT("Relays cannot set CircuitPadding to 0. "); + } + + if (options->ReducedCircuitPadding == 1) { + REJECT("Relays cannot set ReducedCircuitPadding. "); + } + + return 0; +} + +/** + * Legacy validation/normalization function for the relay bandwidth options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_bandwidth(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + /* 31851: the tests expect us to validate bandwidths, even when we are not + * in relay mode. */ + if (config_ensure_bandwidth_cap(&options->MaxAdvertisedBandwidth, + "MaxAdvertisedBandwidth", msg) < 0) + return -1; + if (config_ensure_bandwidth_cap(&options->RelayBandwidthRate, + "RelayBandwidthRate", msg) < 0) + return -1; + if (config_ensure_bandwidth_cap(&options->RelayBandwidthBurst, + "RelayBandwidthBurst", msg) < 0) + return -1; + if (config_ensure_bandwidth_cap(&options->PerConnBWRate, + "PerConnBWRate", msg) < 0) + return -1; + if (config_ensure_bandwidth_cap(&options->PerConnBWBurst, + "PerConnBWBurst", msg) < 0) + return -1; + + if (options->RelayBandwidthRate && !options->RelayBandwidthBurst) + options->RelayBandwidthBurst = options->RelayBandwidthRate; + if (options->RelayBandwidthBurst && !options->RelayBandwidthRate) + options->RelayBandwidthRate = options->RelayBandwidthBurst; + + if (server_mode(options)) { + const unsigned required_min_bw = + public_server_mode(options) ? + RELAY_REQUIRED_MIN_BANDWIDTH : BRIDGE_REQUIRED_MIN_BANDWIDTH; + const char * const optbridge = + public_server_mode(options) ? "" : "bridge "; + if (options->BandwidthRate < required_min_bw) { + tor_asprintf(msg, + "BandwidthRate is set to %d bytes/second. " + "For %sservers, it must be at least %u.", + (int)options->BandwidthRate, optbridge, + required_min_bw); + return -1; + } else if (options->MaxAdvertisedBandwidth < + required_min_bw/2) { + tor_asprintf(msg, + "MaxAdvertisedBandwidth is set to %d bytes/second. " + "For %sservers, it must be at least %u.", + (int)options->MaxAdvertisedBandwidth, optbridge, + required_min_bw/2); + return -1; + } + if (options->RelayBandwidthRate && + options->RelayBandwidthRate < required_min_bw) { + tor_asprintf(msg, + "RelayBandwidthRate is set to %d bytes/second. " + "For %sservers, it must be at least %u.", + (int)options->RelayBandwidthRate, optbridge, + required_min_bw); + return -1; + } + } + + /* 31851: the tests expect us to validate bandwidths, even when we are not + * in relay mode. */ + if (options->RelayBandwidthRate > options->RelayBandwidthBurst) + REJECT("RelayBandwidthBurst must be at least equal " + "to RelayBandwidthRate."); + + /* if they set relaybandwidth* really high but left bandwidth* + * at the default, raise the defaults. */ + if (options->RelayBandwidthRate > options->BandwidthRate) + options->BandwidthRate = options->RelayBandwidthRate; + if (options->RelayBandwidthBurst > options->BandwidthBurst) + options->BandwidthBurst = options->RelayBandwidthBurst; + + return 0; +} + +/** + * Legacy validation/normalization function for the relay bandwidth accounting + * options. Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_accounting(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + /* 31851: the tests expect us to validate accounting, even when we are not + * in relay mode. */ + if (accounting_parse_options(options, 1)<0) + REJECT("Failed to parse accounting options. See logs for details."); + + if (options->AccountingMax) { + if (options->RendConfigLines && server_mode(options)) { + log_warn(LD_CONFIG, "Using accounting with a hidden service and an " + "ORPort is risky: your hidden service(s) and your public " + "address will all turn off at the same time, which may alert " + "observers that they are being run by the same party."); + } else if (config_count_key(options->RendConfigLines, + "HiddenServiceDir") > 1) { + log_warn(LD_CONFIG, "Using accounting with multiple hidden services is " + "risky: they will all turn off at the same time, which may " + "alert observers that they are being run by the same party."); + } + } + + options->AccountingRule = ACCT_MAX; + if (options->AccountingRule_option) { + if (!strcmp(options->AccountingRule_option, "sum")) + options->AccountingRule = ACCT_SUM; + else if (!strcmp(options->AccountingRule_option, "max")) + options->AccountingRule = ACCT_MAX; + else if (!strcmp(options->AccountingRule_option, "in")) + options->AccountingRule = ACCT_IN; + else if (!strcmp(options->AccountingRule_option, "out")) + options->AccountingRule = ACCT_OUT; + else + REJECT("AccountingRule must be 'sum', 'max', 'in', or 'out'"); + } + + return 0; +} + +/** Verify whether lst is a list of strings containing valid-looking + * comma-separated nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' + * to any nickname or fingerprint that needs it. Also splits comma-separated + * list elements into multiple elements. Return 0 on success. + * Warn and return -1 on failure. + */ +static int +normalize_nickname_list(config_line_t **normalized_out, + const config_line_t *lst, const char *name, + char **msg) +{ + if (!lst) + return 0; + + config_line_t *new_nicknames = NULL; + config_line_t **new_nicknames_next = &new_nicknames; + + const config_line_t *cl; + for (cl = lst; cl; cl = cl->next) { + const char *line = cl->value; + if (!line) + continue; + + int valid_line = 1; + smartlist_t *sl = smartlist_new(); + smartlist_split_string(sl, line, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0); + SMARTLIST_FOREACH_BEGIN(sl, char *, s) + { + char *normalized = NULL; + if (!is_legal_nickname_or_hexdigest(s)) { + // check if first char is dollar + if (s[0] != '$') { + // Try again but with a dollar symbol prepended + char *prepended; + tor_asprintf(&prepended, "$%s", s); + + if (is_legal_nickname_or_hexdigest(prepended)) { + // The nickname is valid when it's prepended, set it as the + // normalized version + normalized = prepended; + } else { + // Still not valid, free and fallback to error message + tor_free(prepended); + } + } + + if (!normalized) { + tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name); + valid_line = 0; + break; + } + } else { + normalized = tor_strdup(s); + } + + config_line_t *next = tor_malloc_zero(sizeof(*next)); + next->key = tor_strdup(cl->key); + next->value = normalized; + next->next = NULL; + + *new_nicknames_next = next; + new_nicknames_next = &next->next; + } SMARTLIST_FOREACH_END(s); + + SMARTLIST_FOREACH(sl, char *, s, tor_free(s)); + smartlist_free(sl); + + if (!valid_line) { + config_free_lines(new_nicknames); + return -1; + } + } + + *normalized_out = new_nicknames; + + return 0; +} + +#define ONE_MEGABYTE (UINT64_C(1) << 20) + +/* If we have less than 300 MB suggest disabling dircache */ +#define DIRCACHE_MIN_MEM_MB 300 +#define DIRCACHE_MIN_MEM_BYTES (DIRCACHE_MIN_MEM_MB*ONE_MEGABYTE) +#define STRINGIFY(val) #val + +/** Create a warning message for emitting if we are a dircache but may not have + * enough system memory, or if we are not a dircache but probably should be. + * Return -1 when a message is returned in *msg*, else return 0. */ +STATIC int +have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem, + char **msg) +{ + *msg = NULL; + /* XXX We should possibly be looking at MaxMemInQueues here + * unconditionally. Or we should believe total_mem unconditionally. */ + if (total_mem == 0) { + if (get_total_system_memory(&total_mem) < 0) { + total_mem = options->MaxMemInQueues >= SIZE_MAX ? + SIZE_MAX : (size_t)options->MaxMemInQueues; + } + } + if (options->DirCache) { + if (total_mem < DIRCACHE_MIN_MEM_BYTES) { + if (options->BridgeRelay) { + tor_asprintf(msg, "Running a Bridge with less than %d MB of memory " + "is not recommended.", DIRCACHE_MIN_MEM_MB); + } else { + tor_asprintf(msg, "Being a directory cache (default) with less than " + "%d MB of memory is not recommended and may consume " + "most of the available resources. Consider disabling " + "this functionality by setting the DirCache option " + "to 0.", DIRCACHE_MIN_MEM_MB); + } + } + } else { + if (total_mem >= DIRCACHE_MIN_MEM_BYTES) { + *msg = tor_strdup("DirCache is disabled and we are configured as a " + "relay. We will not become a Guard."); + } + } + return *msg == NULL ? 0 : -1; +} +#undef STRINGIFY + +/** + * Legacy validation/normalization function for the relay mode options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_mode(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (server_mode(options) && options->RendConfigLines) + log_warn(LD_CONFIG, + "Tor is currently configured as a relay and a hidden service. " + "That's not very secure: you should probably run your hidden service " + "in a separate Tor process, at least -- see " + "https://trac.torproject.org/8742"); + + if (options->BridgeRelay && options->DirPort_set) { + log_warn(LD_CONFIG, "Can't set a DirPort on a bridge relay; disabling " + "DirPort"); + config_free_lines(options->DirPort_lines); + options->DirPort_lines = NULL; + options->DirPort_set = 0; + } + + if (options->DirPort_set && !options->DirCache) { + REJECT("DirPort configured but DirCache disabled. DirPort requires " + "DirCache."); + } + + if (options->BridgeRelay && !options->DirCache) { + REJECT("We're a bridge but DirCache is disabled. BridgeRelay requires " + "DirCache."); + } + + if (options->BridgeRelay == 1 && ! options->ORPort_set) + REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid " + "combination."); + + if (server_mode(options)) { + char *dircache_msg = NULL; + if (have_enough_mem_for_dircache(options, 0, &dircache_msg)) { + log_warn(LD_CONFIG, "%s", dircache_msg); + tor_free(dircache_msg); + } + } + + if (options->MyFamily_lines && options->BridgeRelay) { + log_warn(LD_CONFIG, "Listing a family for a bridge relay is not " + "supported: it can reveal bridge fingerprints to censors. " + "You should also make sure you aren't listing this bridge's " + "fingerprint in any other MyFamily."); + } + if (options->MyFamily_lines && !options->ContactInfo) { + log_warn(LD_CONFIG, "MyFamily is set but ContactInfo is not configured. " + "ContactInfo should always be set when MyFamily option is too."); + } + if (normalize_nickname_list(&options->MyFamily, + options->MyFamily_lines, "MyFamily", msg)) + return -1; + + if (options->ConstrainedSockets) { + if (options->DirPort_set) { + /* Providing cached directory entries while system TCP buffers are scarce + * will exacerbate the socket errors. Suggest that this be disabled. */ + COMPLAIN("You have requested constrained socket buffers while also " + "serving directory entries via DirPort. It is strongly " + "suggested that you disable serving directory requests when " + "system TCP buffer resources are scarce."); + } + } + + return 0; +} + +/** + * Legacy validation/normalization function for the relay testing options + * in options. Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_relay_testing(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2) + REJECT("SigningKeyLifetime is too short."); + if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2) + REJECT("LinkCertLifetime is too short."); + if (options->TestingAuthKeyLifetime < options->TestingLinkKeySlop*2) + REJECT("TestingAuthKeyLifetime is too short."); + + return 0; +} + +/** Return 1 if any change from <b>old_options</b> to <b>new_options</b> + * will require us to rotate the CPU and DNS workers; else return 0. */ +static int +options_transition_affects_workers(const or_options_t *old_options, + const or_options_t *new_options) +{ + YES_IF_CHANGED_STRING(DataDirectory); + YES_IF_CHANGED_INT(NumCPUs); + YES_IF_CHANGED_LINELIST(ORPort_lines); + YES_IF_CHANGED_BOOL(ServerDNSSearchDomains); + YES_IF_CHANGED_BOOL(SafeLogging_); + YES_IF_CHANGED_BOOL(ClientOnly); + YES_IF_CHANGED_BOOL(LogMessageDomains); + YES_IF_CHANGED_LINELIST(Logs); + + if (server_mode(old_options) != server_mode(new_options) || + public_server_mode(old_options) != public_server_mode(new_options) || + dir_server_mode(old_options) != dir_server_mode(new_options)) + return 1; + + /* Nothing that changed matters. */ + return 0; +} + +/** Return 1 if any change from <b>old_options</b> to <b>new_options</b> + * will require us to generate a new descriptor; else return 0. */ +static int +options_transition_affects_descriptor(const or_options_t *old_options, + const or_options_t *new_options) +{ + /* XXX We can be smarter here. If your DirPort isn't being + * published and you just turned it off, no need to republish. Etc. */ + + YES_IF_CHANGED_STRING(DataDirectory); + YES_IF_CHANGED_STRING(Nickname); + YES_IF_CHANGED_STRING(Address); + YES_IF_CHANGED_LINELIST(ExitPolicy); + YES_IF_CHANGED_BOOL(ExitRelay); + YES_IF_CHANGED_BOOL(ExitPolicyRejectPrivate); + YES_IF_CHANGED_BOOL(ExitPolicyRejectLocalInterfaces); + YES_IF_CHANGED_BOOL(IPv6Exit); + YES_IF_CHANGED_LINELIST(ORPort_lines); + YES_IF_CHANGED_LINELIST(DirPort_lines); + YES_IF_CHANGED_LINELIST(DirPort_lines); + YES_IF_CHANGED_BOOL(ClientOnly); + YES_IF_CHANGED_BOOL(DisableNetwork); + YES_IF_CHANGED_BOOL(PublishServerDescriptor_); + YES_IF_CHANGED_STRING(ContactInfo); + YES_IF_CHANGED_STRING(BridgeDistribution); + YES_IF_CHANGED_LINELIST(MyFamily); + YES_IF_CHANGED_STRING(AccountingStart); + YES_IF_CHANGED_INT(AccountingMax); + YES_IF_CHANGED_INT(AccountingRule); + YES_IF_CHANGED_BOOL(DirCache); + YES_IF_CHANGED_BOOL(AssumeReachable); + + if (relay_get_effective_bwrate(old_options) != + relay_get_effective_bwrate(new_options) || + relay_get_effective_bwburst(old_options) != + relay_get_effective_bwburst(new_options) || + public_server_mode(old_options) != public_server_mode(new_options)) + return 1; + + return 0; +} + +/** Fetch the active option list, and take relay actions based on it. All of + * the things we do should survive being done repeatedly. If present, + * <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + + const int transition_affects_workers = + old_options && options_transition_affects_workers(old_options, options); + + /* We want to reinit keys as needed before we do much of anything else: + keys are important, and other things can depend on them. */ + if (transition_affects_workers || + (authdir_mode_v3(options) && (!old_options || + !authdir_mode_v3(old_options)))) { + if (init_keys() < 0) { + log_warn(LD_BUG,"Error initializing keys; exiting"); + return -1; + } + } + + if (server_mode(options)) { + static int cdm_initialized = 0; + if (cdm_initialized == 0) { + cdm_initialized = 1; + consdiffmgr_configure(NULL); + consdiffmgr_validate(); + } + } + + /* Check for transitions that need action. */ + if (old_options) { + if (transition_affects_workers) { + log_info(LD_GENERAL, + "Worker-related options changed. Rotating workers."); + const int server_mode_turned_on = + server_mode(options) && !server_mode(old_options); + const int dir_server_mode_turned_on = + dir_server_mode(options) && !dir_server_mode(old_options); + + if (server_mode_turned_on || dir_server_mode_turned_on) { + cpu_init(); + } + + if (server_mode_turned_on) { + ip_address_changed(0); + if (have_completed_a_circuit() || !any_predicted_circuits(time(NULL))) + inform_testing_reachability(); + } + cpuworkers_rotate_keyinfo(); + } + } + + return 0; +} + +/** Fetch the active option list, and take relay accounting actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_accounting(const or_options_t *old_options) +{ + (void)old_options; + + const or_options_t *options = get_options(); + + /* Set up accounting */ + if (accounting_parse_options(options, 0)<0) { + // LCOV_EXCL_START + log_warn(LD_BUG,"Error in previously validated accounting options"); + return -1; + // LCOV_EXCL_STOP + } + if (accounting_is_enabled(options)) + configure_accounting(time(NULL)); + + return 0; +} + +/** Fetch the active option list, and take relay bandwidth actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_bandwidth(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + + /* Check for transitions that need action. */ + if (old_options) { + if (options->PerConnBWRate != old_options->PerConnBWRate || + options->PerConnBWBurst != old_options->PerConnBWBurst) + connection_or_update_token_buckets(get_connection_array(), options); + + if (options->RelayBandwidthRate != old_options->RelayBandwidthRate || + options->RelayBandwidthBurst != old_options->RelayBandwidthBurst) + connection_bucket_adjust(options); + } + + return 0; +} + +/** Fetch the active option list, and take bridge statistics actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_bridge_stats(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + +/* How long should we delay counting bridge stats after becoming a bridge? + * We use this so we don't count clients who used our bridge thinking it is + * a relay. If you change this, don't forget to change the log message + * below. It's 4 hours (the time it takes to stop being used by clients) + * plus some extra time for clock skew. */ +#define RELAY_BRIDGE_STATS_DELAY (6 * 60 * 60) + + /* Check for transitions that need action. */ + if (old_options) { + if (! bool_eq(options->BridgeRelay, old_options->BridgeRelay)) { + int was_relay = 0; + if (options->BridgeRelay) { + time_t int_start = time(NULL); + if (config_lines_eq(old_options->ORPort_lines,options->ORPort_lines)) { + int_start += RELAY_BRIDGE_STATS_DELAY; + was_relay = 1; + } + geoip_bridge_stats_init(int_start); + log_info(LD_CONFIG, "We are acting as a bridge now. Starting new " + "GeoIP stats interval%s.", was_relay ? " in 6 " + "hours from now" : ""); + } else { + geoip_bridge_stats_term(); + log_info(LD_GENERAL, "We are no longer acting as a bridge. " + "Forgetting GeoIP stats."); + } + } + } + + return 0; +} + +/** Fetch the active option list, and take relay statistics actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Sets <b>*print_notice_out</b> if we enabled stats, and need to print + * a stats log using options_act_relay_stats_msg(). + * + * If loading the GeoIP file failed, sets DirReqStatistics and + * EntryStatistics to 0. This breaks the normalization/act ordering + * introduced in 29211. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_stats(const or_options_t *old_options, + bool *print_notice_out) +{ + if (BUG(!print_notice_out)) + return -1; + + or_options_t *options = get_options_mutable(); + + if (options->CellStatistics || options->DirReqStatistics || + options->EntryStatistics || options->ExitPortStatistics || + options->ConnDirectionStatistics || + options->HiddenServiceStatistics) { + time_t now = time(NULL); + int print_notice = 0; + + if ((!old_options || !old_options->CellStatistics) && + options->CellStatistics) { + rep_hist_buffer_stats_init(now); + print_notice = 1; + } + if ((!old_options || !old_options->DirReqStatistics) && + options->DirReqStatistics) { + if (geoip_is_loaded(AF_INET)) { + geoip_dirreq_stats_init(now); + print_notice = 1; + } else { + /* disable statistics collection since we have no geoip file */ + /* 29211: refactor to avoid the normalisation/act inversion */ + options->DirReqStatistics = 0; + if (options->ORPort_set) + log_notice(LD_CONFIG, "Configured to measure directory request " + "statistics, but no GeoIP database found. " + "Please specify a GeoIP database using the " + "GeoIPFile option."); + } + } + if ((!old_options || !old_options->EntryStatistics) && + options->EntryStatistics && !should_record_bridge_info(options)) { + /* If we get here, we've started recording bridge info when we didn't + * do so before. Note that "should_record_bridge_info()" will + * always be false at this point, because of the earlier block + * that cleared EntryStatistics when public_server_mode() was false. + * We're leaving it in as defensive programming. */ + if (geoip_is_loaded(AF_INET) || geoip_is_loaded(AF_INET6)) { + geoip_entry_stats_init(now); + print_notice = 1; + } else { + options->EntryStatistics = 0; + log_notice(LD_CONFIG, "Configured to measure entry node " + "statistics, but no GeoIP database found. " + "Please specify a GeoIP database using the " + "GeoIPFile option."); + } + } + if ((!old_options || !old_options->ExitPortStatistics) && + options->ExitPortStatistics) { + rep_hist_exit_stats_init(now); + print_notice = 1; + } + if ((!old_options || !old_options->ConnDirectionStatistics) && + options->ConnDirectionStatistics) { + rep_hist_conn_stats_init(now); + } + if ((!old_options || !old_options->HiddenServiceStatistics) && + options->HiddenServiceStatistics) { + log_info(LD_CONFIG, "Configured to measure hidden service statistics."); + rep_hist_hs_stats_init(now); + } + if (print_notice) + *print_notice_out = 1; + } + + /* If we used to have statistics enabled but we just disabled them, + stop gathering them. */ + if (old_options && old_options->CellStatistics && + !options->CellStatistics) + rep_hist_buffer_stats_term(); + if (old_options && old_options->DirReqStatistics && + !options->DirReqStatistics) + geoip_dirreq_stats_term(); + if (old_options && old_options->EntryStatistics && + !options->EntryStatistics) + geoip_entry_stats_term(); + if (old_options && old_options->HiddenServiceStatistics && + !options->HiddenServiceStatistics) + rep_hist_hs_stats_term(); + if (old_options && old_options->ExitPortStatistics && + !options->ExitPortStatistics) + rep_hist_exit_stats_term(); + if (old_options && old_options->ConnDirectionStatistics && + !options->ConnDirectionStatistics) + rep_hist_conn_stats_term(); + + return 0; +} + +/** Print a notice about relay/dirauth stats being enabled. */ +void +options_act_relay_stats_msg(void) +{ + log_notice(LD_CONFIG, "Configured to measure statistics. Look for " + "the *-stats files that will first be written to the " + "data directory in 24 hours from now."); +} + +/** Fetch the active option list, and take relay descriptor actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_desc(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + + /* Since our options changed, we might need to regenerate and upload our + * server descriptor. + */ + if (!old_options || + options_transition_affects_descriptor(old_options, options)) + mark_my_descriptor_dirty("config change"); + + return 0; +} + +/** Fetch the active option list, and take relay DoS actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_dos(const or_options_t *old_options) +{ + const or_options_t *options = get_options(); + + /* DoS mitigation subsystem only applies to public relay. */ + if (public_server_mode(options)) { + /* If we are configured as a relay, initialize the subsystem. Even on HUP, + * this is safe to call as it will load data from the current options + * or/and the consensus. */ + dos_init(); + } else if (old_options && public_server_mode(old_options)) { + /* Going from relay to non relay, clean it up. */ + dos_free_all(); + } + + return 0; +} + +/** Fetch the active option list, and take dirport actions based on + * it. All of the things we do should survive being done repeatedly. If + * present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_relay_dir(const or_options_t *old_options) +{ + (void)old_options; + + const or_options_t *options = get_options(); + + if (!public_server_mode(options)) + return 0; + + /* Load the webpage we're going to serve every time someone asks for '/' on + our DirPort. */ + tor_free(global_dirfrontpagecontents); + if (options->DirPortFrontPage) { + global_dirfrontpagecontents = + read_file_to_str(options->DirPortFrontPage, 0, NULL); + if (!global_dirfrontpagecontents) { + log_warn(LD_CONFIG, + "DirPortFrontPage file '%s' not found. Continuing anyway.", + options->DirPortFrontPage); + } + } + + return 0; +} diff --git a/src/feature/relay/relay_config.h b/src/feature/relay/relay_config.h new file mode 100644 index 0000000000..c70c322d88 --- /dev/null +++ b/src/feature/relay/relay_config.h @@ -0,0 +1,196 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_config.h + * @brief Header for feature/relay/relay_config.c + **/ + +#ifndef TOR_FEATURE_RELAY_RELAY_CONFIG_H +#define TOR_FEATURE_RELAY_RELAY_CONFIG_H + +struct or_options_t; + +#ifdef HAVE_MODULE_RELAY + +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +struct smartlist_t; + +int options_validate_relay_mode(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +MOCK_DECL(const char*, relay_get_dirportfrontpage, (void)); +void relay_config_free_all(void); + +uint32_t relay_get_effective_bwrate(const struct or_options_t *options); +uint32_t relay_get_effective_bwburst(const struct or_options_t *options); + +void port_warn_nonlocal_ext_orports(const struct smartlist_t *ports, + const char *portname); + +int port_parse_ports_relay(struct or_options_t *options, + char **msg, + struct smartlist_t *ports_out, + int *have_low_ports_out); +void port_update_port_set_relay(struct or_options_t *options, + const struct smartlist_t *ports); + +int options_validate_relay_os(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_relay_info(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_publish_server(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_relay_padding(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_relay_bandwidth(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_relay_accounting(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_validate_relay_testing(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +int options_act_relay(const struct or_options_t *old_options); +int options_act_relay_accounting(const struct or_options_t *old_options); +int options_act_relay_bandwidth(const struct or_options_t *old_options); +int options_act_bridge_stats(const struct or_options_t *old_options); + +int options_act_relay_stats(const struct or_options_t *old_options, + bool *print_notice_out); +void options_act_relay_stats_msg(void); + +int options_act_relay_desc(const struct or_options_t *old_options); +int options_act_relay_dos(const struct or_options_t *old_options); +int options_act_relay_dir(const struct or_options_t *old_options); + +#ifdef RELAY_CONFIG_PRIVATE + +STATIC int check_bridge_distribution_setting(const char *bd); +STATIC int have_enough_mem_for_dircache(const struct or_options_t *options, + size_t total_mem, char **msg); + +#endif /* defined(RELAY_CONFIG_PRIVATE) */ + +#else /* !defined(HAVE_MODULE_RELAY) */ + +#include "lib/cc/compat_compiler.h" + +/** When tor is compiled with the relay module disabled, it can't be + * configured as a relay or bridge. + * + * Always sets ClientOnly to 1. + * + * Returns -1 and sets msg to a newly allocated string, if ORPort, DirPort, + * DirCache, or BridgeRelay are set in options. Otherwise returns 0. */ +static inline int +options_validate_relay_mode(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg) +{ + (void)old_options; + + /* Only check the primary options for now, #29211 will disable more + * options. These ORPort and DirPort checks are too strict, and will + * reject valid configs that disable ports, like "ORPort 0". */ + if (options->DirCache || + options->BridgeRelay || + options->ORPort_lines || + options->DirPort_lines) { + /* REJECT() this configuration */ + *msg = tor_strdup("This tor was built with relay mode disabled. " + "It can not be configured with an ORPort, a DirPort, " + "DirCache 1, or BridgeRelay 1."); + return -1; + } + + return 0; +} + +static inline int +port_parse_ports_relay(or_options_t *options, + char **msg, + smartlist_t *ports_out, + int *have_low_ports_out) +{ + (void)options; + (void)msg; + (void)ports_out; + if (*have_low_ports_out < 0) + *have_low_ports_out = 0; + return 0; +} + +#define relay_get_dirportfrontpage() \ + (NULL) +#define relay_config_free_all() \ + STMT_BEGIN STMT_END + +#define relay_get_effective_bwrate(options) \ + (((void)(options)),0) +#define relay_get_effective_bwburst(options) \ + (((void)(options)),0) + +#define port_warn_nonlocal_ext_orports(ports, portname) \ + (((void)(ports)),((void)(portname))) + +#define port_update_port_set_relay(options, ports) \ + (((void)(options)),((void)(ports))) + +#define options_validate_relay_os(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_relay_info(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_publish_server(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_relay_padding(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_relay_bandwidth(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_relay_accounting(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_validate_relay_testing(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) + +#define options_act_relay(old_options) \ + (((void)(old_options)),0) +#define options_act_relay_accounting(old_options) \ + (((void)(old_options)),0) +#define options_act_relay_bandwidth(old_options) \ + (((void)(old_options)),0) +#define options_act_bridge_stats(old_options) \ + (((void)(old_options)),0) + +#define options_act_relay_stats(old_options, print_notice_out) \ + (((void)(old_options)),((void)(print_notice_out)),0) +#define options_act_relay_stats_msg() \ + STMT_BEGIN STMT_END + +#define options_act_relay_desc(old_options) \ + (((void)(old_options)),0) +#define options_act_relay_dos(old_options) \ + (((void)(old_options)),0) +#define options_act_relay_dir(old_options) \ + (((void)(old_options)),0) + +#endif /* defined(HAVE_MODULE_RELAY) */ + +#endif /* !defined(TOR_FEATURE_RELAY_RELAY_CONFIG_H) */ diff --git a/src/feature/relay/relay_periodic.c b/src/feature/relay/relay_periodic.c new file mode 100644 index 0000000000..b751323e0d --- /dev/null +++ b/src/feature/relay/relay_periodic.c @@ -0,0 +1,310 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_periodic.c + * @brief Periodic functions for the relay subsytem + **/ + +#include "orconfig.h" +#include "core/or/or.h" + +#include "core/mainloop/periodic.h" +#include "core/mainloop/cpuworker.h" // XXXX use a pubsub event. +#include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.h" +#include "core/or/circuituse.h" // XXXX move have_performed_bandwidth_test + +#include "feature/relay/dns.h" +#include "feature/relay/relay_periodic.h" +#include "feature/relay/router.h" +#include "feature/relay/routerkeys.h" +#include "feature/relay/routermode.h" +#include "feature/relay/selftest.h" +#include "feature/stats/predict_ports.h" + +#include "lib/crypt_ops/crypto_rand.h" + +#include "feature/nodelist/routerinfo_st.h" +#include "feature/control/control_events.h" + +#ifndef COCCI +#define DECLARE_EVENT(name, roles, flags) \ + static periodic_event_item_t name ## _event = \ + PERIODIC_EVENT(name, \ + PERIODIC_EVENT_ROLE_##roles, \ + flags) +#endif /* !defined(COCCI) */ + +#define FL(name) (PERIODIC_EVENT_FLAG_##name) + +/** + * Periodic callback: If we're a server and initializing dns failed, retry. + */ +static int +retry_dns_callback(time_t now, const or_options_t *options) +{ + (void)now; +#define RETRY_DNS_INTERVAL (10*60) + if (server_mode(options) && has_dns_init_failed()) + dns_init(); + return RETRY_DNS_INTERVAL; +} + +DECLARE_EVENT(retry_dns, ROUTER, 0); + +static int dns_honesty_first_time = 1; + +/** + * Periodic event: if we're an exit, see if our DNS server is telling us + * obvious lies. + */ +static int +check_dns_honesty_callback(time_t now, const or_options_t *options) +{ + (void)now; + /* 9. and if we're an exit node, check whether our DNS is telling stories + * to us. */ + if (net_is_disabled() || + ! public_server_mode(options) || + router_my_exit_policy_is_reject_star()) + return PERIODIC_EVENT_NO_UPDATE; + + if (dns_honesty_first_time) { + /* Don't launch right when we start */ + dns_honesty_first_time = 0; + return crypto_rand_int_range(60, 180); + } + + dns_launch_correctness_checks(); + return 12*3600 + crypto_rand_int(12*3600); +} + +DECLARE_EVENT(check_dns_honesty, RELAY, FL(NEED_NET)); + +/* Periodic callback: rotate the onion keys after the period defined by the + * "onion-key-rotation-days" consensus parameter, shut down and restart all + * cpuworkers, and update our descriptor if necessary. + */ +static int +rotate_onion_key_callback(time_t now, const or_options_t *options) +{ + if (server_mode(options)) { + int onion_key_lifetime = get_onion_key_lifetime(); + time_t rotation_time = get_onion_key_set_at()+onion_key_lifetime; + if (rotation_time > now) { + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + + log_info(LD_GENERAL,"Rotating onion key."); + rotate_onion_key(); + cpuworkers_rotate_keyinfo(); + if (router_rebuild_descriptor(1)<0) { + log_info(LD_CONFIG, "Couldn't rebuild router descriptor"); + } + if (advertised_server_mode() && !net_is_disabled()) + router_upload_dir_desc_to_dirservers(0); + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + return PERIODIC_EVENT_NO_UPDATE; +} + +DECLARE_EVENT(rotate_onion_key, ROUTER, 0); + +/** Periodic callback: consider rebuilding or and re-uploading our descriptor + * (if we've passed our internal checks). */ +static int +check_descriptor_callback(time_t now, const or_options_t *options) +{ +/** How often do we check whether part of our router info has changed in a + * way that would require an upload? That includes checking whether our IP + * address has changed. */ +#define CHECK_DESCRIPTOR_INTERVAL (60) + + (void)options; + + /* 2b. Once per minute, regenerate and upload the descriptor if the old + * one is inaccurate. */ + if (!net_is_disabled()) { + check_descriptor_bandwidth_changed(now); + check_descriptor_ipaddress_changed(now); + mark_my_descriptor_dirty_if_too_old(now); + consider_publishable_server(0); + } + + return CHECK_DESCRIPTOR_INTERVAL; +} + +DECLARE_EVENT(check_descriptor, ROUTER, FL(NEED_NET)); + +static int dirport_reachability_count = 0; + +/** + * Periodic callback: check whether we're reachable (as a relay), and + * whether our bandwidth has changed enough that we need to + * publish a new descriptor. + */ +static int +check_for_reachability_bw_callback(time_t now, const or_options_t *options) +{ + /* XXXX This whole thing was stuck in the middle of what is now + * XXXX check_descriptor_callback. I'm not sure it's right. */ + + /* also, check religiously for reachability, if it's within the first + * 20 minutes of our uptime. */ + if (server_mode(options) && + (have_completed_a_circuit() || !any_predicted_circuits(now)) && + !net_is_disabled()) { + if (get_uptime() < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { + router_do_reachability_checks(1, dirport_reachability_count==0); + if (++dirport_reachability_count > 5) + dirport_reachability_count = 0; + return 1; + } else { + /* If we haven't checked for 12 hours and our bandwidth estimate is + * low, do another bandwidth test. This is especially important for + * bridges, since they might go long periods without much use. */ + const routerinfo_t *me = router_get_my_routerinfo(); + static int first_time = 1; + if (!first_time && me && + me->bandwidthcapacity < me->bandwidthrate && + me->bandwidthcapacity < 51200) { + reset_bandwidth_test(); + } + first_time = 0; +#define BANDWIDTH_RECHECK_INTERVAL (12*60*60) + return BANDWIDTH_RECHECK_INTERVAL; + } + } + return CHECK_DESCRIPTOR_INTERVAL; +} + +DECLARE_EVENT(check_for_reachability_bw, ROUTER, FL(NEED_NET)); + +/** + * Callback: Send warnings if Tor doesn't find its ports reachable. + */ +static int +reachability_warnings_callback(time_t now, const or_options_t *options) +{ + (void) now; + + if (get_uptime() < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { + return (int)(TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT - get_uptime()); + } + + if (server_mode(options) && + !net_is_disabled() && + have_completed_a_circuit()) { + /* every 20 minutes, check and complain if necessary */ + const routerinfo_t *me = router_get_my_routerinfo(); + if (me && !check_whether_orport_reachable(options)) { + char *address = tor_dup_ip(me->addr); + log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that " + "its ORPort is reachable. Relays do not publish descriptors " + "until their ORPort and DirPort are reachable. Please check " + "your firewalls, ports, address, /etc/hosts file, etc.", + address, me->or_port); + control_event_server_status(LOG_WARN, + "REACHABILITY_FAILED ORADDRESS=%s:%d", + address, me->or_port); + tor_free(address); + } + + if (me && !check_whether_dirport_reachable(options)) { + char *address = tor_dup_ip(me->addr); + log_warn(LD_CONFIG, + "Your server (%s:%d) has not managed to confirm that its " + "DirPort is reachable. Relays do not publish descriptors " + "until their ORPort and DirPort are reachable. Please check " + "your firewalls, ports, address, /etc/hosts file, etc.", + address, me->dir_port); + control_event_server_status(LOG_WARN, + "REACHABILITY_FAILED DIRADDRESS=%s:%d", + address, me->dir_port); + tor_free(address); + } + } + + return TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT; +} + +DECLARE_EVENT(reachability_warnings, ROUTER, FL(NEED_NET)); + +/* Periodic callback: Every 30 seconds, check whether it's time to make new + * Ed25519 subkeys. + */ +static int +check_ed_keys_callback(time_t now, const or_options_t *options) +{ + if (server_mode(options)) { + if (should_make_new_ed_keys(options, now)) { + int new_signing_key = load_ed_keys(options, now); + if (new_signing_key < 0 || + generate_ed_link_cert(options, now, new_signing_key > 0)) { + log_err(LD_OR, "Unable to update Ed25519 keys! Exiting."); + tor_shutdown_event_loop_and_exit(1); + } + } + return 30; + } + return PERIODIC_EVENT_NO_UPDATE; +} + +DECLARE_EVENT(check_ed_keys, ROUTER, 0); + +/* Period callback: Check if our old onion keys are still valid after the + * period of time defined by the consensus parameter + * "onion-key-grace-period-days", otherwise expire them by setting them to + * NULL. + */ +static int +check_onion_keys_expiry_time_callback(time_t now, const or_options_t *options) +{ + if (server_mode(options)) { + int onion_key_grace_period = get_onion_key_grace_period(); + time_t expiry_time = get_onion_key_set_at()+onion_key_grace_period; + if (expiry_time > now) { + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + + log_info(LD_GENERAL, "Expiring old onion keys."); + expire_old_onion_keys(); + cpuworkers_rotate_keyinfo(); + return ONION_KEY_CONSENSUS_CHECK_INTERVAL; + } + + return PERIODIC_EVENT_NO_UPDATE; +} + +DECLARE_EVENT(check_onion_keys_expiry_time, ROUTER, 0); + +void +relay_register_periodic_events(void) +{ + periodic_events_register(&retry_dns_event); + periodic_events_register(&check_dns_honesty_event); + periodic_events_register(&rotate_onion_key_event); + periodic_events_register(&check_descriptor_event); + periodic_events_register(&check_for_reachability_bw_event); + periodic_events_register(&reachability_warnings_event); + periodic_events_register(&check_ed_keys_event); + periodic_events_register(&check_onion_keys_expiry_time_event); + + dns_honesty_first_time = 1; + dirport_reachability_count = 0; +} + +/** + * Update our schedule so that we'll check whether we need to update our + * descriptor immediately, rather than after up to CHECK_DESCRIPTOR_INTERVAL + * seconds. + */ +void +reschedule_descriptor_update_check(void) +{ + periodic_event_reschedule(&check_descriptor_event); +} diff --git a/src/feature/relay/relay_periodic.h b/src/feature/relay/relay_periodic.h new file mode 100644 index 0000000000..ccda9a440b --- /dev/null +++ b/src/feature/relay/relay_periodic.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_periodic.h + * @brief Header for feature/relay/relay_periodic.c + **/ + +#ifndef TOR_FEATURE_RELAY_RELAY_PERIODIC_H +#define TOR_FEATURE_RELAY_RELAY_PERIODIC_H + +#ifdef HAVE_MODULE_RELAY + +void relay_register_periodic_events(void); +void reschedule_descriptor_update_check(void); + +#else /* !defined(HAVE_MODULE_RELAY) */ + +#include "lib/cc/compat_compiler.h" + +#define relay_register_periodic_events() \ + STMT_NIL +#define reschedule_descriptor_update_check() \ + STMT_NIL + +#endif /* defined(HAVE_MODULE_RELAY) */ + +#endif /* !defined(TOR_FEATURE_RELAY_RELAY_PERIODIC_H) */ diff --git a/src/feature/relay/relay_stub.c b/src/feature/relay/relay_stub.c new file mode 100644 index 0000000000..42e08fcb6c --- /dev/null +++ b/src/feature/relay/relay_stub.c @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_stub.c + * @brief Stub declarations for use when relay module is disabled. + **/ + +#include "orconfig.h" +#include "feature/relay/relay_sys.h" +#include "lib/subsys/subsys.h" + +const struct subsys_fns_t sys_relay = { + .name = "relay", + .supported = false, + .level = RELAY_SUBSYS_LEVEL, +}; diff --git a/src/feature/relay/relay_sys.c b/src/feature/relay/relay_sys.c new file mode 100644 index 0000000000..34489cf5aa --- /dev/null +++ b/src/feature/relay/relay_sys.c @@ -0,0 +1,48 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_sys.c + * @brief Subsystem definitions for the relay module. + **/ + +#include "orconfig.h" +#include "core/or/or.h" + +#include "feature/relay/dns.h" +#include "feature/relay/ext_orport.h" +#include "feature/relay/onion_queue.h" +#include "feature/relay/relay_periodic.h" +#include "feature/relay/relay_sys.h" +#include "feature/relay/routerkeys.h" +#include "feature/relay/router.h" + +#include "lib/subsys/subsys.h" + +static int +subsys_relay_initialize(void) +{ + relay_register_periodic_events(); + return 0; +} + +static void +subsys_relay_shutdown(void) +{ + dns_free_all(); + ext_orport_free_all(); + clear_pending_onions(); + routerkeys_free_all(); + router_free_all(); +} + +const struct subsys_fns_t sys_relay = { + .name = "relay", + .supported = true, + .level = RELAY_SUBSYS_LEVEL, + .initialize = subsys_relay_initialize, + .shutdown = subsys_relay_shutdown, +}; diff --git a/src/feature/relay/relay_sys.h b/src/feature/relay/relay_sys.h new file mode 100644 index 0000000000..9bad93a6c9 --- /dev/null +++ b/src/feature/relay/relay_sys.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_sys.h + * @brief Header for feature/relay/relay_sys.c + **/ + +#ifndef TOR_FEATURE_RELAY_RELAY_SYS_H +#define TOR_FEATURE_RELAY_RELAY_SYS_H + +extern const struct subsys_fns_t sys_relay; + +/** + * Subsystem level for the relay system. + * + * Defined here so that it can be shared between the real and stub + * definitions. + **/ +#define RELAY_SUBSYS_LEVEL 50 + +#endif /* !defined(TOR_FEATURE_RELAY_RELAY_SYS_H) */ diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index 283f7c326b..3e92f202e6 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define ROUTER_PRIVATE @@ -16,7 +16,7 @@ #include "core/or/policies.h" #include "core/or/protover.h" #include "feature/client/transports.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/process_descs.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" @@ -30,10 +30,12 @@ #include "feature/nodelist/dirlist.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nickname.h" +#include "feature/nodelist/nodefamily.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/nodelist/torcert.h" #include "feature/relay/dns.h" +#include "feature/relay/relay_config.h" #include "feature/relay/router.h" #include "feature/relay/routerkeys.h" #include "feature/relay/routermode.h" @@ -49,6 +51,7 @@ #include "lib/encoding/confline.h" #include "lib/osinfo/uname.h" #include "lib/tls/tortls.h" +#include "lib/version/torversion.h" #include "feature/dirauth/authmode.h" @@ -58,6 +61,7 @@ #include "feature/dircommon/dir_connection_st.h" #include "feature/nodelist/authority_cert_st.h" #include "feature/nodelist/extrainfo_st.h" +#include "feature/nodelist/networkstatus_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" @@ -149,6 +153,8 @@ routerinfo_err_to_string(int err) return "Cannot generate descriptor"; case TOR_ROUTERINFO_ERROR_DESC_REBUILDING: return "Descriptor still rebuilding - not ready yet"; + case TOR_ROUTERINFO_ERROR_INTERNAL_BUG: + return "Internal bug, see logs for details"; } log_warn(LD_BUG, "unknown routerinfo error %d - shouldn't happen", err); @@ -191,8 +197,8 @@ set_onion_key(crypto_pk_t *k) /** Return the current onion key. Requires that the onion key has been * loaded or generated. */ -crypto_pk_t * -get_onion_key(void) +MOCK_IMPL(crypto_pk_t *, +get_onion_key,(void)) { tor_assert(onionkey); return onionkey; @@ -239,7 +245,7 @@ expire_old_onion_keys(void) lastonionkey = NULL; } - /* We zero out the keypair. See the tor_mem_is_zero() check made in + /* We zero out the keypair. See the fast_mem_is_zero() check made in * construct_ntor_key_map() below. */ memset(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); @@ -266,11 +272,12 @@ expire_old_onion_keys(void) /** 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) +MOCK_IMPL(STATIC const struct 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 * @@ -281,12 +288,12 @@ construct_ntor_key_map(void) const uint8_t *cur_pk = curve25519_onion_key.pubkey.public_key; const uint8_t *last_pk = last_curve25519_onion_key.pubkey.public_key; - if (!tor_mem_is_zero((const char *)cur_pk, CURVE25519_PUBKEY_LEN)) { + if (!fast_mem_is_zero((const char *)cur_pk, CURVE25519_PUBKEY_LEN)) { dimap_add_entry(&m, cur_pk, tor_memdup(&curve25519_onion_key, sizeof(curve25519_keypair_t))); } - if (!tor_mem_is_zero((const char*)last_pk, CURVE25519_PUBKEY_LEN) && + if (!fast_mem_is_zero((const char*)last_pk, CURVE25519_PUBKEY_LEN) && tor_memneq(cur_pk, last_pk, CURVE25519_PUBKEY_LEN)) { dimap_add_entry(&m, last_pk, tor_memdup(&last_curve25519_onion_key, @@ -337,6 +344,16 @@ set_server_identity_key(crypto_pk_t *k) } } +#ifdef TOR_UNIT_TESTS +/** Testing only -- set the server's RSA identity digest to + * be <b>digest</b> */ +void +set_server_identity_key_digest_testing(const uint8_t *digest) +{ + memcpy(server_identitykey_digest, digest, DIGEST_LEN); +} +#endif /* defined(TOR_UNIT_TESTS) */ + /** 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 @@ -356,11 +373,13 @@ assert_identity_keys_ok(void) } } +#ifdef HAVE_MODULE_RELAY + /** 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) +MOCK_IMPL(crypto_pk_t *, +get_server_identity_key,(void)) { tor_assert(server_identitykey); tor_assert(server_mode(get_options())); @@ -368,6 +387,8 @@ get_server_identity_key(void) return server_identitykey; } +#endif /* defined(HAVE_MODULE_RELAY) */ + /** Return true iff we are a server and the server identity key * has been set. */ int @@ -634,7 +655,7 @@ load_authority_keyset(int legacy, crypto_pk_t **key_out, fname); goto done; } - parsed = authority_cert_parse_from_string(cert, &eos); + parsed = authority_cert_parse_from_string(cert, strlen(cert), &eos); if (!parsed) { log_warn(LD_DIR, "Unable to parse certificate in %s", fname); goto done; @@ -866,15 +887,6 @@ 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; } @@ -1031,7 +1043,7 @@ init_keys(void) return -1; keydir = get_keydir_fname("secret_onion_key_ntor.old"); - if (tor_mem_is_zero((const char *) + if (fast_mem_is_zero((const char *) last_curve25519_onion_key.pubkey.public_key, CURVE25519_PUBKEY_LEN) && file_status(keydir) == FN_FILE) { @@ -1062,8 +1074,10 @@ init_keys(void) 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())) { + /* We need to add our own fingerprint and ed25519 key so it gets + * recognized. */ + if (dirserv_add_own_fingerprint(get_server_identity_key(), + get_master_identity_key())) { log_err(LD_GENERAL,"Error adding own fingerprint to set of relays"); return -1; } @@ -1202,7 +1216,7 @@ router_should_be_dirserver(const or_options_t *options, int dir_port) * 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); + uint32_t effective_bw = relay_get_effective_bwrate(options); uint64_t acc_bytes; if (!interval_length) { log_warn(LD_BUG, "An accounting interval is not allowed to be zero " @@ -1384,7 +1398,7 @@ consider_publishable_server(int force) } /** Return the port of the first active listener of type - * <b>listener_type</b>. */ + * <b>listener_type</b>. Returns 0 if no port is found. */ /** XXX not a very good interface. it's not reliable when there are multiple listeners. */ uint16_t @@ -1406,8 +1420,7 @@ router_get_active_listener_port_by_type_af(int listener_type, /** 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". - */ + * if ORPort is "auto". Returns 0 if no port is found. */ uint16_t router_get_advertised_or_port(const or_options_t *options) { @@ -1511,9 +1524,9 @@ static extrainfo_t *desc_extrainfo = NULL; 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; +STATIC time_t desc_clean_since = 0; /** Why did we mark the descriptor dirty? */ -static const char *desc_dirty_reason = "Tor just started"; +STATIC const char *desc_dirty_reason = "Tor just started"; /** Boolean: do we need to regenerate the above? */ static int desc_needs_upload = 0; @@ -1732,10 +1745,6 @@ 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 @@ -1848,26 +1857,159 @@ router_check_descriptor_address_consistency(uint32_t 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. +/** A list of nicknames that we've warned about including in our family, + * for one reason or another. */ +static smartlist_t *warned_family = NULL; + +/** + * Return a new smartlist containing the family members configured in + * <b>options</b>. Warn about invalid or missing entries. Return NULL + * if this relay should not declare a family. + **/ +STATIC smartlist_t * +get_my_declared_family(const or_options_t *options) +{ + if (!options->MyFamily) + return NULL; + + if (options->BridgeRelay) + return NULL; + + if (!warned_family) + warned_family = smartlist_new(); + + smartlist_t *declared_family = smartlist_new(); + config_line_t *family; + + /* First we try to get the whole family in the form of hexdigests. */ + for (family = options->MyFamily; family; family = family->next) { + char *name = family->value; + const node_t *member; + if (options->Nickname && !strcasecmp(name, options->Nickname)) + continue; /* Don't list ourself by nickname, that's redundant */ + else + member = node_get_by_nickname(name, 0); + + if (!member) { + /* This node doesn't seem to exist, so warn about it if it is not + * a hexdigest. */ + int is_legal = is_legal_nickname_or_hexdigest(name); + if (!smartlist_contains_string(warned_family, name) && + !is_legal_hexdigest(name)) { + if (is_legal) + log_warn(LD_CONFIG, + "There is a router named %s in my declared family, but " + "I have no descriptor for it. I'll use the nickname " + "as is, but this may confuse clients. Please list it " + "by identity digest instead.", escaped(name)); + else + log_warn(LD_CONFIG, "There is a router named %s in my declared " + "family, but that isn't a legal digest or nickname. " + "Skipping it.", escaped(name)); + smartlist_add_strdup(warned_family, name); + } + if (is_legal) { + smartlist_add_strdup(declared_family, name); + } + } else { + /* List the node by digest. */ + char *fp = tor_malloc(HEX_DIGEST_LEN+2); + fp[0] = '$'; + base16_encode(fp+1,HEX_DIGEST_LEN+1, + member->identity, DIGEST_LEN); + smartlist_add(declared_family, fp); + + if (! is_legal_hexdigest(name) && + !smartlist_contains_string(warned_family, name)) { + /* Warn if this node was not specified by hexdigest. */ + log_warn(LD_CONFIG, "There is a router named %s in my declared " + "family, but it wasn't listed by digest. Please consider " + "saying %s instead, if that's what you meant.", + escaped(name), fp); + smartlist_add_strdup(warned_family, name); + } + } + } + + /* Now declared_family should have the closest we can come to the + * identities that the user wanted. + * + * Unlike older versions of Tor, we _do_ include our own identity: this + * helps microdescriptor compression, and helps in-memory compression + * on clients. */ + nodefamily_t *nf = nodefamily_from_members(declared_family, + router_get_my_id_digest(), + NF_WARN_MALFORMED, + NULL); + SMARTLIST_FOREACH(declared_family, char *, s, tor_free(s)); + smartlist_free(declared_family); + if (!nf) { + return NULL; + } + + char *s = nodefamily_format(nf); + nodefamily_free(nf); + + smartlist_t *result = smartlist_new(); + smartlist_split_string(result, s, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + tor_free(s); + + if (smartlist_len(result) == 1) { + /* This is a one-element list containing only ourself; instead return + * nothing */ + const char *singleton = smartlist_get(result, 0); + bool is_me = false; + if (singleton[0] == '$') { + char d[DIGEST_LEN]; + int n = base16_decode(d, sizeof(d), singleton+1, strlen(singleton+1)); + if (n == DIGEST_LEN && + fast_memeq(d, router_get_my_id_digest(), DIGEST_LEN)) { + is_me = true; + } + } + if (!is_me) { + // LCOV_EXCL_START + log_warn(LD_BUG, "Found a singleton family list with an element " + "that wasn't us! Element was %s", escaped(singleton)); + // LCOV_EXCL_STOP + } else { + SMARTLIST_FOREACH(result, char *, cp, tor_free(cp)); + smartlist_free(result); + return NULL; + } + } + + return result; +} + +/** Allocate a fresh, unsigned routerinfo for this OR, without any of the + * fields that depend on the corresponding extrainfo. + * + * On success, set ri_out to the new routerinfo, and return 0. + * Caller is responsible for freeing the generated routerinfo. + * + * Returns a negative value and sets ri_out to NULL on temporary error. */ -int -router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) +MOCK_IMPL(STATIC int, +router_build_fresh_unsigned_routerinfo,(routerinfo_t **ri_out)) { - routerinfo_t *ri; - extrainfo_t *ei; + routerinfo_t *ri = NULL; uint32_t addr; char platform[256]; int hibernating = we_are_hibernating(); const or_options_t *options = get_options(); + int result = TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + + if (BUG(!ri_out)) { + result = TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + goto err; + } 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; + result = TOR_ROUTERINFO_ERROR_NO_EXT_ADDR; + goto err; } /* Log a message if the address in the descriptor doesn't match the ORPort @@ -1901,8 +2043,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key()); if (BUG(crypto_pk_get_digest(ri->identity_pkey, ri->cache_info.identity_digest) < 0)) { - routerinfo_free(ri); - return TOR_ROUTERINFO_ERROR_DIGEST_FAILED; + result = TOR_ROUTERINFO_ERROR_DIGEST_FAILED; + goto err; } ri->cache_info.signing_key_cert = tor_cert_dup(get_master_signing_key_cert()); @@ -1913,10 +2055,10 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) ri->protocol_list = tor_strdup(protover_get_supported_protocols()); /* compute ri->bandwidthrate as the min of various options */ - ri->bandwidthrate = get_effective_bwrate(options); + ri->bandwidthrate = relay_get_effective_bwrate(options); /* and compute ri->bandwidthburst similarly */ - ri->bandwidthburst = get_effective_bwburst(options); + ri->bandwidthburst = relay_get_effective_bwburst(options); /* Report bandwidth, unless we're hibernating or shutting down */ ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess(); @@ -1939,134 +2081,260 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) 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); - } - } + ri->declared_family = get_my_declared_family(options); - /* remove duplicates from the list */ - smartlist_sort_strings(ri->declared_family); - smartlist_uniq_strings(ri->declared_family); + if (options->BridgeRelay) { + ri->purpose = ROUTER_PURPOSE_BRIDGE; + /* 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; + } else { + ri->purpose = ROUTER_PURPOSE_GENERAL; + ri->cache_info.send_unencrypted = 1; } + goto done; + + err: + routerinfo_free(ri); + *ri_out = NULL; + return result; + + done: + *ri_out = ri; + return 0; +} + +/** Allocate and return a fresh, unsigned extrainfo for this OR, based on the + * routerinfo ri. + * + * Uses options->Nickname to set the nickname, and options->BridgeRelay to set + * ei->cache_info.send_unencrypted. + * + * If ri is NULL, logs a BUG() warning and returns NULL. + * Caller is responsible for freeing the generated extrainfo. + */ +static extrainfo_t * +router_build_fresh_unsigned_extrainfo(const routerinfo_t *ri) +{ + extrainfo_t *ei = NULL; + const or_options_t *options = get_options(); + + if (BUG(!ri)) + return NULL; + /* 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)); + strlcpy(ei->nickname, 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 (options->BridgeRelay) { + /* See note in router_build_fresh_routerinfo(). */ + ei->cache_info.send_unencrypted = 0; + } else { + ei->cache_info.send_unencrypted = 1; + } + + return ei; +} + +/** Dump the extrainfo descriptor body for ei, sign it, and add the body and + * signature to ei->cache_info. Note that the extrainfo body is determined by + * ei, and some additional config and statistics state: see + * extrainfo_dump_to_string() for details. + * + * Return 0 on success, -1 on temporary error. + * If ei is NULL, logs a BUG() warning and returns -1. + * On error, ei->cache_info is not modified. + */ +static int +router_dump_and_sign_extrainfo_descriptor_body(extrainfo_t *ei) +{ + if (BUG(!ei)) + return -1; + 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); + return -1; } - /* 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. */ + 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); + + return 0; +} + +/** Allocate and return a fresh, signed extrainfo for this OR, based on the + * routerinfo ri. + * + * If ri is NULL, logs a BUG() warning and returns NULL. + * Caller is responsible for freeing the generated extrainfo. + */ +STATIC extrainfo_t * +router_build_fresh_signed_extrainfo(const routerinfo_t *ri) +{ + int result = -1; + extrainfo_t *ei = NULL; + + if (BUG(!ri)) + return NULL; + + ei = router_build_fresh_unsigned_extrainfo(ri); + /* router_build_fresh_unsigned_extrainfo() should not fail. */ + if (BUG(!ei)) + goto err; + + result = router_dump_and_sign_extrainfo_descriptor_body(ei); + if (result < 0) + goto err; + + goto done; + + err: + extrainfo_free(ei); + return NULL; + + done: + return ei; +} + +/** Set the fields in ri that depend on ei. + * + * If ei is NULL, logs a BUG() warning and zeroes the relevant fields. + */ +STATIC void +router_update_routerinfo_from_extrainfo(routerinfo_t *ri, + const extrainfo_t *ei) +{ + if (BUG(!ei)) { + /* Just to be safe, zero ri->cache_info.extra_info_digest here. */ + memset(ri->cache_info.extra_info_digest, 0, DIGEST_LEN); + memset(ri->cache_info.extra_info_digest256, 0, DIGEST256_LEN); + return; } + + /* Now finish the router descriptor. */ + 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); +} + +/** Dump the descriptor body for ri, sign it, and add the body and signature to + * ri->cache_info. Note that the descriptor body is determined by ri, and some + * additional config and state: see router_dump_router_to_string() for details. + * + * Return 0 on success, and a negative value on temporary error. + * If ri is NULL, logs a BUG() warning and returns a negative value. + * On error, ri->cache_info is not modified. + */ +STATIC int +router_dump_and_sign_routerinfo_descriptor_body(routerinfo_t *ri) +{ + if (BUG(!ri)) + return TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + 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); + return 0; +} + +/** Build a fresh routerinfo, signed server descriptor, and signed extrainfo + * document for this OR. + * + * Set r to the generated routerinfo, e to the generated extrainfo document. + * Failure to generate an extra-info document is not an error and is indicated + * by setting e to NULL. + * Return 0 on success, and a negative value on temporary error. + * Caller is responsible for freeing generated documents on success. + */ +int +router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) +{ + int result = TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + routerinfo_t *ri = NULL; + extrainfo_t *ei = NULL; + + if (BUG(!r)) + goto err; + + if (BUG(!e)) + goto err; + + result = router_build_fresh_unsigned_routerinfo(&ri); + if (result < 0) { + goto err; + } + /* If ri is NULL, then result should be negative. So this check should be + * unreachable. */ + if (BUG(!ri)) { + result = TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + goto err; + } + + ei = router_build_fresh_signed_extrainfo(ri); + + /* Failing to create an ei is not an error. */ if (ei) { - tor_assert(! - routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei, - &ri->cache_info, NULL)); + router_update_routerinfo_from_extrainfo(ri, ei); } + result = router_dump_and_sign_routerinfo_descriptor_body(ri); + if (result < 0) + goto err; + + if (ei) { + if (BUG(routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei, + &ri->cache_info, NULL))) { + result = TOR_ROUTERINFO_ERROR_INTERNAL_BUG; + goto err; + } + } + + goto done; + + err: + routerinfo_free(ri); + extrainfo_free(ei); + *r = NULL; + *e = NULL; + return result; + + done: *r = ri; *e = ei; return 0; @@ -2152,7 +2420,9 @@ mark_my_descriptor_dirty_if_too_old(time_t now) /* 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. */ + * descriptor listed for us in the consensus is very old, or if the + * consensus lists us as "stale" and we haven't regenerated since the + * consensus was published. */ ns = networkstatus_get_live_consensus(now); if (ns) { rs = networkstatus_vote_find_entry(ns, server_identitykey_digest); @@ -2160,6 +2430,8 @@ mark_my_descriptor_dirty_if_too_old(time_t now) retry_fast_reason = "not listed in consensus"; else if (rs->published_on < slow_cutoff) retry_fast_reason = "version listed in consensus is quite old"; + else if (rs->is_staledesc && ns->valid_after > desc_clean_since) + retry_fast_reason = "listed as stale in consensus"; } if (retry_fast_reason && desc_clean_since < fast_cutoff) @@ -2405,6 +2677,10 @@ get_platform_str(char *platform, size_t len) /** 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. + * + * In addition to the fields in router, this function calls + * onion_key_lifetime(), get_options(), and we_are_hibernating(), and uses the + * results to populate some fields in the descriptor. */ char * router_dump_router_to_string(routerinfo_t *router, @@ -2468,11 +2744,8 @@ router_dump_router_to_string(routerinfo_t *router, 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; - } + ed25519_public_to_base64(ed_fp_base64, + &router->cache_info.signing_key_cert->signing_key); tor_asprintf(&ed_cert_line, "identity-ed25519\n" "-----BEGIN ED25519 CERT-----\n" "%s" @@ -2722,8 +2995,7 @@ router_dump_router_to_string(routerinfo_t *router, 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; + ed25519_signature_to_base64(buf, &sig); smartlist_add_asprintf(chunks, "%s\n", buf); } @@ -2862,34 +3134,26 @@ load_stats_file(const char *filename, const char *end_line, time_t now, 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) +/** Add header strings to chunks, based on the extrainfo object extrainfo, + * and ed25519 keypair signing_keypair, if emit_ed_sigs is true. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_header_helper( + smartlist_t *chunks, + const extrainfo_t *extrainfo, + const ed25519_keypair_t *signing_keypair, + int emit_ed_sigs) { - 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; + char *pre = NULL; + int rv = -1; 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, @@ -2915,21 +3179,64 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, ed_cert_line = tor_strdup(""); } - tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n%s", + /* This is the first chunk in the file. If the file is too big, other chunks + * are removed. So we must only add one chunk here. */ + tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n", extrainfo->nickname, identity, ed_cert_line, - published, bandwidth_usage); + published); 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)); + rv = 0; + goto done; + + err: + rv = -1; + + done: + tor_free(ed_cert_line); + return rv; +} + +/** Add pluggable transport and statistics strings to chunks, skipping + * statistics if write_stats_to_extrainfo is false. + * Helper for extrainfo_dump_to_string(). + * Can not fail. */ +static void +extrainfo_dump_to_string_stats_helper(smartlist_t *chunks, + int write_stats_to_extrainfo) +{ + const or_options_t *options = get_options(); + char *contents = NULL; + time_t now = time(NULL); + + /* If the file is too big, these chunks are removed, starting with the last + * chunk. So each chunk must be a complete line, and the file must be valid + * after each chunk. */ + + /* Add information about the pluggable transports we support, even if we + * are not publishing statistics. This information is needed by BridgeDB + * to distribute bridges. */ + if (options->ServerTransportPlugin) { + char *pluggable_transports = pt_get_extra_info_descriptor_string(); + if (pluggable_transports) + smartlist_add(chunks, pluggable_transports); + } if (options->ExtraInfoStatistics && write_stats_to_extrainfo) { log_info(LD_GENERAL, "Adding stats to extra-info descriptor."); + /* Bandwidth usage stats don't have their own option */ + { + contents = rep_hist_get_bandwidth_lines(); + smartlist_add(chunks, contents); + } + /* geoip hashes aren't useful unless we are publishing other stats */ + 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->DirReqStatistics && load_stats_file("stats"PATH_SEPARATOR"dirreq-stats", "dirreq-stats-end", now, &contents) > 0) { @@ -2965,50 +3272,140 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, if (contents) smartlist_add(chunks, contents); } + /* bridge statistics */ + if (should_record_bridge_info(options)) { + const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); + if (bridge_stats) { + smartlist_add_strdup(chunks, bridge_stats); + } + } } +} - /* 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); - } +/** Add an ed25519 signature of chunks to chunks, using the ed25519 keypair + * signing_keypair. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_ed_sig_helper( + smartlist_t *chunks, + const ed25519_keypair_t *signing_keypair) +{ + char sha256_digest[DIGEST256_LEN]; + ed25519_signature_t ed_sig; + char buf[ED25519_SIG_BASE64_LEN+1]; + int rv = -1; + + /* These are two of the three final chunks in the file. If the file is too + * big, other chunks are removed. So we must only add two chunks here. */ + smartlist_add_strdup(chunks, "router-sig-ed25519 "); + crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, + ED_DESC_SIGNATURE_PREFIX, + chunks, "", DIGEST_SHA256); + if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN, + signing_keypair) < 0) + goto err; + ed25519_signature_to_base64(buf, &ed_sig); - 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); - } + smartlist_add_asprintf(chunks, "%s\n", buf); + + rv = 0; + goto done; + + err: + rv = -1; + + done: + return rv; +} + +/** Add an RSA signature of extrainfo_string to chunks, using the RSA key + * ident_key. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_rsa_sig_helper(smartlist_t *chunks, + crypto_pk_t *ident_key, + const char *extrainfo_string) +{ + char sig[DIROBJ_MAX_SIG_LEN+1]; + char digest[DIGEST_LEN]; + int rv = -1; + + memset(sig, 0, sizeof(sig)); + if (router_get_extrainfo_hash(extrainfo_string, strlen(extrainfo_string), + 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); + + rv = 0; + goto done; + + err: + rv = -1; + + done: + return rv; +} + +/** Write the contents of <b>extrainfo</b>, to * *<b>s_out</b>, signing them + * with <b>ident_key</b>. + * + * If ExtraInfoStatistics is 1, also write aggregated statistics and related + * configuration data before signing. Most statistics also have an option that + * enables or disables that particular statistic. + * + * Always write pluggable transport lines. + * + * 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) +{ + int result; + static int write_stats_to_extrainfo = 1; + char *s = NULL, *cp, *s_dup = NULL; + smartlist_t *chunks = smartlist_new(); + extrainfo_t *ei_tmp = NULL; + const int emit_ed_sigs = signing_keypair && + extrainfo->cache_info.signing_key_cert; + int rv = 0; + + rv = extrainfo_dump_to_string_header_helper(chunks, extrainfo, + signing_keypair, + emit_ed_sigs); + if (rv < 0) + goto err; + + extrainfo_dump_to_string_stats_helper(chunks, write_stats_to_extrainfo); 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) + rv = extrainfo_dump_to_string_ed_sig_helper(chunks, signing_keypair); + if (rv < 0) goto err; - if (ed25519_signature_to_base64(buf, &ed_sig) < 0) - goto err; - - smartlist_add_asprintf(chunks, "%s\n", buf); } + /* This is one of the three final chunks in the file. If the file is too big, + * other chunks are removed. So we must only add one chunk here. */ 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; + * things. If emit_ed_sigs is true, we also keep 2 additional chunks at the + * end for the ed25519 signature. */ + const int required_chunks = emit_ed_sigs ? 4 : 2; + if (smartlist_len(chunks) > required_chunks) { + /* We remove the next-to-last or 4th-last element (remember, len-1 is the + * last element), since we need to keep the router-signature elements. */ + int idx = smartlist_len(chunks) - required_chunks; char *e = smartlist_get(chunks, idx); smartlist_del_keeporder(chunks, idx); log_warn(LD_GENERAL, "We just generated an extra-info descriptor " @@ -3025,15 +3422,10 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, } } - 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."); + rv = extrainfo_dump_to_string_rsa_sig_helper(chunks, ident_key, s); + if (rv < 0) goto err; - } - smartlist_add_strdup(chunks, sig); + tor_free(s); s = smartlist_join_strings(chunks, "", 0, NULL); @@ -3069,9 +3461,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, 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; } @@ -3081,9 +3471,9 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, void router_reset_warnings(void) { - if (warned_nonexistent_family) { - SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); - smartlist_clear(warned_nonexistent_family); + if (warned_family) { + SMARTLIST_FOREACH(warned_family, char *, cp, tor_free(cp)); + smartlist_clear(warned_family); } } @@ -3096,6 +3486,10 @@ router_free_all(void) crypto_pk_free(server_identitykey); crypto_pk_free(client_identitykey); + /* Destroying a locked mutex is undefined behaviour. This mutex may be + * locked, because multiple threads can access it. But we need to destroy + * it, otherwise re-initialisation will trigger undefined behaviour. + * See #31735 for details. */ tor_mutex_free(key_lock); routerinfo_free(desc_routerinfo); extrainfo_free(desc_extrainfo); @@ -3107,11 +3501,12 @@ router_free_all(void) 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); + if (warned_family) { + SMARTLIST_FOREACH(warned_family, char *, cp, tor_free(cp)); + smartlist_free(warned_family); } } + /* From the given RSA key object, convert it to ASN-1 encoded format and set * the newly allocated object in onion_pkey_out. The length of the key is set * in onion_pkey_len_out. */ diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index ab1f771017..061daa0628 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -23,11 +23,18 @@ struct ed25519_keypair_t; #define TOR_ROUTERINFO_ERROR_DIGEST_FAILED (-4) #define TOR_ROUTERINFO_ERROR_CANNOT_GENERATE (-5) #define TOR_ROUTERINFO_ERROR_DESC_REBUILDING (-6) +#define TOR_ROUTERINFO_ERROR_INTERNAL_BUG (-7) -crypto_pk_t *get_onion_key(void); +MOCK_DECL(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); +/* Some compilers are clever enough to know that when relay mode is disabled, + * this function never returns. */ +#ifdef HAVE_MODULE_RELAY +MOCK_DECL(crypto_pk_t *,get_server_identity_key,(void)); +#else +#define get_server_identity_key() (tor_abort_(),NULL) +#endif int server_identity_key_is_set(void); void set_client_identity_key(crypto_pk_t *k); crypto_pk_t *get_tlsclient_identity_key(void); @@ -116,9 +123,27 @@ void router_reset_reachability(void); void router_free_all(void); #ifdef ROUTER_PRIVATE -/* Used only by router.c and test.c */ +/* Used only by router.c and the unit tests */ STATIC void get_platform_str(char *platform, size_t len); STATIC int router_write_fingerprint(int hashed); -#endif +STATIC smartlist_t *get_my_declared_family(const or_options_t *options); + +#ifdef TOR_UNIT_TESTS +extern time_t desc_clean_since; +extern const char *desc_dirty_reason; +void set_server_identity_key_digest_testing(const uint8_t *digest); +MOCK_DECL(STATIC const struct curve25519_keypair_t *, + get_current_curve25519_keypair,(void)); + +MOCK_DECL(STATIC int, + router_build_fresh_unsigned_routerinfo,(routerinfo_t **ri_out)); +STATIC extrainfo_t *router_build_fresh_signed_extrainfo( + const routerinfo_t *ri); +STATIC void router_update_routerinfo_from_extrainfo(routerinfo_t *ri, + const extrainfo_t *ei); +STATIC int router_dump_and_sign_routerinfo_descriptor_body(routerinfo_t *ri); +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(ROUTER_PRIVATE) */ #endif /* !defined(TOR_ROUTER_H) */ diff --git a/src/feature/relay/routerkeys.c b/src/feature/relay/routerkeys.c index f639fc91e7..d3de83cb86 100644 --- a/src/feature/relay/routerkeys.c +++ b/src/feature/relay/routerkeys.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -226,7 +226,7 @@ load_ed_keys(const or_options_t *options, time_t now) tor_free(fname); } } - if (tor_mem_is_zero((char*)id->seckey.seckey, sizeof(id->seckey))) + if (safe_mem_is_zero((char*)id->seckey.seckey, sizeof(id->seckey))) sign_signing_key_with_id = NULL; else sign_signing_key_with_id = id; @@ -631,14 +631,14 @@ get_master_identity_keypair(void) } #endif /* defined(TOR_UNIT_TESTS) */ -const ed25519_keypair_t * -get_master_signing_keypair(void) +MOCK_IMPL(const ed25519_keypair_t *, +get_master_signing_keypair,(void)) { return master_signing_key; } -const struct tor_cert_st * -get_master_signing_key_cert(void) +MOCK_IMPL(const struct tor_cert_st *, +get_master_signing_key_cert,(void)) { return signing_key_cert; } @@ -706,6 +706,8 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, *len_out = 0; if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) { + log_info(LD_OR, "crypto_pk_get_digest failed in " + "make_tap_onion_key_crosscert!"); return NULL; } memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN); @@ -713,8 +715,12 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, int r = crypto_pk_private_sign(onion_key, (char*)signature, sizeof(signature), (const char*)signed_data, sizeof(signed_data)); - if (r < 0) + if (r < 0) { + /* It's probably missing the private key */ + log_info(LD_OR, "crypto_pk_private_sign failed in " + "make_tap_onion_key_crosscert!"); return NULL; + } *len_out = r; diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h index 0badd34191..c2475f195f 100644 --- a/src/feature/relay/routerkeys.h +++ b/src/feature/relay/routerkeys.h @@ -1,14 +1,19 @@ -/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* Copyright (c) 2014-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routerkeys.h + * @brief Header for routerkeys.c + **/ + #ifndef TOR_ROUTERKEYS_H #define TOR_ROUTERKEYS_H #include "lib/crypt_ops/crypto_ed25519.h" 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); +MOCK_DECL(const ed25519_keypair_t *, get_master_signing_keypair,(void)); +MOCK_DECL(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); diff --git a/src/feature/relay/routermode.c b/src/feature/relay/routermode.c index 2a9ddeac4d..c4d8792b5b 100644 --- a/src/feature/relay/routermode.c +++ b/src/feature/relay/routermode.c @@ -1,14 +1,17 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file routermode.c + * @brief Check if we're running as a relay/cache. + **/ + #include "core/or/or.h" #include "app/config/config.h" -#include "core/mainloop/connection.h" -#include "core/or/port_cfg_st.h" #include "feature/relay/router.h" #include "feature/relay/routermode.h" @@ -25,21 +28,6 @@ dir_server_mode(const or_options_t *options) (server_mode(options) && router_has_bandwidth_to_be_dirserver(options)); } -/** 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; -} - /** Return true iff we are trying to be a server. */ MOCK_IMPL(int, diff --git a/src/feature/relay/routermode.h b/src/feature/relay/routermode.h index be535af478..6d7404968d 100644 --- a/src/feature/relay/routermode.h +++ b/src/feature/relay/routermode.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,13 +12,31 @@ #ifndef TOR_ROUTERMODE_H #define TOR_ROUTERMODE_H +#ifdef HAVE_MODULE_RELAY + int dir_server_mode(const or_options_t *options); 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 set_server_advertised(int s); +/** Is the relay module enabled? */ +#define have_module_relay() (1) + +#else /* !defined(HAVE_MODULE_RELAY) */ + +#define dir_server_mode(options) (((void)(options)),0) +#define server_mode(options) (((void)(options)),0) +#define public_server_mode(options) (((void)(options)),0) +#define advertised_server_mode() (0) + +/* We shouldn't be publishing descriptors when relay mode is disabled. */ +#define set_server_advertised(s) tor_assert_nonfatal(!(s)) + +#define have_module_relay() (0) + +#endif /* defined(HAVE_MODULE_RELAY) */ + #endif /* !defined(TOR_ROUTERMODE_H) */ diff --git a/src/feature/relay/selftest.c b/src/feature/relay/selftest.c index 064eea6c46..29febdee82 100644 --- a/src/feature/relay/selftest.c +++ b/src/feature/relay/selftest.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -12,8 +12,6 @@ * their own bandwidth, before publishing. */ -#define SELFTEST_PRIVATE - #include "core/or/or.h" #include "app/config/config.h" @@ -26,7 +24,7 @@ #include "core/or/crypt_path_st.h" #include "core/or/origin_circuit_st.h" #include "core/or/relay.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirclient/dirclient.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/authority_cert_st.h" @@ -35,6 +33,7 @@ #include "feature/nodelist/routerlist.h" // but... #include "feature/nodelist/routerset.h" #include "feature/nodelist/torcert.h" +#include "feature/relay/relay_periodic.h" #include "feature/relay/router.h" #include "feature/relay/selftest.h" diff --git a/src/feature/relay/selftest.h b/src/feature/relay/selftest.h index a80ec8936e..94f305f203 100644 --- a/src/feature/relay/selftest.h +++ b/src/feature/relay/selftest.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -21,4 +21,4 @@ void router_orport_found_reachable(void); void router_dirport_found_reachable(void); void router_perform_bandwidth_test(int num_circs, time_t now); -#endif +#endif /* !defined(TOR_SELFTEST_H) */ diff --git a/src/feature/relay/transport_config.c b/src/feature/relay/transport_config.c new file mode 100644 index 0000000000..7dcce70e30 --- /dev/null +++ b/src/feature/relay/transport_config.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file transport_config.c + * @brief Code to interpret the user's configuration of Tor's server + * pluggable transports. + **/ + +#include "orconfig.h" +#define RELAY_TRANSPORT_CONFIG_PRIVATE +#include "feature/relay/transport_config.h" + +#include "lib/encoding/confline.h" +#include "lib/encoding/keyval.h" + +#include "lib/container/smartlist.h" + +/* Required for dirinfo_type_t in or_options_t */ +#include "core/or/or.h" +#include "app/config/config.h" + +#include "feature/relay/ext_orport.h" +#include "feature/relay/routermode.h" + +/* Copied from config.c, we will refactor later in 29211. */ +#define REJECT(arg) \ + STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END + +/** Given a ServerTransportListenAddr <b>line</b>, return its + * <address:port> string. Return NULL if the line was not + * well-formed. + * + * If <b>transport</b> is set, return NULL if the line is not + * referring to <b>transport</b>. + * + * The returned string is allocated on the heap and it's the + * responsibility of the caller to free it. */ +static char * +get_bindaddr_from_transport_listen_line(const char *line, + const char *transport) +{ + smartlist_t *items = NULL; + const char *parsed_transport = NULL; + char *addrport = NULL; + tor_addr_t addr; + uint16_t port = 0; + + items = smartlist_new(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + if (smartlist_len(items) < 2) { + log_warn(LD_CONFIG,"Too few arguments on ServerTransportListenAddr line."); + goto err; + } + + parsed_transport = smartlist_get(items, 0); + addrport = tor_strdup(smartlist_get(items, 1)); + + /* If 'transport' is given, check if it matches the one on the line */ + if (transport && strcmp(transport, parsed_transport)) + goto err; + + /* Validate addrport */ + if (tor_addr_port_parse(LOG_WARN, addrport, &addr, &port, -1)<0) { + log_warn(LD_CONFIG, "Error parsing ServerTransportListenAddr " + "address '%s'", addrport); + goto err; + } + + goto done; + + err: + tor_free(addrport); + addrport = NULL; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return addrport; +} + +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has explicitly asked for + * it to listen on a specific port. Return a <address:port> string if + * so, otherwise NULL. */ +char * +pt_get_bindaddr_from_config(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { + char *bindaddr = + get_bindaddr_from_transport_listen_line(cl->value, transport); + if (bindaddr) + return bindaddr; + } + + return NULL; +} + +/** Given a ServerTransportOptions <b>line</b>, return a smartlist + * with the options. Return NULL if the line was not well-formed. + * + * If <b>transport</b> is set, return NULL if the line is not + * referring to <b>transport</b>. + * + * The returned smartlist and its strings are allocated on the heap + * and it's the responsibility of the caller to free it. */ +STATIC smartlist_t * +get_options_from_transport_options_line(const char *line, + const char *transport) +{ + smartlist_t *items = smartlist_new(); + smartlist_t *pt_options = smartlist_new(); + const char *parsed_transport = NULL; + + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + if (smartlist_len(items) < 2) { + log_warn(LD_CONFIG,"Too few arguments on ServerTransportOptions line."); + goto err; + } + + parsed_transport = smartlist_get(items, 0); + /* If 'transport' is given, check if it matches the one on the line */ + if (transport && strcmp(transport, parsed_transport)) + goto err; + + SMARTLIST_FOREACH_BEGIN(items, const char *, option) { + if (option_sl_idx == 0) /* skip the transport field (first field)*/ + continue; + + /* validate that it's a k=v value */ + if (!string_is_key_value(LOG_WARN, option)) { + log_warn(LD_CONFIG, "%s is not a k=v value.", escaped(option)); + goto err; + } + + /* add it to the options smartlist */ + smartlist_add_strdup(pt_options, option); + log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option)); + } SMARTLIST_FOREACH_END(option); + + goto done; + + err: + SMARTLIST_FOREACH(pt_options, char*, s, tor_free(s)); + smartlist_free(pt_options); + pt_options = NULL; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return pt_options; +} + +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has asked us to pass any + * parameters to the pluggable transport. Return a smartlist + * containing the parameters, otherwise NULL. */ +smartlist_t * +pt_get_options_for_server_transport(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, transport); + if (options_sl) + return options_sl; + } + + return NULL; +} + +/** + * Legacy validation/normalization function for the server transport options. + * Uses old_options as the previous options. + * + * Returns 0 on success, returns -1 and sets *msg to a newly allocated string + * on error. + */ +int +options_validate_server_transport(const or_options_t *old_options, + or_options_t *options, + char **msg) +{ + (void)old_options; + + if (BUG(!options)) + return -1; + + if (BUG(!msg)) + return -1; + + config_line_t *cl; + + if (options->ServerTransportPlugin && !server_mode(options)) { + log_notice(LD_GENERAL, "Tor is not configured as a relay but you specified" + " a ServerTransportPlugin line (%s). The ServerTransportPlugin " + "line will be ignored.", + escaped(options->ServerTransportPlugin->value)); + } + + if (options->ServerTransportListenAddr && !options->ServerTransportPlugin) { + log_notice(LD_GENERAL, "You need at least a single managed-proxy to " + "specify a transport listen address. The " + "ServerTransportListenAddr line will be ignored."); + } + + for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { + if (pt_parse_transport_line(options, cl->value, 1, 1) < 0) + REJECT("Invalid server transport line. See logs for details."); + } + + for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { + /** If get_bindaddr_from_transport_listen_line() fails with + 'transport' being NULL, it means that something went wrong + while parsing the ServerTransportListenAddr line. */ + char *bindaddr = get_bindaddr_from_transport_listen_line(cl->value, NULL); + if (!bindaddr) + REJECT("ServerTransportListenAddr did not parse. See logs for details."); + tor_free(bindaddr); + } + + for (cl = options->ServerTransportOptions; cl; cl = cl->next) { + /** If get_options_from_transport_options_line() fails with + 'transport' being NULL, it means that something went wrong + while parsing the ServerTransportOptions line. */ + smartlist_t *options_sl = + get_options_from_transport_options_line(cl->value, NULL); + if (!options_sl) + REJECT("ServerTransportOptions did not parse. See logs for details."); + + SMARTLIST_FOREACH(options_sl, char *, cp, tor_free(cp)); + smartlist_free(options_sl); + } + + return 0; +} + +/** Fetch the active option list, and take server pluggable transport actions + * based on it. All of the things we do should survive being done repeatedly. + * If present, <b>old_options</b> contains the previous value of the options. + * + * Return 0 if all goes well, return -1 if it's time to die. + * + * Note: We haven't moved all the "act on new configuration" logic + * into the options_act* functions yet. Some is still in do_hup() and other + * places. + */ +int +options_act_server_transport(const or_options_t *old_options) +{ + (void)old_options; + + config_line_t *cl; + const or_options_t *options = get_options(); + int running_tor = options->command == CMD_RUN_TOR; + + /* If we are a bridge with a pluggable transport proxy but no + Extended ORPort, inform the user that they are missing out. */ + if (options->ServerTransportPlugin && + !options->ExtORPort_lines) { + log_notice(LD_CONFIG, "We use pluggable transports but the Extended " + "ORPort is disabled. Tor and your pluggable transports proxy " + "communicate with each other via the Extended ORPort so it " + "is suggested you enable it: it will also allow your Bridge " + "to collect statistics about its clients that use pluggable " + "transports. Please enable it using the ExtORPort torrc option " + "(e.g. set 'ExtORPort auto')."); + } + + /* If we have an ExtORPort, initialize its auth cookie. */ + if (running_tor && + init_ext_or_cookie_authentication(!!options->ExtORPort_lines) < 0) { + log_warn(LD_CONFIG,"Error creating Extended ORPort cookie file."); + return -1; + } + + if (!options->DisableNetwork) { + if (options->ServerTransportPlugin) { + for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { + if (pt_parse_transport_line(options, cl->value, 0, 1) < 0) { + // LCOV_EXCL_START + log_warn(LD_BUG, + "Previously validated ServerTransportPlugin line " + "could not be added!"); + return -1; + // LCOV_EXCL_STOP + } + } + } + } + + return 0; +} diff --git a/src/feature/relay/transport_config.h b/src/feature/relay/transport_config.h new file mode 100644 index 0000000000..6d956d9af1 --- /dev/null +++ b/src/feature/relay/transport_config.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file transport_config.h + * @brief Header for feature/relay/transport_config.c + **/ + +#ifndef TOR_FEATURE_RELAY_TRANSPORT_CONFIG_H +#define TOR_FEATURE_RELAY_TRANSPORT_CONFIG_H + +#ifdef HAVE_MODULE_RELAY + +#include "lib/testsupport/testsupport.h" + +struct or_options_t; +struct smartlist_t; + +int options_validate_server_transport(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg); + +char *pt_get_bindaddr_from_config(const char *transport); +struct smartlist_t *pt_get_options_for_server_transport(const char *transport); + +int options_act_server_transport(const struct or_options_t *old_options); + +#ifdef RELAY_TRANSPORT_CONFIG_PRIVATE + +STATIC struct smartlist_t *get_options_from_transport_options_line( + const char *line, + const char *transport); + +#endif /* defined(RELAY_TRANSPORT_CONFIG_PRIVATE) */ + +#else /* !defined(HAVE_MODULE_RELAY) */ + +/** When tor is compiled with the relay module disabled, it can't be + * configured with server pluggable transports. + * + * Returns -1 and sets msg to a newly allocated string, if ExtORPort, + * ServerTransportPlugin, ServerTransportListenAddr, or + * ServerTransportOptions are set in options. Otherwise returns 0. */ +static inline int +options_validate_server_transport(const struct or_options_t *old_options, + struct or_options_t *options, + char **msg) +{ + (void)old_options; + + /* These ExtORPort checks are too strict, and will reject valid configs + * that disable ports, like "ExtORPort 0". */ + if (options->ServerTransportPlugin || + options->ServerTransportListenAddr || + options->ServerTransportOptions || + options->ExtORPort_lines) { + /* REJECT() this configuration */ + *msg = tor_strdup("This tor was built with relay mode disabled. " + "It can not be configured with an ExtORPort, " + "a ServerTransportPlugin, a ServerTransportListenAddr, " + "or ServerTransportOptions."); + return -1; + } + + return 0; +} + +#define pt_get_bindaddr_from_config(transport) \ + (((void)(transport)),NULL) + +/* 31851: called from client/transports.c, but only from server code */ +#define pt_get_options_for_server_transport(transport) \ + (((void)(transport)),NULL) + +#define options_validate_server_transport(old_options, options, msg) \ + (((void)(old_options)),((void)(options)),((void)(msg)),0) +#define options_act_server_transport(old_options) \ + (((void)(old_options)),0) + +#endif /* defined(HAVE_MODULE_RELAY) */ + +#endif /* !defined(TOR_FEATURE_RELAY_TRANSPORT_CONFIG_H) */ diff --git a/src/feature/rend/.may_include b/src/feature/rend/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/rend/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/rend/feature_rend.md b/src/feature/rend/feature_rend.md new file mode 100644 index 0000000000..bfd8ae3dbc --- /dev/null +++ b/src/feature/rend/feature_rend.md @@ -0,0 +1,7 @@ +@dir /feature/rend +@brief feature/rend: version 2 (old) hidden services + +This directory implements the v2 onion service protocol, +as specified in +[rend-spec-v2.txt](https://gitweb.torproject.org/torspec.git/tree/rend-spec-v2.txt). + diff --git a/src/feature/rend/include.am b/src/feature/rend/include.am new file mode 100644 index 0000000000..fb12439a90 --- /dev/null +++ b/src/feature/rend/include.am @@ -0,0 +1,22 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/rend/rendcache.c \ + src/feature/rend/rendclient.c \ + src/feature/rend/rendcommon.c \ + src/feature/rend/rendmid.c \ + src/feature/rend/rendparse.c \ + src/feature/rend/rendservice.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/rend/rend_authorized_client_st.h \ + src/feature/rend/rend_encoded_v2_service_descriptor_st.h \ + src/feature/rend/rend_intro_point_st.h \ + src/feature/rend/rend_service_descriptor_st.h \ + src/feature/rend/rendcache.h \ + src/feature/rend/rendclient.h \ + src/feature/rend/rendcommon.h \ + src/feature/rend/rendmid.h \ + src/feature/rend/rendparse.h \ + src/feature/rend/rendservice.h diff --git a/src/feature/rend/rend_authorized_client_st.h b/src/feature/rend/rend_authorized_client_st.h index 7bd4f2fe8c..c6a6676da9 100644 --- a/src/feature/rend/rend_authorized_client_st.h +++ b/src/feature/rend/rend_authorized_client_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file rend_authorized_client_st.h + * @brief Hidden-service authorized client structure. + **/ + #ifndef REND_AUTHORIZED_CLIENT_ST_H #define REND_AUTHORIZED_CLIENT_ST_H @@ -14,5 +19,4 @@ struct rend_authorized_client_t { crypto_pk_t *client_key; }; -#endif - +#endif /* !defined(REND_AUTHORIZED_CLIENT_ST_H) */ diff --git a/src/feature/rend/rend_encoded_v2_service_descriptor_st.h b/src/feature/rend/rend_encoded_v2_service_descriptor_st.h index 05ff145d53..fea91b876a 100644 --- a/src/feature/rend/rend_encoded_v2_service_descriptor_st.h +++ b/src/feature/rend/rend_encoded_v2_service_descriptor_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file rend_encoded_v2_service_descriptor_st.h + * @brief Encoded v2 HS descriptor structure. + **/ + #ifndef REND_ENCODED_V2_SERVICE_DESCRIPTOR_ST_H #define REND_ENCODED_V2_SERVICE_DESCRIPTOR_ST_H @@ -13,5 +18,4 @@ struct rend_encoded_v2_service_descriptor_t { char *desc_str; /**< Descriptor string. */ }; -#endif - +#endif /* !defined(REND_ENCODED_V2_SERVICE_DESCRIPTOR_ST_H) */ diff --git a/src/feature/rend/rend_intro_point_st.h b/src/feature/rend/rend_intro_point_st.h index de6987e569..4f0aa01523 100644 --- a/src/feature/rend/rend_intro_point_st.h +++ b/src/feature/rend/rend_intro_point_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file rend_intro_point_st.h + * @brief v2 hidden service introduction point structure. + **/ + #ifndef REND_INTRO_POINT_ST_H #define REND_INTRO_POINT_ST_H @@ -73,4 +78,4 @@ struct rend_intro_point_t { unsigned int circuit_established:1; }; -#endif +#endif /* !defined(REND_INTRO_POINT_ST_H) */ diff --git a/src/feature/rend/rend_service_descriptor_st.h b/src/feature/rend/rend_service_descriptor_st.h index aeb3178064..80c8034f46 100644 --- a/src/feature/rend/rend_service_descriptor_st.h +++ b/src/feature/rend/rend_service_descriptor_st.h @@ -1,9 +1,14 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +/** + * @file rend_service_descriptor_st.h + * @brief Parsed v2 HS descriptor structure. + **/ + #ifndef REND_SERVICE_DESCRIPTOR_ST_H #define REND_SERVICE_DESCRIPTOR_ST_H @@ -30,5 +35,4 @@ struct rend_service_descriptor_t { smartlist_t *successful_uploads; }; -#endif - +#endif /* !defined(REND_SERVICE_DESCRIPTOR_ST_H) */ diff --git a/src/feature/rend/rendcache.c b/src/feature/rend/rendcache.c index 1c3badaff3..0890a81d8f 100644 --- a/src/feature/rend/rendcache.c +++ b/src/feature/rend/rendcache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2019, The Tor Project, Inc. */ +/* Copyright (c) 2015-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -19,6 +19,8 @@ #include "feature/rend/rend_intro_point_st.h" #include "feature/rend/rend_service_descriptor_st.h" +#include "lib/ctime/di_ops.h" + /** Map from service id (as generated by rend_get_service_id) to * rend_cache_entry_t. */ STATIC strmap_t *rend_cache = NULL; @@ -45,7 +47,7 @@ STATIC digestmap_t *rend_cache_v2_dir = NULL; * looked up in this cache and if present, it is discarded from the fetched * descriptor. At the end, all IP(s) in the cache, for a specific service * ID, that were NOT present in the descriptor are removed from this cache. - * Which means that if at least one IP was not in this cache, thus usuable, + * Which means that if at least one IP was not in this cache, thus usable, * it's considered a new descriptor so we keep it. Else, if all IPs were in * this cache, we discard the descriptor as it's considered unusable. * @@ -226,6 +228,17 @@ rend_cache_entry_free_void(void *p) rend_cache_entry_free_(p); } +/** Check if a failure cache entry exists for the given intro point. */ +bool +rend_cache_intro_failure_exists(const char *service_id, + const uint8_t *intro_identity) +{ + tor_assert(service_id); + tor_assert(intro_identity); + + return cache_failure_intro_lookup(intro_identity, service_id, NULL); +} + /** Free all storage held by the service descriptor cache. */ void rend_cache_free_all(void) @@ -513,9 +526,16 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) rend_cache_entry_t *entry = NULL; static const int default_version = 2; - tor_assert(rend_cache); tor_assert(query); + /* This is possible if we are in the shutdown process and the cache was + * freed while some other subsystem might do a lookup to the cache for + * cleanup reasons such HS circuit cleanup for instance. */ + if (!rend_cache) { + ret = -ENOENT; + goto end; + } + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; @@ -593,10 +613,10 @@ rend_cache_lookup_v2_desc_as_dir(const char *desc_id, const char **desc) char desc_id_digest[DIGEST_LEN]; tor_assert(rend_cache_v2_dir); if (base32_decode(desc_id_digest, DIGEST_LEN, - desc_id, REND_DESC_ID_V2_LEN_BASE32) < 0) { + desc_id, REND_DESC_ID_V2_LEN_BASE32) != DIGEST_LEN) { log_fn(LOG_PROTOCOL_WARN, LD_REND, "Rejecting v2 rendezvous descriptor request -- descriptor ID " - "contains illegal characters: %s", + "has wrong length or illegal characters: %s", safe_str(desc_id)); return -1; } @@ -854,7 +874,8 @@ rend_cache_store_v2_desc_as_client(const char *desc, *entry = NULL; } if (base32_decode(want_desc_id, sizeof(want_desc_id), - desc_id_base32, strlen(desc_id_base32)) != 0) { + desc_id_base32, strlen(desc_id_base32)) != + sizeof(want_desc_id)) { log_warn(LD_BUG, "Couldn't decode base32 %s for descriptor id.", escaped_safe_str_client(desc_id_base32)); goto err; @@ -888,8 +909,8 @@ rend_cache_store_v2_desc_as_client(const char *desc, if (intro_content && intro_size > 0) { int n_intro_points; if (rend_data->auth_type != REND_NO_AUTH && - !tor_mem_is_zero(rend_data->descriptor_cookie, - sizeof(rend_data->descriptor_cookie))) { + !safe_mem_is_zero(rend_data->descriptor_cookie, + sizeof(rend_data->descriptor_cookie))) { char *ipos_decrypted = NULL; size_t ipos_decrypted_size; if (rend_decrypt_introduction_points(&ipos_decrypted, @@ -1005,4 +1026,3 @@ rend_cache_store_v2_desc_as_client(const char *desc, tor_free(intro_content); return retval; } - diff --git a/src/feature/rend/rendcache.h b/src/feature/rend/rendcache.h index aec97eabb8..45410610b4 100644 --- a/src/feature/rend/rendcache.h +++ b/src/feature/rend/rendcache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2019, The Tor Project, Inc. */ +/* Copyright (c) 2015-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -80,6 +80,8 @@ int rend_cache_store_v2_desc_as_client(const char *desc, rend_cache_entry_t **entry); size_t rend_cache_get_total_allocation(void); +bool rend_cache_intro_failure_exists(const char *service_id, + const uint8_t *intro_identity); void rend_cache_intro_failure_note(rend_intro_point_failure_t failure, const uint8_t *identity, const char *service_id); diff --git a/src/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index 6e95142c0b..427491e3a8 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,7 +17,7 @@ #include "core/or/connection_edge.h" #include "core/or/relay.h" #include "feature/client/circpathbias.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirclient/dirclient.h" #include "feature/dircommon/directory.h" #include "feature/hs/hs_circuit.h" @@ -119,7 +119,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, char tmp[RELAY_PAYLOAD_SIZE]; rend_cache_entry_t *entry = NULL; crypt_path_t *cpath; - off_t dh_offset; + ptrdiff_t dh_offset; crypto_pk_t *intro_key = NULL; int status = 0; const char *onion_address; @@ -403,14 +403,23 @@ rend_client_introduction_acked(origin_circuit_t *circ, } else { log_info(LD_REND,"...Found no rend circ. Dropping on the floor."); } + /* Save the rend data digest to a temporary object so that we don't access + * it after we mark the circuit for close. */ + const uint8_t *rend_digest_tmp = NULL; + size_t digest_len; + uint8_t *cached_rend_digest = NULL; + rend_digest_tmp = rend_data_get_pk_digest(circ->rend_data, &digest_len); + cached_rend_digest = tor_malloc_zero(digest_len); + memcpy(cached_rend_digest, rend_digest_tmp, digest_len); + /* close the circuit: we won't need it anymore. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); /* close any other intros launched in parallel */ - rend_client_close_other_intros(rend_data_get_pk_digest(circ->rend_data, - NULL)); + rend_client_close_other_intros(cached_rend_digest); + tor_free(cached_rend_digest); /* free the temporary digest */ } else { /* It's a NAK; the introduction point didn't relay our request. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); @@ -469,16 +478,19 @@ directory_get_from_hs_dir(const char *desc_id, /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { + bool rate_limited = false; + /* Determine responsible dirs. Even if we can't get all we want, work with * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ smartlist_t *responsible_dirs = smartlist_new(); hid_serv_get_responsible_directories(responsible_dirs, desc_id); - hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32, &rate_limited); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ - control_event_hsv2_descriptor_failed(rend_query, NULL, - "QUERY_NO_HSDIR"); + const char *query_response = (rate_limited) ? "QUERY_RATE_LIMITED" : + "QUERY_NO_HSDIR"; + control_event_hsv2_descriptor_failed(rend_query, NULL, query_response); control_event_hs_descriptor_content(rend_data_get_address(rend_query), desc_id_base32, NULL, NULL); return 0; @@ -1036,18 +1048,30 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, const or_options_t *options = get_options(); smartlist_t *usable_nodes; int n_excluded = 0; + char service_id[REND_SERVICE_ID_LEN_BASE32 + 1]; /* We'll keep a separate list of the usable nodes. If this becomes empty, * no nodes are usable. */ usable_nodes = smartlist_new(); smartlist_add_all(usable_nodes, entry->parsed->intro_nodes); + /* Get service ID so we can use it to query the failure cache. If we fail to + * parse it, this cache entry is no good. */ + if (BUG(rend_get_service_id(entry->parsed->pk, service_id) < 0)) { + smartlist_free(usable_nodes); + return NULL; + } + /* Remove the intro points that have timed out during this HS * connection attempt from our list of usable nodes. */ - SMARTLIST_FOREACH(usable_nodes, rend_intro_point_t *, ip, - if (ip->timed_out) { - SMARTLIST_DEL_CURRENT(usable_nodes, ip); - }); + SMARTLIST_FOREACH_BEGIN(usable_nodes, const rend_intro_point_t *, ip) { + bool failed_intro = + rend_cache_intro_failure_exists(service_id, + (const uint8_t *) ip->extend_info->identity_digest); + if (ip->timed_out || failed_intro) { + SMARTLIST_DEL_CURRENT(usable_nodes, ip); + }; + } SMARTLIST_FOREACH_END(ip); again: if (smartlist_len(usable_nodes) == 0) { @@ -1226,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..b7aa212487 100644 --- a/src/feature/rend/rendclient.h +++ b/src/feature/rend/rendclient.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -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) */ diff --git a/src/feature/rend/rendcommon.c b/src/feature/rend/rendcommon.c index de48af795f..5d04755819 100644 --- a/src/feature/rend/rendcommon.c +++ b/src/feature/rend/rendcommon.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -15,7 +15,7 @@ #include "core/or/circuitlist.h" #include "core/or/circuituse.h" #include "app/config/config.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/hs/hs_client.h" @@ -171,9 +171,10 @@ rend_compute_v2_desc_id(char *desc_id_out, const char *service_id, } /* Convert service ID to binary. */ if (base32_decode(service_id_binary, REND_SERVICE_ID_LEN, - service_id, REND_SERVICE_ID_LEN_BASE32) < 0) { + service_id, REND_SERVICE_ID_LEN_BASE32) != + REND_SERVICE_ID_LEN) { log_warn(LD_REND, "Could not compute v2 descriptor ID: " - "Illegal characters in service ID: %s", + "Illegal characters or wrong length for service ID: %s", safe_str_client(service_id)); return -1; } @@ -785,39 +786,39 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, switch (command) { case RELAY_COMMAND_ESTABLISH_INTRO: if (or_circ) - r = hs_intro_received_establish_intro(or_circ,payload,length); + r = hs_intro_received_establish_intro(or_circ, payload, length); break; case RELAY_COMMAND_ESTABLISH_RENDEZVOUS: if (or_circ) - r = rend_mid_establish_rendezvous(or_circ,payload,length); + r = rend_mid_establish_rendezvous(or_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE1: if (or_circ) - r = hs_intro_received_introduce1(or_circ,payload,length); + r = hs_intro_received_introduce1(or_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = hs_service_receive_introduce2(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) - r = hs_client_receive_introduce_ack(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) - r = rend_mid_rendezvous(or_circ,payload,length); + r = rend_mid_rendezvous(or_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = hs_client_receive_rendezvous2(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ, payload, length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = hs_service_receive_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ, payload, length); break; default: tor_fragile_assert(); diff --git a/src/feature/rend/rendcommon.h b/src/feature/rend/rendcommon.h index f136863c7a..d8281e0578 100644 --- a/src/feature/rend/rendcommon.h +++ b/src/feature/rend/rendcommon.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c index af02b34e6b..b497362857 100644 --- a/src/feature/rend/rendmid.c +++ b/src/feature/rend/rendmid.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,6 +18,7 @@ #include "feature/rend/rendmid.h" #include "feature/stats/rephist.h" #include "feature/hs/hs_circuitmap.h" +#include "feature/hs/hs_dos.h" #include "feature/hs/hs_intropoint.h" #include "core/or/or_circuit_st.h" @@ -58,7 +59,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, pk = crypto_pk_asn1_decode((char*)(request+2), asn1len); if (!pk) { reason = END_CIRC_REASON_TORPROTOCOL; - log_warn(LD_PROTOCOL, "Couldn't decode public key."); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Couldn't decode public key."); goto err; } @@ -80,7 +81,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, (char*)request, 2+asn1len+DIGEST_LEN, (char*)(request+2+DIGEST_LEN+asn1len), request_len-(2+DIGEST_LEN+asn1len))<0) { - log_warn(LD_PROTOCOL, + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Incorrect signature on ESTABLISH_INTRO cell; rejecting."); reason = END_CIRC_REASON_TORPROTOCOL; goto err; @@ -117,6 +118,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, /* Now, set up this circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); hs_circuitmap_register_intro_circ_v2_relay_side(circ, (uint8_t *)pk_digest); + hs_dos_setup_default_intro2_defenses(circ); log_info(LD_REND, "Established introduction point on circuit %u for service %s", @@ -160,9 +162,9 @@ rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request, if (request_len < (DIGEST_LEN+(MAX_NICKNAME_LEN+1)+REND_COOKIE_LEN+ DH1024_KEY_LEN+CIPHER_KEY_LEN+ PKCS1_OAEP_PADDING_OVERHEAD)) { - log_warn(LD_PROTOCOL, "Impossibly short INTRODUCE1 cell on circuit %u; " - "responding with nack.", - (unsigned)circ->p_circ_id); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Impossibly short INTRODUCE1 cell on circuit %u; " + "responding with nack.", (unsigned)circ->p_circ_id); goto err; } @@ -181,6 +183,14 @@ rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request, goto err; } + /* Before sending, lets make sure this cell can be sent on the service + * circuit asking the DoS defenses. */ + if (!hs_dos_can_send_intro2(intro_circ)) { + log_info(LD_PROTOCOL, "Can't relay INTRODUCE1 v2 cell due to DoS " + "limitations. Sending NACK to client."); + goto err; + } + log_info(LD_REND, "Sending introduction request for service %s " "from circ %u to circ %u", @@ -237,8 +247,8 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, goto err; } - /* Check if we are configured to accept established rendezvous cells from - * client or in other words Tor2Web clients. */ + /* Check if we are configured to defend ourselves from clients that + * attempt to establish rendezvous points directly to us. */ if (channel_is_client(circ->p_chan) && dos_should_refuse_single_hop_client()) { /* Note it down for the heartbeat log purposes. */ @@ -248,7 +258,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, } if (circ->base_.n_chan) { - log_warn(LD_PROTOCOL, + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Tried to establish rendezvous on non-edge circuit"); goto err; } @@ -260,8 +270,8 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, } if (hs_circuitmap_get_rend_circ_relay_side(request)) { - log_warn(LD_PROTOCOL, - "Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS."); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS."); goto err; } @@ -303,9 +313,9 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, int reason = END_CIRC_REASON_INTERNAL; if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { - log_info(LD_REND, - "Tried to complete rendezvous on non-OR or non-edge circuit %u.", - (unsigned)circ->p_circ_id); + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Tried to complete rendezvous on non-OR or non-edge circuit %u.", + (unsigned)circ->p_circ_id); reason = END_CIRC_REASON_TORPROTOCOL; goto err; } diff --git a/src/feature/rend/rendmid.h b/src/feature/rend/rendmid.h index 8ae1fa16b8..789596d855 100644 --- a/src/feature/rend/rendmid.h +++ b/src/feature/rend/rendmid.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/rend/rendparse.c b/src/feature/rend/rendparse.c index abd0feb448..0979d767a7 100644 --- a/src/feature/rend/rendparse.c +++ b/src/feature/rend/rendparse.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -143,8 +143,9 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, goto err; } if (base32_decode(desc_id_out, DIGEST_LEN, - tok->args[0], REND_DESC_ID_V2_LEN_BASE32) < 0) { - log_warn(LD_REND, "Descriptor ID contains illegal characters: %s", + tok->args[0], REND_DESC_ID_V2_LEN_BASE32) != DIGEST_LEN) { + log_warn(LD_REND, + "Descriptor ID has wrong length or illegal characters: %s", tok->args[0]); goto err; } @@ -174,8 +175,10 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, log_warn(LD_REND, "Invalid secret ID part: '%s'", tok->args[0]); goto err; } - if (base32_decode(secret_id_part, DIGEST_LEN, tok->args[0], 32) < 0) { - log_warn(LD_REND, "Secret ID part contains illegal characters: %s", + if (base32_decode(secret_id_part, DIGEST_LEN, tok->args[0], 32) != + DIGEST_LEN) { + log_warn(LD_REND, + "Secret ID part has wrong length or illegal characters: %s", tok->args[0]); goto err; } @@ -429,8 +432,10 @@ rend_parse_introduction_points(rend_service_descriptor_t *parsed, /* Parse identifier. */ tok = find_by_keyword(tokens, R_IPO_IDENTIFIER); if (base32_decode(info->identity_digest, DIGEST_LEN, - tok->args[0], REND_INTRO_POINT_ID_LEN_BASE32) < 0) { - log_warn(LD_REND, "Identity digest contains illegal characters: %s", + tok->args[0], REND_INTRO_POINT_ID_LEN_BASE32) != + DIGEST_LEN) { + log_warn(LD_REND, + "Identity digest has wrong length or illegal characters: %s", tok->args[0]); rend_intro_point_free(intro); goto err; diff --git a/src/feature/rend/rendparse.h b/src/feature/rend/rendparse.h index 0cef931e90..75109c204d 100644 --- a/src/feature/rend/rendparse.h +++ b/src/feature/rend/rendparse.h @@ -1,12 +1,12 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** - * \file rend_parse.h - * \brief Header file for rend_parse.c. + * \file rendparse.h + * \brief Header file for rendparse.c. **/ #ifndef TOR_REND_PARSE_H @@ -29,4 +29,4 @@ int rend_parse_introduction_points(rend_service_descriptor_t *parsed, size_t intro_points_encoded_size); int rend_parse_client_keys(strmap_t *parsed_clients, const char *str); -#endif +#endif /* !defined(TOR_REND_PARSE_H) */ diff --git a/src/feature/rend/rendservice.c b/src/feature/rend/rendservice.c index eaf0432a7d..12ff056678 100644 --- a/src/feature/rend/rendservice.c +++ b/src/feature/rend/rendservice.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -18,8 +18,9 @@ #include "core/or/circuituse.h" #include "core/or/policies.h" #include "core/or/relay.h" +#include "core/or/crypt_path.h" #include "feature/client/circpathbias.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirclient/dirclient.h" #include "feature/dircommon/directory.h" #include "feature/hs/hs_common.h" @@ -2145,7 +2146,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, * * We only use a one-hop path on the first attempt. If the first attempt * fails, we use a 3-hop path for reachability / reliability. - * See the comment in rend_service_relauch_rendezvous() for details. */ + * See the comment in rend_service_relaunch_rendezvous() for details. */ if (rend_service_use_direct_connection(options, rp) && i == 0) { flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL; } @@ -2186,7 +2187,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, cpath->rend_dh_handshake_state = dh; dh = NULL; - if (circuit_init_cpath_crypto(cpath, + if (cpath_init_circuit_crypto(cpath, keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, 1, 0)<0) goto err; @@ -3035,6 +3036,10 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) { origin_circuit_t *newcirc; cpath_build_state_t *newstate, *oldstate; + const char *rend_pk_digest; + rend_service_t *service = NULL; + + int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); oldstate = oldcirc->build_state; @@ -3049,13 +3054,31 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) log_info(LD_REND,"Reattempting rendezvous circuit to '%s'", safe_str(extend_info_describe(oldstate->chosen_exit))); + /* Look up the service. */ + rend_pk_digest = (char *) rend_data_get_pk_digest(oldcirc->rend_data, NULL); + service = rend_service_get_by_pk_digest(rend_pk_digest); + + if (!service) { + char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; + base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, + rend_pk_digest, REND_SERVICE_ID_LEN); + + log_warn(LD_BUG, "Internal error: Trying to relaunch a rendezvous circ " + "for an unrecognized service %s.", + safe_str_client(serviceid)); + return; + } + + if (hs_service_requires_uptime_circ(service->ports)) { + flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* You'd think Single Onion Services would want to retry the rendezvous * using a direct connection. But if it's blocked by a firewall, or the * service is IPv6-only, or the rend point avoiding becoming a one-hop * proxy, we need a 3-hop connection. */ newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, - oldstate->chosen_exit, - CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + oldstate->chosen_exit, flags); if (!newcirc) { log_warn(LD_REND,"Couldn't relaunch rendezvous circuit to '%s'.", @@ -3555,7 +3578,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) hop->package_window = circuit_initial_package_window(); hop->deliver_window = CIRCWINDOW_START; - onion_append_to_cpath(&circuit->cpath, hop); + cpath_extend_linked_list(&circuit->cpath, hop); circuit->build_state->pending_final_cpath = NULL; /* prevent double-free */ /* Change the circuit purpose. */ @@ -3995,7 +4018,7 @@ remove_invalid_intro_points(rend_service_t *service, * accounted for when considiring uploading a descriptor. */ intro->circuit_established = 0; - /* Node is gone or we've reached our maximum circuit creationg retry + /* Node is gone or we've reached our maximum circuit creation retry * count, clean up everything, we'll find a new one. */ if (node == NULL || intro->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES) { @@ -4235,6 +4258,7 @@ rend_consider_services_intro_points(time_t now) * directly ourselves. */ intro->extend_info = extend_info_from_node(node, 0); if (BUG(intro->extend_info == NULL)) { + tor_free(intro); break; } intro->intro_key = crypto_pk_new(); diff --git a/src/feature/rend/rendservice.h b/src/feature/rend/rendservice.h index a8eb28bee2..8202c4fcd3 100644 --- a/src/feature/rend/rendservice.h +++ b/src/feature/rend/rendservice.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/stats/.may_include b/src/feature/stats/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/stats/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/stats/feature_stats.md b/src/feature/stats/feature_stats.md new file mode 100644 index 0000000000..d205fe5571 --- /dev/null +++ b/src/feature/stats/feature_stats.md @@ -0,0 +1,10 @@ +@dir /feature/stats +@brief feature/stats: Relay statistics. Also, port prediction. + +This module collects anonymized relay statistics in order to publish them in +relays' routerinfo and extrainfo documents. + +Additionally, it contains predict_ports.c, which remembers which ports we've +visited recently as a client, so we can make sure we have open circuits that +support them. + diff --git a/src/feature/stats/geoip_stats.c b/src/feature/stats/geoip_stats.c index a54b589eb6..3228b18973 100644 --- a/src/feature/stats/geoip_stats.c +++ b/src/feature/stats/geoip_stats.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -30,9 +30,9 @@ #include "core/or/or.h" #include "ht.h" -#include "lib/container/buffers.h" +#include "lib/buf/buffers.h" #include "app/config/config.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/client/dnsserv.h" #include "core/or/dos.h" #include "lib/geoip/geoip.h" diff --git a/src/feature/stats/geoip_stats.h b/src/feature/stats/geoip_stats.h index 2fc62b5466..fcfe7a31f0 100644 --- a/src/feature/stats/geoip_stats.h +++ b/src/feature/stats/geoip_stats.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -13,6 +13,7 @@ #define TOR_GEOIP_STATS_H #include "core/or/dos.h" +#include "ext/ht.h" /** Indicates an action that we might be noting geoip statistics on. * Note that if we're noticing CONNECT, we're a bridge, and if we're noticing diff --git a/src/feature/stats/include.am b/src/feature/stats/include.am new file mode 100644 index 0000000000..8789bc3d96 --- /dev/null +++ b/src/feature/stats/include.am @@ -0,0 +1,12 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/stats/geoip_stats.c \ + src/feature/stats/rephist.c \ + src/feature/stats/predict_ports.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/stats/geoip_stats.h \ + src/feature/stats/rephist.h \ + src/feature/stats/predict_ports.h diff --git a/src/feature/stats/predict_ports.c b/src/feature/stats/predict_ports.c index 3cbba2c831..d728f106a2 100644 --- a/src/feature/stats/predict_ports.c +++ b/src/feature/stats/predict_ports.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/feature/stats/predict_ports.h b/src/feature/stats/predict_ports.h index 272344da2f..ed067b6ced 100644 --- a/src/feature/stats/predict_ports.h +++ b/src/feature/stats/predict_ports.h @@ -1,11 +1,11 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** - * \file predict_portst.h + * \file predict_ports.h * \brief Header file for predict_ports.c. **/ @@ -27,4 +27,4 @@ int rep_hist_circbuilding_dormant(time_t now); int predicted_ports_prediction_time_remaining(time_t now); void predicted_ports_free_all(void); -#endif +#endif /* !defined(TOR_PREDICT_PORTS_H) */ diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index 3f560fbce7..d229c755b4 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -1122,7 +1122,7 @@ static bw_array_t *dir_read_array = NULL; directory protocol. */ static bw_array_t *dir_write_array = NULL; -/** Set up [dir-]read_array and [dir-]write_array, freeing them if they +/** Set up [dir_]read_array and [dir_]write_array, freeing them if they * already exist. */ static void bw_arrays_init(void) diff --git a/src/feature/stats/rephist.h b/src/feature/stats/rephist.h index 3accc8c610..92c3d2a5a5 100644 --- a/src/feature/stats/rephist.h +++ b/src/feature/stats/rephist.h @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2019, The Tor Project, Inc. */ + * Copyright (c) 2007-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -103,7 +103,7 @@ typedef struct bw_array_t bw_array_t; STATIC uint64_t find_largest_max(bw_array_t *b); STATIC void commit_max(bw_array_t *b); STATIC void advance_obs(bw_array_t *b); -#endif +#endif /* defined(REPHIST_PRIVATE) */ /** * Represents the type of a cell for padding accounting |