diff options
Diffstat (limited to 'src')
204 files changed, 16912 insertions, 9877 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c index 8e0bfbe797..07521ea0ae 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -86,6 +86,8 @@ #include "feature/client/entrynodes.h" #include "feature/client/transports.h" #include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_events.h" #include "feature/dirauth/bwauth.h" #include "feature/dirauth/guardfraction.h" #include "feature/dircache/consdiffmgr.h" @@ -4188,6 +4190,10 @@ options_validate(or_options_t *old_options, or_options_t *options, "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; diff --git a/src/app/config/statefile.c b/src/app/config/statefile.c index 9681f6f8b3..fdfd68b244 100644 --- a/src/app/config/statefile.c +++ b/src/app/config/statefile.c @@ -36,7 +36,7 @@ #include "core/mainloop/mainloop.h" #include "core/mainloop/netstatus.h" #include "core/mainloop/connection.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/client/entrynodes.h" #include "feature/hibernate/hibernate.h" #include "feature/stats/rephist.h" diff --git a/src/app/main/main.c b/src/app/main/main.c index 0ffc27d456..184b9e91da 100644 --- a/src/app/main/main.c +++ b/src/app/main/main.c @@ -15,66 +15,51 @@ #include "app/config/statefile.h" #include "app/main/main.h" #include "app/main/ntmain.h" +#include "app/main/shutdown.h" #include "app/main/subsysmgr.h" #include "core/mainloop/connection.h" #include "core/mainloop/cpuworker.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/mainloop_pubsub.h" #include "core/mainloop/netstatus.h" #include "core/or/channel.h" #include "core/or/channelpadding.h" #include "core/or/circuitpadding.h" -#include "core/or/channeltls.h" #include "core/or/circuitlist.h" -#include "core/or/circuitmux_ewma.h" #include "core/or/command.h" -#include "core/or/connection_edge.h" #include "core/or/connection_or.h" -#include "core/or/dos.h" -#include "core/or/policies.h" -#include "core/or/protover.h" #include "core/or/relay.h" -#include "core/or/scheduler.h" #include "core/or/status.h" -#include "core/or/versions.h" #include "feature/api/tor_api.h" #include "feature/api/tor_api_internal.h" #include "feature/client/addressmap.h" -#include "feature/client/bridges.h" -#include "feature/client/entrynodes.h" -#include "feature/client/transports.h" #include "feature/control/control.h" -#include "feature/dirauth/bwauth.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_events.h" #include "feature/dirauth/keypin.h" #include "feature/dirauth/process_descs.h" #include "feature/dircache/consdiffmgr.h" -#include "feature/dircache/dirserv.h" #include "feature/dirparse/routerparse.h" #include "feature/hibernate/hibernate.h" -#include "feature/hs/hs_cache.h" #include "feature/nodelist/authcert.h" -#include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" -#include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/relay/dns.h" #include "feature/relay/ext_orport.h" -#include "feature/relay/onion_queue.h" #include "feature/relay/routerkeys.h" #include "feature/relay/routermode.h" #include "feature/rend/rendcache.h" -#include "feature/rend/rendclient.h" #include "feature/rend/rendservice.h" -#include "feature/stats/geoip_stats.h" #include "feature/stats/predict_ports.h" #include "feature/stats/rephist.h" #include "lib/compress/compress.h" #include "lib/buf/buffers.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_s2k.h" -#include "lib/geoip/geoip.h" #include "lib/net/resolve.h" #include "lib/process/waitpid.h" +#include "lib/pubsub/pubsub_build.h" #include "lib/meminfo/meminfo.h" #include "lib/osinfo/uname.h" @@ -90,7 +75,6 @@ #include <event2/event.h> -#include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/shared_random.h" @@ -111,8 +95,6 @@ #include <systemd/sd-daemon.h> #endif /* defined(HAVE_SYSTEMD) */ -void evdns_shutdown(int); - #ifdef HAVE_RUST // helper function defined in Rust to output a log message indicating if tor is // running with Rust enabled. See src/rust/tor_util @@ -669,7 +651,7 @@ tor_init(int argc, char *argv[]) log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting."); return -1; } - stream_choice_seed_weak_rng(); + if (tor_init_libevent_rng() < 0) { log_warn(LD_NET, "Problem initializing libevent RNG."); } @@ -742,86 +724,6 @@ release_lockfile(void) } } -/** Free all memory that we might have allocated somewhere. - * If <b>postfork</b>, we are a worker process and we want to free - * only the parts of memory that we won't touch. If !<b>postfork</b>, - * Tor is shutting down and we should free everything. - * - * Helps us find the real leaks with sanitizers and the like. Also valgrind - * should then report 0 reachable in its leak report (in an ideal world -- - * in practice libevent, SSL, libc etc never quite free everything). */ -void -tor_free_all(int postfork) -{ - if (!postfork) { - evdns_shutdown(1); - } - geoip_free_all(); - geoip_stats_free_all(); - dirvote_free_all(); - routerlist_free_all(); - networkstatus_free_all(); - addressmap_free_all(); - dirserv_free_fingerprint_list(); - dirserv_free_all(); - dirserv_clear_measured_bw_cache(); - rend_cache_free_all(); - rend_service_authorization_free_all(); - rep_hist_free_all(); - dns_free_all(); - clear_pending_onions(); - circuit_free_all(); - circpad_machines_free(); - entry_guards_free_all(); - pt_free_all(); - channel_tls_free_all(); - channel_free_all(); - connection_free_all(); - connection_edge_free_all(); - scheduler_free_all(); - nodelist_free_all(); - microdesc_free_all(); - routerparse_free_all(); - ext_orport_free_all(); - control_free_all(); - protover_free_all(); - bridges_free_all(); - consdiffmgr_free_all(); - hs_free_all(); - dos_free_all(); - circuitmux_ewma_free_all(); - accounting_free_all(); - protover_summary_cache_free_all(); - - if (!postfork) { - config_free_all(); - or_state_free_all(); - router_free_all(); - routerkeys_free_all(); - policies_free_all(); - } - if (!postfork) { -#ifndef _WIN32 - tor_getpwnam(NULL); -#endif - } - /* stuff in main.c */ - - tor_mainloop_free_all(); - - if (!postfork) { - release_lockfile(); - } - tor_libevent_free_all(); - - subsystems_shutdown(); - - /* Stuff in util.c and address.c*/ - if (!postfork) { - esc_router_info(NULL); - } -} - /** * Remove the specified file, and log a warning if the operation fails for * any reason other than the file not existing. Ignores NULL filenames. @@ -835,50 +737,6 @@ tor_remove_file(const char *filename) } } -/** Do whatever cleanup is necessary before shutting Tor down. */ -void -tor_cleanup(void) -{ - const or_options_t *options = get_options(); - if (options->command == CMD_RUN_TOR) { - time_t now = time(NULL); - /* Remove our pid file. We don't care if there was an error when we - * unlink, nothing we could do about it anyways. */ - tor_remove_file(options->PidFile); - /* Remove control port file */ - tor_remove_file(options->ControlPortWriteToFile); - /* Remove cookie authentication file */ - { - char *cookie_fname = get_controller_cookie_file_name(); - tor_remove_file(cookie_fname); - tor_free(cookie_fname); - } - /* Remove Extended ORPort cookie authentication file */ - { - char *cookie_fname = get_ext_or_auth_cookie_file_name(); - tor_remove_file(cookie_fname); - tor_free(cookie_fname); - } - if (accounting_is_enabled(options)) - accounting_record_bandwidth_usage(now, get_or_state()); - or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */ - or_state_save(now); - if (authdir_mode(options)) { - sr_save_and_cleanup(); - } - if (authdir_mode_tests_reachability(options)) - rep_hist_record_mtbf_data(now, 0); - keypin_close_journal(); - } - - timers_shutdown(); - - tor_free_all(0); /* We could move tor_free_all back into the ifdef below - later, if it makes shutdown unacceptably slow. But for - now, leave it here: it's helped us catch bugs in the - past. */ -} - /** Read/create keys as needed, and echo our fingerprint to stdout. */ static int do_list_fingerprint(void) @@ -1376,6 +1234,30 @@ run_tor_main_loop(void) return do_main_loop(); } +/** Install the publish/subscribe relationships for all the subsystems. */ +static void +pubsub_install(void) +{ + pubsub_builder_t *builder = pubsub_builder_new(); + int r = subsystems_add_pubsub(builder); + tor_assert(r == 0); + r = tor_mainloop_connect_pubsub(builder); // consumes builder + tor_assert(r == 0); +} + +/** Connect the mainloop to its publish/subscribe message delivery events if + * appropriate, and configure the global channels appropriately. */ +static void +pubsub_connect(void) +{ + if (get_options()->command == CMD_RUN_TOR) { + tor_mainloop_connect_pubsub_events(); + /* XXXX For each pubsub channel, its delivery strategy should be set at + * this XXXX point, using tor_mainloop_set_delivery_strategy(). + */ + } +} + /* Main entry point for the Tor process. Called from tor_main(), and by * anybody embedding Tor. */ int @@ -1407,6 +1289,9 @@ tor_run_main(const tor_main_configuration_t *tor_cfg) } } #endif /* defined(NT_SERVICE) */ + + pubsub_install(); + { int init_rv = tor_init(argc, argv); if (init_rv) { @@ -1416,6 +1301,8 @@ tor_run_main(const tor_main_configuration_t *tor_cfg) } } + pubsub_connect(); + if (get_options()->Sandbox && get_options()->command == CMD_RUN_TOR) { sandbox_cfg_t* cfg = sandbox_init_filter(); diff --git a/src/app/main/main.h b/src/app/main/main.h index bbbbf984fb..9dfaf4b8ef 100644 --- a/src/app/main/main.h +++ b/src/app/main/main.h @@ -21,9 +21,6 @@ void release_lockfile(void); void tor_remove_file(const char *filename); -void tor_cleanup(void); -void tor_free_all(int postfork); - int tor_init(int argc, char **argv); int run_tor_main_loop(void); diff --git a/src/app/main/ntmain.c b/src/app/main/ntmain.c index 05d203b0be..f00b712702 100644 --- a/src/app/main/ntmain.c +++ b/src/app/main/ntmain.c @@ -24,6 +24,7 @@ #include "app/config/config.h" #include "app/main/main.h" #include "app/main/ntmain.h" +#include "app/main/shutdown.h" #include "core/mainloop/mainloop.h" #include "lib/evloop/compat_libevent.h" #include "lib/fs/winlib.h" diff --git a/src/app/main/shutdown.c b/src/app/main/shutdown.c new file mode 100644 index 0000000000..314e33f228 --- /dev/null +++ b/src/app/main/shutdown.c @@ -0,0 +1,192 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file shutdown.c + * @brief Code to free global resources used by Tor. + * + * In the future, this should all be handled by the subsystem manager. */ + +#include "core/or/or.h" + +#include "app/config/config.h" +#include "app/config/statefile.h" +#include "app/main/main.h" +#include "app/main/shutdown.h" +#include "app/main/subsysmgr.h" +#include "core/mainloop/connection.h" +#include "core/mainloop/mainloop.h" +#include "core/mainloop/mainloop_pubsub.h" +#include "core/or/channeltls.h" +#include "core/or/circuitlist.h" +#include "core/or/circuitmux_ewma.h" +#include "core/or/circuitpadding.h" +#include "core/or/connection_edge.h" +#include "core/or/dos.h" +#include "core/or/policies.h" +#include "core/or/protover.h" +#include "core/or/scheduler.h" +#include "core/or/versions.h" +#include "feature/client/addressmap.h" +#include "feature/client/bridges.h" +#include "feature/client/entrynodes.h" +#include "feature/client/transports.h" +#include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/dirauth/authmode.h" +#include "feature/dirauth/bwauth.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/keypin.h" +#include "feature/dirauth/process_descs.h" +#include "feature/dirauth/shared_random.h" +#include "feature/dircache/consdiffmgr.h" +#include "feature/dircache/dirserv.h" +#include "feature/dirparse/routerparse.h" +#include "feature/hibernate/hibernate.h" +#include "feature/hs/hs_common.h" +#include "feature/nodelist/microdesc.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerlist.h" +#include "feature/nodelist/routerlist.h" +#include "feature/relay/dns.h" +#include "feature/relay/ext_orport.h" +#include "feature/relay/onion_queue.h" +#include "feature/relay/routerkeys.h" +#include "feature/rend/rendcache.h" +#include "feature/rend/rendclient.h" +#include "feature/stats/geoip_stats.h" +#include "feature/stats/rephist.h" +#include "lib/evloop/compat_libevent.h" +#include "lib/geoip/geoip.h" +#include "src/feature/relay/router.h" + +void evdns_shutdown(int); + +/** Do whatever cleanup is necessary before shutting Tor down. */ +void +tor_cleanup(void) +{ + const or_options_t *options = get_options(); + if (options->command == CMD_RUN_TOR) { + time_t now = time(NULL); + /* Remove our pid file. We don't care if there was an error when we + * unlink, nothing we could do about it anyways. */ + tor_remove_file(options->PidFile); + /* Remove control port file */ + tor_remove_file(options->ControlPortWriteToFile); + /* Remove cookie authentication file */ + { + char *cookie_fname = get_controller_cookie_file_name(); + tor_remove_file(cookie_fname); + tor_free(cookie_fname); + } + /* Remove Extended ORPort cookie authentication file */ + { + char *cookie_fname = get_ext_or_auth_cookie_file_name(); + tor_remove_file(cookie_fname); + tor_free(cookie_fname); + } + if (accounting_is_enabled(options)) + accounting_record_bandwidth_usage(now, get_or_state()); + or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */ + or_state_save(now); + if (authdir_mode(options)) { + sr_save_and_cleanup(); + } + if (authdir_mode_tests_reachability(options)) + rep_hist_record_mtbf_data(now, 0); + keypin_close_journal(); + } + + timers_shutdown(); + + tor_free_all(0); /* We could move tor_free_all back into the ifdef below + later, if it makes shutdown unacceptably slow. But for + now, leave it here: it's helped us catch bugs in the + past. */ +} + +/** Free all memory that we might have allocated somewhere. + * If <b>postfork</b>, we are a worker process and we want to free + * only the parts of memory that we won't touch. If !<b>postfork</b>, + * Tor is shutting down and we should free everything. + * + * Helps us find the real leaks with sanitizers and the like. Also valgrind + * should then report 0 reachable in its leak report (in an ideal world -- + * in practice libevent, SSL, libc etc never quite free everything). */ +void +tor_free_all(int postfork) +{ + if (!postfork) { + evdns_shutdown(1); + } + geoip_free_all(); + geoip_stats_free_all(); + dirvote_free_all(); + routerlist_free_all(); + networkstatus_free_all(); + addressmap_free_all(); + dirserv_free_fingerprint_list(); + dirserv_free_all(); + dirserv_clear_measured_bw_cache(); + rend_cache_free_all(); + rend_service_authorization_free_all(); + rep_hist_free_all(); + dns_free_all(); + clear_pending_onions(); + circuit_free_all(); + circpad_machines_free(); + entry_guards_free_all(); + pt_free_all(); + channel_tls_free_all(); + channel_free_all(); + connection_free_all(); + connection_edge_free_all(); + scheduler_free_all(); + nodelist_free_all(); + microdesc_free_all(); + routerparse_free_all(); + ext_orport_free_all(); + control_free_all(); + protover_free_all(); + bridges_free_all(); + consdiffmgr_free_all(); + hs_free_all(); + dos_free_all(); + circuitmux_ewma_free_all(); + accounting_free_all(); + protover_summary_cache_free_all(); + + if (!postfork) { + config_free_all(); + or_state_free_all(); + router_free_all(); + routerkeys_free_all(); + policies_free_all(); + } + if (!postfork) { +#ifndef _WIN32 + tor_getpwnam(NULL); +#endif + } + /* stuff in main.c */ + + tor_mainloop_disconnect_pubsub(); + tor_mainloop_free_all(); + + if (!postfork) { + release_lockfile(); + } + tor_libevent_free_all(); + + subsystems_shutdown(); + + /* Stuff in util.c and address.c*/ + if (!postfork) { + esc_router_info(NULL); + } +} diff --git a/src/app/main/shutdown.h b/src/app/main/shutdown.h new file mode 100644 index 0000000000..1bca96a0aa --- /dev/null +++ b/src/app/main/shutdown.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file shutdown.h + * \brief Header file for shutdown.c. + **/ + +#ifndef TOR_SHUTDOWN_H +#define TOR_SHUTDOWN_H + +void tor_cleanup(void); +void tor_free_all(int postfork); + +#endif /* !defined(TOR_SHUTDOWN_H) */ diff --git a/src/app/main/subsysmgr.c b/src/app/main/subsysmgr.c index e0ca3ce4df..5aa4fd76c9 100644 --- a/src/app/main/subsysmgr.c +++ b/src/app/main/subsysmgr.c @@ -5,9 +5,14 @@ #include "orconfig.h" #include "app/main/subsysmgr.h" -#include "lib/err/torerr.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/msgtypes.h" +#include "lib/err/torerr.h" #include "lib/log/log.h" +#include "lib/malloc/malloc.h" +#include "lib/pubsub/pubsub_build.h" +#include "lib/pubsub/pubsub_connect.h" #include <stdio.h> #include <stdlib.h> @@ -106,6 +111,51 @@ subsystems_init_upto(int target_level) } /** + * Add publish/subscribe relationships to <b>builder</b> for all + * initialized subsystems of level no more than <b>target_level</b>. + **/ +int +subsystems_add_pubsub_upto(pubsub_builder_t *builder, + int target_level) +{ + for (unsigned i = 0; i < n_tor_subsystems; ++i) { + const subsys_fns_t *sys = tor_subsystems[i]; + if (!sys->supported) + continue; + if (sys->level > target_level) + break; + if (! sys_initialized[i]) + continue; + int r = 0; + if (sys->add_pubsub) { + subsys_id_t sysid = get_subsys_id(sys->name); + raw_assert(sysid != ERROR_ID); + pubsub_connector_t *connector; + connector = pubsub_connector_for_subsystem(builder, sysid); + r = sys->add_pubsub(connector); + pubsub_connector_free(connector); + } + if (r < 0) { + fprintf(stderr, "BUG: subsystem %s (at %u) could not connect to " + "publish/subscribe system.", sys->name, sys->level); + raw_assert_unreached_msg("A subsystem couldn't be connected."); + } + } + + return 0; +} + +/** + * Add publish/subscribe relationships to <b>builder</b> for all + * initialized subsystems. + **/ +int +subsystems_add_pubsub(pubsub_builder_t *builder) +{ + return subsystems_add_pubsub_upto(builder, MAX_SUBSYS_LEVEL); +} + +/** * Shut down all the subsystems. **/ void diff --git a/src/app/main/subsysmgr.h b/src/app/main/subsysmgr.h index a5e62f71d9..4ac44afca7 100644 --- a/src/app/main/subsysmgr.h +++ b/src/app/main/subsysmgr.h @@ -14,6 +14,11 @@ extern const unsigned n_tor_subsystems; int subsystems_init(void); int subsystems_init_upto(int level); +struct pubsub_builder_t; +int subsystems_add_pubsub_upto(struct pubsub_builder_t *builder, + int target_level); +int subsystems_add_pubsub(struct pubsub_builder_t *builder); + void subsystems_shutdown(void); void subsystems_shutdown_downto(int level); diff --git a/src/config/mmdb-convert.py b/src/config/mmdb-convert.py index 706a8b03cc..b861e9433e 100644 --- a/src/config/mmdb-convert.py +++ b/src/config/mmdb-convert.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/python # This software has been dedicated to the public domain under the CC0 # public domain dedication. diff --git a/src/core/crypto/hs_ntor.c b/src/core/crypto/hs_ntor.c index c34073690e..add8a2b8f2 100644 --- a/src/core/crypto/hs_ntor.c +++ b/src/core/crypto/hs_ntor.c @@ -176,7 +176,6 @@ get_introduce1_key_material(const uint8_t *secret_input, uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN]; uint8_t info_blob[INFO_BLOB_LEN]; uint8_t kdf_input[KDF_INPUT_LEN]; - crypto_xof_t *xof; uint8_t *ptr; /* Let's build info */ @@ -193,10 +192,8 @@ get_introduce1_key_material(const uint8_t *secret_input, tor_assert(ptr == kdf_input + sizeof(kdf_input)); /* Now we need to run kdf_input over SHAKE-256 */ - xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); - crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)) ; - crypto_xof_free(xof); + crypto_xof(keystream, sizeof(keystream), + kdf_input, sizeof(kdf_input)); { /* Get the keys */ memcpy(&hs_ntor_intro_cell_keys_out->enc_key, keystream,CIPHER256_KEY_LEN); @@ -594,7 +591,6 @@ hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len, { uint8_t *ptr; uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; - crypto_xof_t *xof; /* Sanity checks on lengths to make sure we are good */ if (BUG(seed_len != DIGEST256_LEN)) { @@ -611,10 +607,8 @@ hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len, tor_assert(ptr == kdf_input + sizeof(kdf_input)); /* Generate the keys */ - xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); - crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN); - crypto_xof_free(xof); + crypto_xof(keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN, + kdf_input, sizeof(kdf_input)); return 0; } diff --git a/src/core/include.am b/src/core/include.am index ae47c75e09..9824601725 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -11,6 +11,7 @@ LIBTOR_APP_A_SOURCES = \ src/app/config/confparse.c \ src/app/config/statefile.c \ src/app/main/main.c \ + src/app/main/shutdown.c \ src/app/main/subsystem_list.c \ src/app/main/subsysmgr.c \ src/core/crypto/hs_ntor.c \ @@ -22,6 +23,7 @@ LIBTOR_APP_A_SOURCES = \ src/core/mainloop/connection.c \ src/core/mainloop/cpuworker.c \ src/core/mainloop/mainloop.c \ + src/core/mainloop/mainloop_pubsub.c \ src/core/mainloop/netstatus.c \ src/core/mainloop/periodic.c \ src/core/or/address_set.c \ @@ -70,7 +72,12 @@ LIBTOR_APP_A_SOURCES = \ 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_events.c \ + src/feature/control/control_fmt.c \ + src/feature/control/control_getinfo.c \ src/feature/control/fmt_serverstatus.c \ src/feature/control/getinfo_geoip.c \ src/feature/dirauth/keypin.c \ @@ -203,6 +210,7 @@ noinst_HEADERS += \ src/app/config/statefile.h \ src/app/main/main.h \ src/app/main/ntmain.h \ + src/app/main/shutdown.h \ src/app/main/subsysmgr.h \ src/core/crypto/hs_ntor.h \ src/core/crypto/onion_crypto.h \ @@ -213,6 +221,7 @@ noinst_HEADERS += \ src/core/mainloop/connection.h \ src/core/mainloop/cpuworker.h \ src/core/mainloop/mainloop.h \ + src/core/mainloop/mainloop_pubsub.h \ src/core/mainloop/netstatus.h \ src/core/mainloop/periodic.h \ src/core/or/addr_policy_st.h \ @@ -287,7 +296,12 @@ noinst_HEADERS += \ 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_connection_st.h \ + src/feature/control/control_events.h \ + src/feature/control/control_fmt.h \ + src/feature/control/control_getinfo.h \ src/feature/control/fmt_serverstatus.h \ src/feature/control/getinfo_geoip.h \ src/feature/dirauth/authmode.h \ diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 7f2e96b4d1..a56e7f9e0a 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -88,6 +88,7 @@ #include "feature/client/entrynodes.h" #include "feature/client/transports.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "feature/dircache/dirserv.h" #include "feature/dircommon/directory.h" diff --git a/src/core/mainloop/cpuworker.c b/src/core/mainloop/cpuworker.c index e704d55642..436fcd28c3 100644 --- a/src/core/mainloop/cpuworker.c +++ b/src/core/mainloop/cpuworker.c @@ -34,7 +34,6 @@ #include "core/crypto/onion_crypto.h" #include "core/or/or_circuit_st.h" -#include "lib/intmath/weakrng.h" static void queue_pending_tasks(void); @@ -74,8 +73,6 @@ worker_state_free_void(void *arg) static replyqueue_t *replyqueue = NULL; static threadpool_t *threadpool = NULL; -static tor_weak_rng_t request_sample_rng = TOR_WEAK_RNG_INIT; - static int total_pending_tasks = 0; static int max_pending_tasks = 128; @@ -109,7 +106,6 @@ cpu_init(void) /* Total voodoo. Can we make this more sensible? */ max_pending_tasks = get_num_cpus(get_options()) * 64; - crypto_seed_weak_rng(&request_sample_rng); } /** Magic numbers to make sure our cpuworker_requests don't grow any @@ -235,9 +231,10 @@ should_time_request(uint16_t onionskin_type) * sample */ if (onionskins_n_processed[onionskin_type] < 4096) return 1; + /** Otherwise, measure with P=1/128. We avoid doing this for every * handshake, since the measurement itself can take a little time. */ - return tor_weak_random_one_in_n(&request_sample_rng, 128); + return crypto_fast_rng_one_in_n(get_thread_fast_rng(), 128); } /** Return an estimate of how many microseconds we will need for a single diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index 18e87fa87a..c9f2b0d896 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -73,6 +73,7 @@ #include "feature/client/entrynodes.h" #include "feature/client/transports.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/reachability.h" #include "feature/dircache/consdiffmgr.h" diff --git a/src/core/mainloop/mainloop_pubsub.c b/src/core/mainloop/mainloop_pubsub.c new file mode 100644 index 0000000000..724a3115c8 --- /dev/null +++ b/src/core/mainloop/mainloop_pubsub.c @@ -0,0 +1,170 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#include "src/core/or/or.h" +#include "src/core/mainloop/mainloop.h" +#include "src/core/mainloop/mainloop_pubsub.h" + +#include "lib/container/smartlist.h" +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/evloop/compat_libevent.h" +#include "lib/pubsub/pubsub.h" +#include "lib/pubsub/pubsub_build.h" + +/** + * Dispatcher to use for delivering messages. + **/ +static dispatch_t *the_dispatcher = NULL; +static pubsub_items_t *the_pubsub_items = NULL; +/** + * A list of mainloop_event_t, indexed by channel ID, to flush the messages + * on a channel. + **/ +static smartlist_t *alert_events = NULL; + +/** + * Mainloop event callback: flush all the messages in a channel. + * + * The channel is encoded as a pointer, and passed via arg. + **/ +static void +flush_channel_event(mainloop_event_t *ev, void *arg) +{ + (void)ev; + if (!the_dispatcher) + return; + + channel_id_t chan = (channel_id_t)(uintptr_t)(arg); + dispatch_flush(the_dispatcher, chan, INT_MAX); +} + +/** + * Construct our global pubsub object from <b>builder</b>. Return 0 on + * success, -1 on failure. */ +int +tor_mainloop_connect_pubsub(struct pubsub_builder_t *builder) +{ + int rv = -1; + tor_mainloop_disconnect_pubsub(); + + the_dispatcher = pubsub_builder_finalize(builder, &the_pubsub_items); + if (! the_dispatcher) + goto err; + + rv = 0; + goto done; + err: + tor_mainloop_disconnect_pubsub(); + done: + return rv; +} + +/** + * Install libevent events for all of the pubsub channels. + * + * Invoke this after tor_mainloop_connect_pubsub, and after libevent has been + * initialized. + */ +void +tor_mainloop_connect_pubsub_events(void) +{ + tor_assert(the_dispatcher); + tor_assert(! alert_events); + + const size_t num_channels = get_num_channel_ids(); + alert_events = smartlist_new(); + for (size_t i = 0; i < num_channels; ++i) { + smartlist_add(alert_events, + mainloop_event_postloop_new(flush_channel_event, + (void*)(uintptr_t)(i))); + } +} + +/** + * Dispatch alertfn callback: do nothing. Implements DELIV_NEVER. + **/ +static void +alertfn_never(dispatch_t *d, channel_id_t chan, void *arg) +{ + (void)d; + (void)chan; + (void)arg; +} + +/** + * Dispatch alertfn callback: activate a mainloop event. Implements + * DELIV_PROMPT. + **/ +static void +alertfn_prompt(dispatch_t *d, channel_id_t chan, void *arg) +{ + (void)d; + (void)chan; + mainloop_event_t *event = arg; + mainloop_event_activate(event); +} + +/** + * Dispatch alertfn callback: flush all messages right now. Implements + * DELIV_IMMEDIATE. + **/ +static void +alertfn_immediate(dispatch_t *d, channel_id_t chan, void *arg) +{ + (void) arg; + dispatch_flush(d, chan, INT_MAX); +} + +/** + * Set the strategy to be used for delivering messages on the named channel. + * + * This function needs to be called once globally for each channel, to + * set up how messages are delivered. + **/ +int +tor_mainloop_set_delivery_strategy(const char *msg_channel_name, + deliv_strategy_t strategy) +{ + channel_id_t chan = get_channel_id(msg_channel_name); + if (BUG(chan == ERROR_ID) || + BUG(chan >= smartlist_len(alert_events))) + return -1; + + switch (strategy) { + case DELIV_NEVER: + dispatch_set_alert_fn(the_dispatcher, chan, alertfn_never, NULL); + break; + case DELIV_PROMPT: + dispatch_set_alert_fn(the_dispatcher, chan, alertfn_prompt, + smartlist_get(alert_events, chan)); + break; + case DELIV_IMMEDIATE: + dispatch_set_alert_fn(the_dispatcher, chan, alertfn_immediate, NULL); + break; + } + return 0; +} + +/** + * Remove all pubsub dispatchers and events from the mainloop. + **/ +void +tor_mainloop_disconnect_pubsub(void) +{ + if (the_pubsub_items) { + pubsub_items_clear_bindings(the_pubsub_items); + pubsub_items_free(the_pubsub_items); + } + if (alert_events) { + SMARTLIST_FOREACH(alert_events, mainloop_event_t *, ev, + mainloop_event_free(ev)); + smartlist_free(alert_events); + } + dispatch_free(the_dispatcher); +} diff --git a/src/core/mainloop/mainloop_pubsub.h b/src/core/mainloop/mainloop_pubsub.h new file mode 100644 index 0000000000..a31b2b4ba7 --- /dev/null +++ b/src/core/mainloop/mainloop_pubsub.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-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_MAINLOOP_PUBSUB_H +#define TOR_MAINLOOP_PUBSUB_H + +struct pubsub_builder_t; + +typedef enum { + DELIV_NEVER=0, + DELIV_PROMPT, + DELIV_IMMEDIATE, +} deliv_strategy_t; + +int tor_mainloop_connect_pubsub(struct pubsub_builder_t *builder); +void tor_mainloop_connect_pubsub_events(void); +int tor_mainloop_set_delivery_strategy(const char *msg_channel_name, + deliv_strategy_t strategy); +void tor_mainloop_disconnect_pubsub(void); + +#endif diff --git a/src/core/or/circuit_st.h b/src/core/or/circuit_st.h index af343f082e..cc21cf62f7 100644 --- a/src/core/or/circuit_st.h +++ b/src/core/or/circuit_st.h @@ -13,7 +13,7 @@ struct hs_token_t; struct circpad_machine_spec_t; -struct circpad_machine_state_t; +struct circpad_machine_runtime_t; /** Number of padding state machines on a circuit. */ #define CIRCPAD_MAX_MACHINES (2) @@ -66,12 +66,6 @@ struct circuit_t { */ circid_t n_circ_id; - /** - * Circuit mux associated with n_chan to which this circuit is attached; - * NULL if we have no n_chan. - */ - circuitmux_t *n_mux; - /** Queue of cells waiting to be transmitted on n_chan */ cell_queue_t n_chan_cells; @@ -193,8 +187,8 @@ struct circuit_t { * and we can have up to CIRCPAD_MAX_MACHINES such machines. */ const struct circpad_machine_spec_t *padding_machine[CIRCPAD_MAX_MACHINES]; - /** Adaptive Padding machine info for above machines. This is the - * per-circuit mutable information, such as the current state and + /** Adaptive Padding machine runtime info for above machines. This is + * the per-circuit mutable information, such as the current state and * histogram token counts. Some of it is optional (aka NULL). * If a machine is being shut down, these indexes can be NULL * without the corresponding padding_machine being NULL, while we @@ -202,7 +196,7 @@ struct circuit_t { * * Each element of this array corresponds to a different padding machine, * and we can have up to CIRCPAD_MAX_MACHINES such machines. */ - struct circpad_machine_state_t *padding_info[CIRCPAD_MAX_MACHINES]; + struct circpad_machine_runtime_t *padding_info[CIRCPAD_MAX_MACHINES]; }; #endif diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 3ec1e01f11..f8e87bf026 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -55,7 +55,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" diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c index 6b5f30e418..afbde06434 100644 --- a/src/core/or/circuitlist.c +++ b/src/core/or/circuitlist.c @@ -67,7 +67,7 @@ #include "app/config/config.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/crypt_ops/crypto_util.h" #include "lib/crypt_ops/crypto_dh.h" @@ -2433,13 +2433,9 @@ marked_circuit_free_cells(circuit_t *circ) return; } cell_queue_clear(&circ->n_chan_cells); - if (circ->n_mux) - circuitmux_clear_num_cells(circ->n_mux, circ); if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); cell_queue_clear(&orcirc->p_chan_cells); - if (orcirc->p_mux) - circuitmux_clear_num_cells(orcirc->p_mux, circ); } } diff --git a/src/core/or/circuitmux.c b/src/core/or/circuitmux.c index 88f9ac7923..b2628bec3f 100644 --- a/src/core/or/circuitmux.c +++ b/src/core/or/circuitmux.c @@ -294,9 +294,6 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux, smartlist_t *detached_out) circuitmux_make_circuit_inactive(cmux, circ); } - /* Clear n_mux */ - circ->n_mux = NULL; - if (detached_out) smartlist_add(detached_out, circ); } else if (circ->magic == OR_CIRCUIT_MAGIC) { @@ -309,12 +306,6 @@ circuitmux_detach_all_circuits(circuitmux_t *cmux, smartlist_t *detached_out) circuitmux_make_circuit_inactive(cmux, circ); } - /* - * It has a sensible p_chan and direction == CELL_DIRECTION_IN, - * so clear p_mux. - */ - TO_OR_CIRCUIT(circ)->p_mux = NULL; - if (detached_out) smartlist_add(detached_out, circ); } else { @@ -836,18 +827,14 @@ circuitmux_attach_circuit,(circuitmux_t *cmux, circuit_t *circ, */ log_info(LD_CIRC, "Circuit %u on channel %"PRIu64 " was already attached to " - "cmux %p (trying to attach to %p)", + "(trying to attach to %p)", (unsigned)circ_id, (channel_id), - ((direction == CELL_DIRECTION_OUT) ? - circ->n_mux : TO_OR_CIRCUIT(circ)->p_mux), cmux); /* * The mux pointer on this circuit and the direction in result should * match; otherwise assert. */ - if (direction == CELL_DIRECTION_OUT) tor_assert(circ->n_mux == cmux); - else tor_assert(TO_OR_CIRCUIT(circ)->p_mux == cmux); tor_assert(hashent->muxinfo.direction == direction); /* @@ -872,13 +859,6 @@ circuitmux_attach_circuit,(circuitmux_t *cmux, circuit_t *circ, "Attaching circuit %u on channel %"PRIu64 " to cmux %p", (unsigned)circ_id, (channel_id), cmux); - /* - * Assert that the circuit doesn't already have a mux for this - * direction. - */ - if (direction == CELL_DIRECTION_OUT) tor_assert(circ->n_mux == NULL); - else tor_assert(TO_OR_CIRCUIT(circ)->p_mux == NULL); - /* Insert it in the map */ hashent = tor_malloc_zero(sizeof(*hashent)); hashent->chan_id = channel_id; @@ -902,10 +882,6 @@ circuitmux_attach_circuit,(circuitmux_t *cmux, circuit_t *circ, HT_INSERT(chanid_circid_muxinfo_map, cmux->chanid_circid_map, hashent); - /* Set the circuit's mux for this direction */ - if (direction == CELL_DIRECTION_OUT) circ->n_mux = cmux; - else TO_OR_CIRCUIT(circ)->p_mux = cmux; - /* Update counters */ ++(cmux->n_circuits); if (cell_count > 0) { @@ -993,9 +969,6 @@ circuitmux_detach_circuit,(circuitmux_t *cmux, circuit_t *circ)) /* Consistency check: the direction must match the direction searched */ tor_assert(last_searched_direction == hashent->muxinfo.direction); - /* Clear the circuit's mux for this direction */ - if (last_searched_direction == CELL_DIRECTION_OUT) circ->n_mux = NULL; - else TO_OR_CIRCUIT(circ)->p_mux = NULL; /* Now remove it from the map */ HT_REMOVE(chanid_circid_muxinfo_map, cmux->chanid_circid_map, hashent); diff --git a/src/core/or/circuitpadding.c b/src/core/or/circuitpadding.c index 0e3dc502ce..d30edf9db8 100644 --- a/src/core/or/circuitpadding.c +++ b/src/core/or/circuitpadding.c @@ -23,7 +23,7 @@ * As specified by prop#254, clients can negotiate padding with relays by using * PADDING_NEGOTIATE cells. After successful padding negotiation, padding * machines are assigned to the circuit in their mutable form as a - * circpad_machine_state_t. + * circpad_machine_runtime_t. * * Each state of a padding state machine can be either: * - A histogram that specifies inter-arrival padding delays. @@ -48,6 +48,7 @@ #include "core/or/circuitpadding.h" #include "core/or/circuitlist.h" #include "core/or/circuituse.h" +#include "core/mainloop/netstatus.h" #include "core/or/relay.h" #include "feature/stats/rephist.h" #include "feature/nodelist/networkstatus.h" @@ -187,11 +188,11 @@ circpad_circuit_free_all_machineinfos(circuit_t *circ) /** * Allocate a new mutable machineinfo structure. */ -STATIC circpad_machine_state_t * +STATIC circpad_machine_runtime_t * circpad_circuit_machineinfo_new(circuit_t *on_circ, int machine_index) { - circpad_machine_state_t *mi = - tor_malloc_zero(sizeof(circpad_machine_state_t)); + circpad_machine_runtime_t *mi = + tor_malloc_zero(sizeof(circpad_machine_runtime_t)); mi->machine_index = machine_index; mi->on_circ = on_circ; @@ -206,7 +207,7 @@ circpad_circuit_machineinfo_new(circuit_t *on_circ, int machine_index) * invalid state. */ STATIC const circpad_state_t * -circpad_machine_current_state(const circpad_machine_state_t *mi) +circpad_machine_current_state(const circpad_machine_runtime_t *mi) { const circpad_machine_spec_t *machine = CIRCPAD_GET_MACHINE(mi); @@ -224,25 +225,20 @@ circpad_machine_current_state(const circpad_machine_state_t *mi) } /** - * Calculate the lower bound of a histogram bin. The upper bound - * is obtained by calling this function with bin+1, and subtracting 1. + * Get the lower bound of a histogram bin. * - * The 0th bin has a special value -- it only represents start_usec. - * This is so we can specify a probability on 0-delay values. + * You can obtain the upper bound using histogram_get_bin_upper_bound(). * - * After bin 0, bins are exponentially spaced, so that each subsequent - * bin is twice as large as the previous. This is done so that higher - * time resolution is given to lower time values. - * - * The infinity bin is a the last bin in the array (histogram_len-1). - * It has a usec value of CIRCPAD_DELAY_INFINITE (UINT32_MAX). + * This function can also be called with 'bin' set to a value equal or greater + * than histogram_len in which case the infinity bin is chosen and + * CIRCPAD_DELAY_INFINITE is returned. */ STATIC circpad_delay_t -circpad_histogram_bin_to_usec(const circpad_machine_state_t *mi, +circpad_histogram_bin_to_usec(const circpad_machine_runtime_t *mi, circpad_hist_index_t bin) { const circpad_state_t *state = circpad_machine_current_state(mi); - circpad_delay_t start_usec; + circpad_delay_t rtt_add_usec = 0; /* Our state should have been checked to be non-null by the caller * (circpad_machine_remove_token()) */ @@ -250,37 +246,38 @@ circpad_histogram_bin_to_usec(const circpad_machine_state_t *mi, return CIRCPAD_DELAY_INFINITE; } - if (state->use_rtt_estimate) - start_usec = mi->rtt_estimate_usec+state->start_usec; - else - start_usec = state->start_usec; - - if (bin >= CIRCPAD_INFINITY_BIN(state)) + /* The infinity bin has an upper bound of infinity, so make sure we return + * that if they ask for it. */ + if (bin > CIRCPAD_INFINITY_BIN(mi)) { return CIRCPAD_DELAY_INFINITE; + } - if (bin == 0) - return start_usec; + /* If we are using an RTT estimate, consider it as well. */ + if (state->use_rtt_estimate) { + rtt_add_usec = mi->rtt_estimate_usec; + } - if (bin == 1) - return start_usec+1; + return state->histogram_edges[bin] + rtt_add_usec; +} - /* The bin widths double every index, so that we can have more resolution - * for lower time values in the histogram. */ - const circpad_time_t bin_width_exponent = - 1 << (CIRCPAD_INFINITY_BIN(state) - bin); - return (circpad_delay_t)MIN(start_usec + - state->range_usec/bin_width_exponent, - CIRCPAD_DELAY_INFINITE); +/** + * Like circpad_histogram_bin_to_usec() but return the upper bound of bin. + * (The upper bound is included in the bin.) + */ +STATIC circpad_delay_t +histogram_get_bin_upper_bound(const circpad_machine_runtime_t *mi, + circpad_hist_index_t bin) +{ + return circpad_histogram_bin_to_usec(mi, bin+1) - 1; } /** Return the midpoint of the histogram bin <b>bin_index</b>. */ static circpad_delay_t -circpad_get_histogram_bin_midpoint(const circpad_machine_state_t *mi, +circpad_get_histogram_bin_midpoint(const circpad_machine_runtime_t *mi, int bin_index) { circpad_delay_t left_bound = circpad_histogram_bin_to_usec(mi, bin_index); - circpad_delay_t right_bound = - circpad_histogram_bin_to_usec(mi, bin_index+1)-1; + circpad_delay_t right_bound = histogram_get_bin_upper_bound(mi, bin_index); return left_bound + (right_bound - left_bound)/2; } @@ -289,19 +286,17 @@ circpad_get_histogram_bin_midpoint(const circpad_machine_state_t *mi, * Return the bin that contains the usec argument. * "Contains" is defined as us in [lower, upper). * - * This function will never return the infinity bin (histogram_len-1), - * in order to simplify the rest of the code. - * - * This means that technically the last bin (histogram_len-2) - * has range [start_usec+range_usec, CIRCPAD_DELAY_INFINITE]. + * This function will never return the infinity bin (histogram_len-1), in order + * to simplify the rest of the code, so if a usec is provided that falls above + * the highest non-infinity bin, that bin index will be returned. */ STATIC circpad_hist_index_t -circpad_histogram_usec_to_bin(const circpad_machine_state_t *mi, +circpad_histogram_usec_to_bin(const circpad_machine_runtime_t *mi, circpad_delay_t usec) { const circpad_state_t *state = circpad_machine_current_state(mi); - circpad_delay_t start_usec; - int32_t bin; /* Larger than return type to properly clamp overflow */ + circpad_delay_t rtt_add_usec = 0; + circpad_hist_index_t bin; /* Our state should have been checked to be non-null by the caller * (circpad_machine_remove_token()) */ @@ -309,34 +304,25 @@ circpad_histogram_usec_to_bin(const circpad_machine_state_t *mi, return 0; } - if (state->use_rtt_estimate) - start_usec = mi->rtt_estimate_usec+state->start_usec; - else - start_usec = state->start_usec; - - /* The first bin (#0) has zero width and starts (and ends) at start_usec. */ - if (usec <= start_usec) - return 0; - - if (usec == start_usec+1) - return 1; + /* If we are using an RTT estimate, consider it as well. */ + if (state->use_rtt_estimate) { + rtt_add_usec = mi->rtt_estimate_usec; + } - const circpad_time_t histogram_range_usec = state->range_usec; - /* We need to find the bin corresponding to our position in the range. - * Since bins are exponentially spaced in powers of two, we need to - * take the log2 of our position in histogram_range_usec. However, - * since tor_log2() returns the floor(log2(u64)), we have to adjust - * it to behave like ceil(log2(u64)). This is verified in our tests - * to properly invert the operation done in - * circpad_histogram_bin_to_usec(). */ - bin = CIRCPAD_INFINITY_BIN(state) - - tor_log2(2*histogram_range_usec/(usec-start_usec+1)); + /* Walk through the bins and check the upper bound of each bin, if 'usec' is + * less-or-equal to that, return that bin. If rtt_estimate is enabled then + * add that to the upper bound of each bin. + * + * We don't want to return the infinity bin here, so don't go there. */ + for (bin = 0 ; bin < CIRCPAD_INFINITY_BIN(state) ; bin++) { + if (usec <= histogram_get_bin_upper_bound(mi, bin) + rtt_add_usec) { + return bin; + } + } - /* Clamp the return value to account for timevals before the start - * of bin 0, or after the last bin. Don't return the infinity bin - * index. */ - bin = MIN(MAX(bin, 1), CIRCPAD_INFINITY_BIN(state)-1); - return bin; + /* We don't want to return the infinity bin here, so if we still didn't find + * the right bin, return the highest non-infinity bin */ + return CIRCPAD_INFINITY_BIN(state)-1; } /** @@ -345,7 +331,7 @@ circpad_histogram_usec_to_bin(const circpad_machine_state_t *mi, * Called after a state transition, or if the bins are empty. */ STATIC void -circpad_machine_setup_tokens(circpad_machine_state_t *mi) +circpad_machine_setup_tokens(circpad_machine_runtime_t *mi) { const circpad_state_t *state = circpad_machine_current_state(mi); @@ -377,7 +363,7 @@ circpad_machine_setup_tokens(circpad_machine_state_t *mi) * Choose a length for this state (in cells), if specified. */ static void -circpad_choose_state_length(circpad_machine_state_t *mi) +circpad_choose_state_length(circpad_machine_runtime_t *mi) { const circpad_state_t *state = circpad_machine_current_state(mi); double length; @@ -398,20 +384,25 @@ circpad_choose_state_length(circpad_machine_state_t *mi) /** * Sample a value from our iat_dist, and clamp it safely * to circpad_delay_t. + * + * Before returning, add <b>delay_shift</b> (can be zero) to the sampled value. */ static circpad_delay_t circpad_distribution_sample_iat_delay(const circpad_state_t *state, - circpad_delay_t start_usec) + circpad_delay_t delay_shift) { double val = circpad_distribution_sample(state->iat_dist); /* These comparisons are safe, because the output is in the range * [0, 2**32), and double has a precision of 53 bits. */ + /* We want a positive sample value */ val = MAX(0, val); - val = MIN(val, state->range_usec); + /* Respect the maximum sample setting */ + val = MIN(val, state->dist_max_sample_usec); - /* This addition is exact: val is at most 2**32-1, start_usec - * is at most 2**32-1, and doubles have a precision of 53 bits. */ - val += start_usec; + /* Now apply the shift: + * This addition is exact: val is at most 2**32-1, delay_shift is at most + * 2**32-1, and doubles have a precision of 53 bits. */ + val += delay_shift; /* Clamp the distribution at infinite delay val */ return (circpad_delay_t)MIN(tor_llround(val), CIRCPAD_DELAY_INFINITE); @@ -425,13 +416,12 @@ circpad_distribution_sample_iat_delay(const circpad_state_t *state, * that bin's [start,end) time range. */ STATIC circpad_delay_t -circpad_machine_sample_delay(circpad_machine_state_t *mi) +circpad_machine_sample_delay(circpad_machine_runtime_t *mi) { const circpad_state_t *state = circpad_machine_current_state(mi); const circpad_hist_token_t *histogram = NULL; circpad_hist_index_t curr_bin = 0; circpad_delay_t bin_start, bin_end; - circpad_delay_t start_usec; /* These three must all be larger than circpad_hist_token_t, because * we sum several circpad_hist_token_t values across the histogram */ uint64_t curr_weight = 0; @@ -440,14 +430,12 @@ circpad_machine_sample_delay(circpad_machine_state_t *mi) tor_assert(state); - if (state->use_rtt_estimate) - start_usec = mi->rtt_estimate_usec+state->start_usec; - else - start_usec = state->start_usec; - if (state->iat_dist.type != CIRCPAD_DIST_NONE) { /* Sample from a fixed IAT distribution and return */ - return circpad_distribution_sample_iat_delay(state, start_usec); + circpad_delay_t iat_delay_shift = state->use_rtt_estimate ? + mi->rtt_estimate_usec + state->dist_added_shift_usec : + state->dist_added_shift_usec; + return circpad_distribution_sample_iat_delay(state, iat_delay_shift); } else if (state->token_removal != CIRCPAD_TOKEN_REMOVAL_NONE) { /* We have a mutable histogram. Do basic sanity check and apply: */ if (BUG(!mi->histogram) || @@ -464,7 +452,8 @@ circpad_machine_sample_delay(circpad_machine_state_t *mi) histogram_total_tokens = state->histogram_total_tokens; } - bin_choice = crypto_rand_uint64(histogram_total_tokens); + bin_choice = crypto_fast_rng_get_uint64(get_thread_fast_rng(), + histogram_total_tokens); /* Skip all the initial zero bins */ while (!histogram[curr_bin]) { @@ -512,16 +501,13 @@ circpad_machine_sample_delay(circpad_machine_state_t *mi) * function below samples from [bin_start, bin_end) */ bin_end = circpad_histogram_bin_to_usec(mi, curr_bin+1); - /* Truncate the high bin in case it's the infinity bin: - * Don't actually schedule an "infinite"-1 delay */ - bin_end = MIN(bin_end, start_usec+state->range_usec); - - // Sample uniformly between histogram[i] to histogram[i+1]-1, - // but no need to sample if they are the same timeval (aka bin 0 or bin 1). - if (bin_end <= bin_start+1) + /* Bin edges are monotonically increasing so this is a bug. Handle it. */ + if (BUG(bin_start >= bin_end)) { return bin_start; - else - return (circpad_delay_t)crypto_rand_uint64_range(bin_start, bin_end); + } + + return (circpad_delay_t)crypto_fast_rng_uint64_range(get_thread_fast_rng(), + bin_start, bin_end); } /** @@ -618,7 +604,7 @@ circpad_distribution_sample(circpad_distribution_t dist) * greater than the target, and that has tokens remaining. */ static circpad_hist_index_t -circpad_machine_first_higher_index(const circpad_machine_state_t *mi, +circpad_machine_first_higher_index(const circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec) { circpad_hist_index_t bin = circpad_histogram_usec_to_bin(mi, @@ -627,7 +613,7 @@ circpad_machine_first_higher_index(const circpad_machine_state_t *mi, /* Don't remove from the infinity bin */ for (; bin < CIRCPAD_INFINITY_BIN(mi); bin++) { if (mi->histogram[bin] && - circpad_histogram_bin_to_usec(mi, bin+1) > target_bin_usec) { + histogram_get_bin_upper_bound(mi, bin) >= target_bin_usec) { return bin; } } @@ -640,7 +626,7 @@ circpad_machine_first_higher_index(const circpad_machine_state_t *mi, * <b>target_bin_usec</b>, and that still has tokens remaining. */ static circpad_hist_index_t -circpad_machine_first_lower_index(const circpad_machine_state_t *mi, +circpad_machine_first_lower_index(const circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec) { circpad_hist_index_t bin = circpad_histogram_usec_to_bin(mi, @@ -661,7 +647,7 @@ circpad_machine_first_lower_index(const circpad_machine_state_t *mi, * greater than the target. */ STATIC void -circpad_machine_remove_higher_token(circpad_machine_state_t *mi, +circpad_machine_remove_higher_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec) { /* We need to remove the token from the first bin @@ -682,7 +668,7 @@ circpad_machine_remove_higher_token(circpad_machine_state_t *mi, * lower than the target. */ STATIC void -circpad_machine_remove_lower_token(circpad_machine_state_t *mi, +circpad_machine_remove_lower_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec) { circpad_hist_index_t bin = circpad_machine_first_lower_index(mi, @@ -711,7 +697,7 @@ circpad_machine_remove_lower_token(circpad_machine_state_t *mi, * If it is false, use bin index distance only. */ STATIC void -circpad_machine_remove_closest_token(circpad_machine_state_t *mi, +circpad_machine_remove_closest_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec, bool use_usec) { @@ -793,7 +779,7 @@ circpad_machine_remove_closest_token(circpad_machine_state_t *mi, * If it is empty, do nothing. */ static void -circpad_machine_remove_exact(circpad_machine_state_t *mi, +circpad_machine_remove_exact(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_usec) { circpad_hist_index_t bin = circpad_histogram_usec_to_bin(mi, @@ -810,7 +796,7 @@ circpad_machine_remove_exact(circpad_machine_state_t *mi, * otherwise returns 0. */ static circpad_decision_t -check_machine_token_supply(circpad_machine_state_t *mi) +check_machine_token_supply(circpad_machine_runtime_t *mi) { uint32_t histogram_total_tokens = 0; @@ -850,7 +836,7 @@ check_machine_token_supply(circpad_machine_state_t *mi) * Returns 1 if we transition states, 0 otherwise. */ STATIC circpad_decision_t -circpad_machine_remove_token(circpad_machine_state_t *mi) +circpad_machine_remove_token(circpad_machine_runtime_t *mi) { const circpad_state_t *state = NULL; circpad_time_t current_time; @@ -977,14 +963,14 @@ circpad_send_command_to_hop,(origin_circuit_t *circ, uint8_t hopnum, * CIRCPAD_STATE_CHANGED. Otherwise return CIRCPAD_STATE_UNCHANGED. */ circpad_decision_t -circpad_send_padding_cell_for_callback(circpad_machine_state_t *mi) +circpad_send_padding_cell_for_callback(circpad_machine_runtime_t *mi) { circuit_t *circ = mi->on_circ; int machine_idx = mi->machine_index; mi->padding_scheduled_at_usec = 0; circpad_statenum_t state = mi->current_state; - // Make sure circuit didn't close on us + /* Make sure circuit didn't close on us */ if (mi->on_circ->marked_for_close) { log_fn(LOG_INFO,LD_CIRC, "Padding callback on a circuit marked for close. Ignoring."); @@ -1063,7 +1049,7 @@ circpad_send_padding_cell_for_callback(circpad_machine_state_t *mi) /** * Tor-timer compatible callback that tells us to send a padding cell. * - * Timers are associated with circpad_machine_state_t's. When the machineinfo + * Timers are associated with circpad_machine_runtime_t's. When the machineinfo * is freed on a circuit, the timers are cancelled. Since the lifetime * of machineinfo is always longer than the timers, handles are not * needed. @@ -1072,7 +1058,7 @@ static void circpad_send_padding_callback(tor_timer_t *timer, void *args, const struct monotime_t *time) { - circpad_machine_state_t *mi = ((circpad_machine_state_t*)args); + circpad_machine_runtime_t *mi = ((circpad_machine_runtime_t*)args); (void)timer; (void)time; if (mi && mi->on_circ) { @@ -1122,7 +1108,7 @@ circpad_new_consensus_params(const networkstatus_t *ns) * Returns 1 if limits are set and we've hit them. Otherwise returns 0. */ STATIC bool -circpad_machine_reached_padding_limit(circpad_machine_state_t *mi) +circpad_machine_reached_padding_limit(circpad_machine_runtime_t *mi) { const circpad_machine_spec_t *machine = CIRCPAD_GET_MACHINE(mi); @@ -1170,12 +1156,18 @@ circpad_machine_reached_padding_limit(circpad_machine_state_t *mi) * 0 otherwise. */ MOCK_IMPL(circpad_decision_t, -circpad_machine_schedule_padding,(circpad_machine_state_t *mi)) +circpad_machine_schedule_padding,(circpad_machine_runtime_t *mi)) { circpad_delay_t in_usec = 0; struct timeval timeout; tor_assert(mi); + /* Don't schedule padding if we are currently in dormant mode. */ + if (!is_participating_on_network()) { + log_info(LD_CIRC, "Not scheduling padding because we are dormant."); + return CIRCPAD_STATE_UNCHANGED; + } + // Don't pad in end (but also don't cancel any previously // scheduled padding either). if (mi->current_state == CIRCPAD_STATE_END) { @@ -1260,7 +1252,7 @@ circpad_machine_schedule_padding,(circpad_machine_state_t *mi)) * not access it. */ static void -circpad_machine_spec_transitioned_to_end(circpad_machine_state_t *mi) +circpad_machine_spec_transitioned_to_end(circpad_machine_runtime_t *mi) { const circpad_machine_spec_t *machine = CIRCPAD_GET_MACHINE(mi); @@ -1310,7 +1302,7 @@ circpad_machine_spec_transitioned_to_end(circpad_machine_state_t *mi) * Returns 1 if we transition states, 0 otherwise. */ MOCK_IMPL(circpad_decision_t, -circpad_machine_spec_transition,(circpad_machine_state_t *mi, +circpad_machine_spec_transition,(circpad_machine_runtime_t *mi, circpad_event_t event)) { const circpad_state_t *state = @@ -1391,7 +1383,7 @@ circpad_machine_spec_transition,(circpad_machine_state_t *mi, */ static void circpad_estimate_circ_rtt_on_received(circuit_t *circ, - circpad_machine_state_t *mi) + circpad_machine_runtime_t *mi) { /* Origin circuits don't estimate RTT. They could do it easily enough, * but they have no reason to use it in any delay calculations. */ @@ -1438,7 +1430,7 @@ circpad_estimate_circ_rtt_on_received(circuit_t *circ, */ static void circpad_estimate_circ_rtt_on_send(circuit_t *circ, - circpad_machine_state_t *mi) + circpad_machine_runtime_t *mi) { /* Origin circuits don't estimate RTT. They could do it easily enough, * but they have no reason to use it in any delay calculations. */ @@ -1584,7 +1576,7 @@ circpad_cell_event_padding_received(circuit_t *on_circ) * Return 1 if we decide to transition, 0 otherwise. */ circpad_decision_t -circpad_internal_event_infinity(circpad_machine_state_t *mi) +circpad_internal_event_infinity(circpad_machine_runtime_t *mi) { return circpad_machine_spec_transition(mi, CIRCPAD_EVENT_INFINITY); } @@ -1598,7 +1590,7 @@ circpad_internal_event_infinity(circpad_machine_state_t *mi) * Return 1 if we decide to transition, 0 otherwise. */ circpad_decision_t -circpad_internal_event_bins_empty(circpad_machine_state_t *mi) +circpad_internal_event_bins_empty(circpad_machine_runtime_t *mi) { if (circpad_machine_spec_transition(mi, CIRCPAD_EVENT_BINS_EMPTY) == CIRCPAD_STATE_CHANGED) { @@ -1617,7 +1609,7 @@ circpad_internal_event_bins_empty(circpad_machine_state_t *mi) * Return 1 if we decide to transition, 0 otherwise. */ circpad_decision_t -circpad_internal_event_state_length_up(circpad_machine_state_t *mi) +circpad_internal_event_state_length_up(circpad_machine_runtime_t *mi) { return circpad_machine_spec_transition(mi, CIRCPAD_EVENT_LENGTH_COUNT); } @@ -2084,16 +2076,92 @@ circpad_setup_machine_on_circ(circuit_t *on_circ, on_circ->padding_machine[machine->machine_index] = machine; } -/* These padding machines are only used for tests pending #28634. */ #ifdef TOR_UNIT_TESTS +/** Validate a single state of a padding machine */ +static bool +padding_machine_state_is_valid(const circpad_state_t *state) +{ + int b; + uint32_t tokens_count = 0; + circpad_delay_t prev_bin_edge = 0; + + /* We only validate histograms */ + if (!state->histogram_len) { + return true; + } + + /* We need at least two bins in a histogram */ + if (state->histogram_len < 2) { + log_warn(LD_GENERAL, "You can't have a histogram with less than 2 bins"); + return false; + } + + /* For each machine state, if it's a histogram, make sure all the + * histogram edges are well defined (i.e. are strictly monotonic). */ + for (b = 0 ; b < state->histogram_len ; b++) { + /* Check that histogram edges are strictly increasing. Ignore the first + * edge since it can be zero. */ + if (prev_bin_edge >= state->histogram_edges[b] && b > 0) { + log_warn(LD_GENERAL, "Histogram edges are not increasing [%u/%u]", + prev_bin_edge, state->histogram_edges[b]); + return false; + } + + prev_bin_edge = state->histogram_edges[b]; + + /* Also count the number of tokens as we go through the histogram states */ + tokens_count += state->histogram[b]; + } + /* Verify that the total number of tokens is correct */ + if (tokens_count != state->histogram_total_tokens) { + log_warn(LD_GENERAL, "Histogram token count is wrong [%u/%u]", + tokens_count, state->histogram_total_tokens); + return false; + } + + return true; +} + +/** Basic validation of padding machine */ +static bool +padding_machine_is_valid(const circpad_machine_spec_t *machine) +{ + int i; + + /* Validate the histograms of the padding machine */ + for (i = 0 ; i < machine->num_states ; i++) { + if (!padding_machine_state_is_valid(&machine->states[i])) { + return false; + } + } + + return true; +} + +/* Validate and register <b>machine</b> into <b>machine_list</b>. If + * <b>machine_list</b> is NULL, then just validate. */ +STATIC void +register_padding_machine(circpad_machine_spec_t *machine, + smartlist_t *machine_list) +{ + if (!padding_machine_is_valid(machine)) { + log_warn(LD_GENERAL, "Machine #%u is invalid. Ignoring.", + machine->machine_num); + return; + } + + if (machine_list) { + smartlist_add(machine_list, machine); + } +} + +/* These padding machines are only used for tests pending #28634. */ static void circpad_circ_client_machine_init(void) { circpad_machine_spec_t *circ_client_machine = tor_malloc_zero(sizeof(circpad_machine_spec_t)); - // XXX: Better conditions for merge.. Or disable this machine in - // merge? circ_client_machine->conditions.min_hops = 2; circ_client_machine->conditions.state_mask = CIRCPAD_CIRC_BUILDING|CIRCPAD_CIRC_OPENED|CIRCPAD_CIRC_HAS_RELAY_EARLY; @@ -2125,19 +2193,20 @@ circpad_circ_client_machine_init(void) circ_client_machine->states[CIRCPAD_STATE_BURST].token_removal = CIRCPAD_TOKEN_REMOVAL_CLOSEST; - // FIXME: Tune this histogram circ_client_machine->states[CIRCPAD_STATE_BURST].histogram_len = 2; - circ_client_machine->states[CIRCPAD_STATE_BURST].start_usec = 500; - circ_client_machine->states[CIRCPAD_STATE_BURST].range_usec = 1000000; + circ_client_machine->states[CIRCPAD_STATE_BURST].histogram_edges[0]= 500; + circ_client_machine->states[CIRCPAD_STATE_BURST].histogram_edges[1]= 1000000; + /* We have 5 tokens in the histogram, which means that all circuits will look * like they have 7 hops (since we start this machine after the second hop, * and tokens are decremented for any valid hops, and fake extends are * used after that -- 2+5==7). */ circ_client_machine->states[CIRCPAD_STATE_BURST].histogram[0] = 5; + circ_client_machine->states[CIRCPAD_STATE_BURST].histogram_total_tokens = 5; circ_client_machine->machine_num = smartlist_len(origin_padding_machines); - smartlist_add(origin_padding_machines, circ_client_machine); + register_padding_machine(circ_client_machine, origin_padding_machines); } static void @@ -2184,8 +2253,9 @@ circpad_circ_responder_machine_init(void) circ_responder_machine->states[CIRCPAD_STATE_BURST].use_rtt_estimate = 1; /* The histogram is 2 bins: an empty one, and infinity */ circ_responder_machine->states[CIRCPAD_STATE_BURST].histogram_len = 2; - circ_responder_machine->states[CIRCPAD_STATE_BURST].start_usec = 5000; - circ_responder_machine->states[CIRCPAD_STATE_BURST].range_usec = 1000000; + circ_responder_machine->states[CIRCPAD_STATE_BURST].histogram_edges[0]= 500; + circ_responder_machine->states[CIRCPAD_STATE_BURST].histogram_edges[1] = + 1000000; /* During burst state we wait forever for padding to arrive. We are waiting for a padding cell from the client to come in, so that we @@ -2216,8 +2286,14 @@ circpad_circ_responder_machine_init(void) before you send a padding response */ circ_responder_machine->states[CIRCPAD_STATE_GAP].use_rtt_estimate = 1; circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_len = 6; - circ_responder_machine->states[CIRCPAD_STATE_GAP].start_usec = 5000; - circ_responder_machine->states[CIRCPAD_STATE_GAP].range_usec = 1000000; + /* Specify histogram bins */ + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[0]= 500; + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[1]= 1000; + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[2]= 2000; + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[3]= 4000; + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[4]= 8000; + circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_edges[5]= 16000; + /* Specify histogram tokens */ circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram[0] = 0; circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram[1] = 1; circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram[2] = 2; @@ -2225,11 +2301,12 @@ circpad_circ_responder_machine_init(void) circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram[4] = 1; /* Total number of tokens */ circ_responder_machine->states[CIRCPAD_STATE_GAP].histogram_total_tokens = 6; + circ_responder_machine->states[CIRCPAD_STATE_GAP].token_removal = CIRCPAD_TOKEN_REMOVAL_CLOSEST_USEC; circ_responder_machine->machine_num = smartlist_len(relay_padding_machines); - smartlist_add(relay_padding_machines, circ_responder_machine); + register_padding_machine(circ_responder_machine, relay_padding_machines); } #endif diff --git a/src/core/or/circuitpadding.h b/src/core/or/circuitpadding.h index 92fd4fc2d5..bc2522c210 100644 --- a/src/core/or/circuitpadding.h +++ b/src/core/or/circuitpadding.h @@ -88,7 +88,7 @@ typedef uint32_t circpad_delay_t; /** * Macro to clarify when we're checking the infinity bin. * - * Works with either circpad_state_t or circpad_machine_state_t + * Works with either circpad_state_t or circpad_machine_runtime_t */ #define CIRCPAD_INFINITY_BIN(mi) ((mi)->histogram_len-1) @@ -198,14 +198,23 @@ typedef enum { * These can be used instead of histograms for the inter-packet * timing distribution, or to specify a distribution on the number * of cells that can be sent while in a specific state of the state - * machine. */ + * machine. + * + * Each distribution takes up to two parameters which are described below. */ typedef enum { + /* No probability distribution is used */ CIRCPAD_DIST_NONE = 0, + /* Uniform distribution: param1 is lower bound and param2 is upper bound */ CIRCPAD_DIST_UNIFORM = 1, + /* Logistic distribution: param1 is Mu, param2 is sigma. */ CIRCPAD_DIST_LOGISTIC = 2, + /* Log-logistic distribution: param1 is Alpha, param2 is 1.0/Beta */ CIRCPAD_DIST_LOG_LOGISTIC = 3, + /* Geometric distribution: param1 is 'p' (success probability) */ CIRCPAD_DIST_GEOMETRIC = 4, + /* Weibull distribution: param1 is k, param2 is Lambda */ CIRCPAD_DIST_WEIBULL = 5, + /* Generalized Pareto distribution: param1 is sigma, param2 is xi */ CIRCPAD_DIST_PARETO = 6 } circpad_distribution_type_t; @@ -228,19 +237,24 @@ typedef uint16_t circpad_statenum_t; #define CIRCPAD_STATENUM_MAX (UINT16_MAX) /** A histogram is used to sample padding delays given a machine state. This - * constant defines the maximum histogram width (i.e. the max number of bins) + * constant defines the maximum histogram width (i.e. the max number of bins). + * + * The current limit is arbitrary and could be raised if there is a need, + * however too many bins will be hard to serialize in the future. + * + * Memory concerns are not so great here since the corresponding histogram and + * histogram_edges arrays are global and not per-circuit. * - * Each histogram bin is twice as large as the previous. Two exceptions: The - * first bin has zero width (which means that minimum delay is applied to the - * next padding cell), and the last bin (infinity bin) has infinite width - * (which means that the next padding cell will be delayed infinitely). */ -#define CIRCPAD_MAX_HISTOGRAM_LEN (sizeof(circpad_delay_t)*8 + 1) + * If we ever upgrade this to a value that can't be represented by 8-bits we + * also need to upgrade circpad_hist_index_t. + */ +#define CIRCPAD_MAX_HISTOGRAM_LEN (100) /** * A state of a padding state machine. The information here are immutable and * represent the initial form of the state; it does not get updated as things * happen. The mutable information that gets updated in runtime are carried in - * a circpad_machine_state_t. + * a circpad_machine_runtime_t. * * This struct describes the histograms and parameters of a single * state in the adaptive padding machine. Instances of this struct @@ -248,38 +262,60 @@ typedef uint16_t circpad_statenum_t; * or the consensus. */ typedef struct circpad_state_t { - /** If a histogram is used for this state, this specifies the number of bins - * of this histogram. Histograms must have at least 2 bins. + /** + * If a histogram is used for this state, this specifies the number of bins + * of this histogram. Histograms must have at least 2 bins. + * + * In particular, the following histogram: * - * If a delay probability distribution is used for this state, this is set - * to 0. */ + * Tokens + * + + * 10 | +----+ + * 9 | | | +---------+ + * 8 | | | | | + * 7 | | | +-----+ | + * 6 +----+ Bin+-----+ | +---------------+ + * 5 | | #1 | | | | | + * | Bin| | Bin | Bin | Bin #4 | Bin #5 | + * | #0 | | #2 | #3 | | (infinity bin)| + * | | | | | | | + * | | | | | | | + * 0 +----+----+-----+-----+---------+---------------+ + * 0 100 200 350 500 1000 ∞ microseconds + * + * would be specified the following way: + * histogram_len = 6; + * histogram[] = { 6, 10, 6, 7, 9, 6 } + * histogram_edges[] = { 0, 100, 200, 350, 500, 1000 } + * + * The final bin is called the "infinity bin" and if it's chosen we don't + * schedule any padding. The infinity bin is strange because its lower edge + * is the max value of possible non-infinite delay allowed by this histogram, + * and its upper edge is CIRCPAD_DELAY_INFINITE. You can tell if the infinity + * bin is chosen by inspecting its bin index or inspecting its upper edge. + * + * If a delay probability distribution is used for this state, this is set + * to 0. */ circpad_hist_index_t histogram_len; /** The histogram itself: an array of uint16s of tokens, whose - * widths are exponentially spaced, in microseconds */ + * widths are exponentially spaced, in microseconds. + * + * This array must have histogram_len elements that are strictly + * monotonically increasing. */ circpad_hist_token_t histogram[CIRCPAD_MAX_HISTOGRAM_LEN]; + /* The histogram bin edges in usec. + * + * Each element of this array specifies the left edge of the corresponding + * bin. The rightmost edge is always infinity and is not specified in this + * array. + * + * This array must have histogram_len elements. */ + circpad_delay_t histogram_edges[CIRCPAD_MAX_HISTOGRAM_LEN+1]; /** Total number of tokens in this histogram. This is a constant and is *not* * decremented every time we spend a token. It's used for initializing and * refilling the histogram. */ uint32_t histogram_total_tokens; - /** Minimum padding delay of this state in microseconds. - * - * If histograms are used, this is the left (and right) bound of the first - * bin (since it has zero width). - * - * If a delay probability distribution is used, this represents the minimum - * delay we can sample from the distribution. - */ - circpad_delay_t start_usec; - - /** If histograms are used, this is the width of the whole histogram in - * microseconds, and it's used to calculate individual bin width. - * - * If a delay probability distribution is used, this is used as the max - * delay we can sample from the distribution. - */ - circpad_delay_t range_usec; - /** * Represents a delay probability distribution (aka IAT distribution). It's a * parametrized way of encoding inter-packet delay information in @@ -292,6 +328,16 @@ typedef struct circpad_state_t { * results of sampling from this distribution (range_sec is used as a max). */ circpad_distribution_t iat_dist; + /* If a delay probability distribution is used, this is used as the max + * value we can sample from the distribution. However, RTT measurements and + * dist_added_shift gets applied on top of this value to derive the final + * padding delay. */ + circpad_delay_t dist_max_sample_usec; + /* If a delay probability distribution is used and this is set, we will add + * this value on top of the value sampled from the IAT distribution to + * derive the final padding delay (We also add the RTT measurement if it's + * enabled.). */ + circpad_delay_t dist_added_shift_usec; /** * The length dist is a parameterized way of encoding how long this @@ -430,7 +476,7 @@ typedef struct circpad_state_t { * * XXX: Play with layout to minimize space on x64 Linux (most common relay). */ -typedef struct circpad_machine_state_t { +typedef struct circpad_machine_runtime_t { /** The callback pointer for the padding callbacks. * * These timers stick around the machineinfo until the machineinfo's circuit @@ -514,7 +560,7 @@ typedef struct circpad_machine_state_t { * CIRCPAD_MAX_MACHINES define). */ unsigned machine_index : 1; -} circpad_machine_state_t; +} circpad_machine_runtime_t; /** Helper macro to get an actual state machine from a machineinfo */ #define CIRCPAD_GET_MACHINE(machineinfo) \ @@ -592,11 +638,11 @@ void circpad_cell_event_padding_received(struct circuit_t *on_circ); /** Internal events are events the machines send to themselves */ circpad_decision_t -circpad_internal_event_infinity(circpad_machine_state_t *mi); +circpad_internal_event_infinity(circpad_machine_runtime_t *mi); circpad_decision_t -circpad_internal_event_bins_empty(circpad_machine_state_t *); +circpad_internal_event_bins_empty(circpad_machine_runtime_t *); circpad_decision_t circpad_internal_event_state_length_up( - circpad_machine_state_t *); + circpad_machine_runtime_t *); /** Machine creation events are events that cause us to set up or * tear down padding state machines. */ @@ -639,56 +685,64 @@ bool circpad_padding_negotiated(struct circuit_t *circ, uint8_t response); MOCK_DECL(circpad_decision_t, -circpad_machine_schedule_padding,(circpad_machine_state_t *)); +circpad_machine_schedule_padding,(circpad_machine_runtime_t *)); MOCK_DECL(circpad_decision_t, -circpad_machine_spec_transition, (circpad_machine_state_t *mi, +circpad_machine_spec_transition, (circpad_machine_runtime_t *mi, circpad_event_t event)); circpad_decision_t circpad_send_padding_cell_for_callback( - circpad_machine_state_t *mi); + circpad_machine_runtime_t *mi); #ifdef CIRCUITPADDING_PRIVATE STATIC circpad_delay_t -circpad_machine_sample_delay(circpad_machine_state_t *mi); +circpad_machine_sample_delay(circpad_machine_runtime_t *mi); STATIC bool -circpad_machine_reached_padding_limit(circpad_machine_state_t *mi); +circpad_machine_reached_padding_limit(circpad_machine_runtime_t *mi); STATIC -circpad_decision_t circpad_machine_remove_token(circpad_machine_state_t *mi); +circpad_decision_t circpad_machine_remove_token(circpad_machine_runtime_t *mi); STATIC circpad_delay_t -circpad_histogram_bin_to_usec(const circpad_machine_state_t *mi, +circpad_histogram_bin_to_usec(const circpad_machine_runtime_t *mi, circpad_hist_index_t bin); STATIC const circpad_state_t * -circpad_machine_current_state(const circpad_machine_state_t *mi); +circpad_machine_current_state(const circpad_machine_runtime_t *mi); STATIC circpad_hist_index_t circpad_histogram_usec_to_bin( - const circpad_machine_state_t *mi, + const circpad_machine_runtime_t *mi, circpad_delay_t us); -STATIC circpad_machine_state_t *circpad_circuit_machineinfo_new( +STATIC circpad_machine_runtime_t *circpad_circuit_machineinfo_new( struct circuit_t *on_circ, int machine_index); -STATIC void circpad_machine_remove_higher_token(circpad_machine_state_t *mi, +STATIC void circpad_machine_remove_higher_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_us); -STATIC void circpad_machine_remove_lower_token(circpad_machine_state_t *mi, +STATIC void circpad_machine_remove_lower_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_us); -STATIC void circpad_machine_remove_closest_token(circpad_machine_state_t *mi, +STATIC void circpad_machine_remove_closest_token(circpad_machine_runtime_t *mi, circpad_delay_t target_bin_us, bool use_usec); -STATIC void circpad_machine_setup_tokens(circpad_machine_state_t *mi); +STATIC void circpad_machine_setup_tokens(circpad_machine_runtime_t *mi); MOCK_DECL(STATIC signed_error_t, circpad_send_command_to_hop,(struct origin_circuit_t *circ, uint8_t hopnum, uint8_t relay_command, const uint8_t *payload, ssize_t payload_len)); +STATIC circpad_delay_t +histogram_get_bin_upper_bound(const circpad_machine_runtime_t *mi, + circpad_hist_index_t bin); + #ifdef TOR_UNIT_TESTS extern smartlist_t *origin_padding_machines; extern smartlist_t *relay_padding_machines; + +STATIC void +register_padding_machine(circpad_machine_spec_t *machine, + smartlist_t *machine_list); #endif #endif diff --git a/src/core/or/circuitstats.c b/src/core/or/circuitstats.c index c6ea2fff97..e5a3bac30b 100644 --- a/src/core/or/circuitstats.c +++ b/src/core/or/circuitstats.c @@ -30,7 +30,7 @@ #include "core/or/circuitstats.h" #include "app/config/config.h" #include "app/config/confparse.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "lib/crypt_ops/crypto_rand.h" #include "core/mainloop/mainloop.h" #include "feature/nodelist/networkstatus.h" diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index fd782c0cd1..2f27cf996f 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -42,7 +42,7 @@ #include "feature/client/bridges.h" #include "feature/client/circpathbias.h" #include "feature/client/entrynodes.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dircommon/directory.h" #include "feature/hs/hs_circuit.h" #include "feature/hs/hs_client.h" @@ -3121,7 +3121,9 @@ circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len) { if (!circ) return; - tor_assert_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE); + tor_assertf_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE, + "Wrong relay_body_len: %d (should be at most %d)", + relay_body_len, RELAY_PAYLOAD_SIZE); circ->n_delivered_written_circ_bw = tor_add_u32_nowrap(circ->n_delivered_written_circ_bw, relay_body_len); diff --git a/src/core/or/command.c b/src/core/or/command.c index 5fb6640c22..77e5447ce3 100644 --- a/src/core/or/command.c +++ b/src/core/or/command.c @@ -49,7 +49,7 @@ #include "core/or/dos.h" #include "core/or/onion.h" #include "core/or/relay.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/hibernate/hibernate.h" #include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index cc240bdc98..071a8c91ed 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -78,7 +78,7 @@ #include "feature/client/addressmap.h" #include "feature/client/circpathbias.h" #include "feature/client/dnsserv.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/dircache/dirserv.h" #include "feature/dircommon/directory.h" #include "feature/hibernate/hibernate.h" diff --git a/src/core/or/connection_or.c b/src/core/or/connection_or.c index debf482cb3..e9b4b21955 100644 --- a/src/core/or/connection_or.c +++ b/src/core/or/connection_or.c @@ -39,7 +39,7 @@ #include "app/config/config.h" #include "core/mainloop/connection.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/crypt_ops/crypto_util.h" #include "feature/dirauth/reachability.h" diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h index 6b6feb9d89..062e4ac854 100644 --- a/src/core/or/or_circuit_st.h +++ b/src/core/or/or_circuit_st.h @@ -33,11 +33,6 @@ struct or_circuit_t { cell_queue_t p_chan_cells; /** The channel that is previous in this circuit. */ channel_t *p_chan; - /** - * Circuit mux associated with p_chan to which this circuit is attached; - * NULL if we have no p_chan. - */ - circuitmux_t *p_mux; /** Linked list of Exit streams associated with this circuit. */ edge_connection_t *n_streams; /** Linked list of Exit streams associated with this circuit that are diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 706a6e05cb..a166904a5f 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -61,7 +61,7 @@ #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/crypt_ops/crypto_util.h" #include "feature/dircommon/directory.h" @@ -94,8 +94,6 @@ #include "feature/nodelist/routerinfo_st.h" #include "core/or/socks_request_st.h" -#include "lib/intmath/weakrng.h" - static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t *layer_hint); @@ -134,9 +132,6 @@ uint64_t stats_n_relay_cells_delivered = 0; * reached (see append_cell_to_circuit_queue()) */ uint64_t stats_n_circ_max_cell_reached = 0; -/** Used to tell which stream to read from first on a circuit. */ -static tor_weak_rng_t stream_choice_rng = TOR_WEAK_RNG_INIT; - /** * Update channel usage state based on the type of relay cell and * circuit properties. @@ -2180,12 +2175,6 @@ circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) circ, layer_hint); } -void -stream_choice_seed_weak_rng(void) -{ - crypto_seed_weak_rng(&stream_choice_rng); -} - /** A helper function for circuit_resume_edge_reading() above. * The arguments are the same, except that <b>conn</b> is the head * of a linked list of edge streams that should each be considered. @@ -2237,7 +2226,8 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, int num_streams = 0; for (conn = first_conn; conn; conn = conn->next_stream) { num_streams++; - if (tor_weak_random_one_in_n(&stream_choice_rng, num_streams)) { + + if (crypto_fast_rng_one_in_n(get_thread_fast_rng(), num_streams)) { chosen_stream = conn; } /* Invariant: chosen_stream has been chosen uniformly at random from diff --git a/src/core/or/relay.h b/src/core/or/relay.h index 044f6be156..ea1b358ffb 100644 --- a/src/core/or/relay.h +++ b/src/core/or/relay.h @@ -94,8 +94,6 @@ const uint8_t *decode_address_from_payload(tor_addr_t *addr_out, int payload_len); void circuit_clear_cell_queue(circuit_t *circ, channel_t *chan); -void stream_choice_seed_weak_rng(void); - circid_t packed_cell_get_circid(const packed_cell_t *cell, int wide_circ_ids); #ifdef RELAY_PRIVATE @@ -126,4 +124,3 @@ STATIC int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, #endif /* defined(RELAY_PRIVATE) */ #endif /* !defined(TOR_RELAY_H) */ - diff --git a/src/core/proto/proto_socks.c b/src/core/proto/proto_socks.c index ac0c9e911b..b657a7b758 100644 --- a/src/core/proto/proto_socks.c +++ b/src/core/proto/proto_socks.c @@ -8,7 +8,7 @@ #include "feature/client/addressmap.h" #include "lib/buf/buffers.h" #include "core/mainloop/connection.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "app/config/config.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/relay/ext_orport.h" diff --git a/src/ext/include.am b/src/ext/include.am index 6bdce2d79e..317e25d78e 100644 --- a/src/ext/include.am +++ b/src/ext/include.am @@ -143,6 +143,7 @@ noinst_HEADERS += $(ED25519_DONNA_HDRS) LIBED25519_DONNA=src/ext/ed25519/donna/libed25519_donna.a noinst_LIBRARIES += $(LIBED25519_DONNA) +if BUILD_KECCAK_TINY src_ext_keccak_tiny_libkeccak_tiny_a_CFLAGS=\ @CFLAGS_CONSTTIME@ @@ -156,6 +157,7 @@ noinst_HEADERS += $(LIBKECCAK_TINY_HDRS) LIBKECCAK_TINY=src/ext/keccak-tiny/libkeccak-tiny.a noinst_LIBRARIES += $(LIBKECCAK_TINY) +endif EXTRA_DIST += \ src/ext/timeouts/bench/bench-add.lua \ diff --git a/src/feature/client/addressmap.c b/src/feature/client/addressmap.c index bbe786a6a2..c5a27ce8c6 100644 --- a/src/feature/client/addressmap.c +++ b/src/feature/client/addressmap.c @@ -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/dnsserv.c b/src/feature/client/dnsserv.c index 44e0caaafa..7fb3fff6c1 100644 --- a/src/feature/client/dnsserv.c +++ b/src/feature/client/dnsserv.c @@ -26,7 +26,7 @@ #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" diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index e543289ce0..e59f8b34e0 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -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" diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c index e7ff3bf34a..97bfc8ae30 100644 --- a/src/feature/client/transports.c +++ b/src/feature/client/transports.c @@ -100,7 +100,7 @@ #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" @@ -1424,11 +1424,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. @@ -1439,6 +1434,11 @@ 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); diff --git a/src/feature/control/btrack_orconn_cevent.c b/src/feature/control/btrack_orconn_cevent.c index ee142f2873..535aa8f614 100644 --- a/src/feature/control/btrack_orconn_cevent.c +++ b/src/feature/control/btrack_orconn_cevent.c @@ -20,7 +20,7 @@ #include "core/or/orconn_event.h" #include "feature/control/btrack_orconn.h" #include "feature/control/btrack_orconn_cevent.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" /** * Have we completed our first OR connection? diff --git a/src/feature/control/btrack_orconn_cevent.h b/src/feature/control/btrack_orconn_cevent.h index f9d24633aa..954b452451 100644 --- a/src/feature/control/btrack_orconn_cevent.h +++ b/src/feature/control/btrack_orconn_cevent.h @@ -7,6 +7,7 @@ **/ #ifndef TOR_BTRACK_ORCONN_CEVENT_H +#define TOR_BTRACK_ORCONN_CEVENT_H #include "feature/control/btrack_orconn.h" diff --git a/src/feature/control/btrack_orconn_maps.h b/src/feature/control/btrack_orconn_maps.h index 3ead40984c..2065eb61b2 100644 --- a/src/feature/control/btrack_orconn_maps.h +++ b/src/feature/control/btrack_orconn_maps.h @@ -7,6 +7,7 @@ **/ #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); diff --git a/src/feature/control/control.c b/src/feature/control/control.c index 6f8cd8f0aa..41e21c0a14 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -1,4 +1,3 @@ - /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ @@ -33,86 +32,26 @@ * stack. **/ -#define CONTROL_PRIVATE -#define OCIRC_EVENT_PRIVATE +#define CONTROL_MODULE_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/ocirc_event.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_fmt.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/buf/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/version/torversion.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> @@ -121,145 +60,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; - -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 * @@ -269,410 +69,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()) { - rescan_periodic_events(get_options()); - } - -#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> @@ -716,29 +112,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) @@ -783,886 +156,7 @@ 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[] = { { SIGHUP, "RELOAD" }, { SIGHUP, "HUP" }, { SIGINT, "SHUTDOWN" }, @@ -1681,3588 +175,6 @@ static const struct signal_t signal_table[] = { { 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; -} - -/** 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, uint32_t len, - const char *body) -{ - (void)len; - (void)body; - - 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; -} - -/** 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 */ - tor_mmap_t *mapped = networkstatus_map_cached_consensus("ns"); - 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; - } - } - } 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 = 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 = 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("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 */ - 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); - 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: /* FALLSTHROUGH */ - 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) @@ -5530,1433 +442,31 @@ connection_control_process_inbuf(control_connection_t *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, "DROPOWNERSHIP")) { - if (handle_control_dropownership(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; @@ -7025,553 +535,12 @@ monitor_owning_controller_process(const char *process_spec) } } -/** 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 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; - } + control_auth_free_all(); + control_events_free_all(); + control_cmd_free_all(); control_event_bootstrap_reset(); - authentication_cookie_is_set = 0; - 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.h b/src/feature/control/control.h index b2ab4c1997..3083837931 100644 --- a/src/feature/control/control.h +++ b/src/feature/control/control.h @@ -12,84 +12,6 @@ #ifndef TOR_CONTROL_H #define TOR_CONTROL_H -#include "core/or/ocirc_event.h" - -/** 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; - -#include "core/or/orconn_event.h" - -/** Used to indicate the type of a stream event passed to the controller. - * The various types are defined in control-spec.txt */ -typedef enum stream_status_event_t { - 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; - control_connection_t *TO_CONTROL_CONN(connection_t *); #define CONTROL_CONN_STATE_MIN_ 1 @@ -100,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 @@ -132,300 +42,22 @@ 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); -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); 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_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_) - -/* 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) */ +#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) */ #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..927115a308 --- /dev/null +++ b/src/feature/control/control_auth.c @@ -0,0 +1,439 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, 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_auth.h" +#include "feature/control/control_connection_st.h" +#include "feature/control/control_fmt.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.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; +} + +/** Called when we get an AUTHCHALLENGE command. */ +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 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, 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; +} + +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..f436482e4a --- /dev/null +++ b/src/feature/control/control_auth.h @@ -0,0 +1,27 @@ +/* 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 control_auth.h + * \brief Header file for control_auth.c. + **/ + +#ifndef TOR_CONTROL_AUTH_H +#define TOR_CONTROL_AUTH_H + +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, uint32_t len, + const char *body); +int handle_control_authenticate(control_connection_t *conn, + uint32_t cmd_data_len, + const char *args); +void control_auth_free_all(void); + +#endif /* !defined(TOR_CONTROL_AUTH_H) */ diff --git a/src/feature/control/control_bootstrap.c b/src/feature/control/control_bootstrap.c index 8153d7595a..098e24682e 100644 --- a/src/feature/control/control_bootstrap.c +++ b/src/feature/control/control_bootstrap.c @@ -14,7 +14,7 @@ #include "core/or/connection_st.h" #include "core/or/or_connection_st.h" #include "core/or/reasons.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/hibernate/hibernate.h" #include "lib/malloc/malloc.h" diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c new file mode 100644 index 0000000000..95cf0d561e --- /dev/null +++ b/src/feature/control/control_cmd.c @@ -0,0 +1,2323 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, 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 "app/config/confparse.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_events.h" +#include "feature/control/control_fmt.h" +#include "feature/control/control_getinfo.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 "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_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, uint32_t len, + char *body, + 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) + +/** 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; +} + +/** 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; +} + +/** 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; +} + +/** 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; +} + +/** 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, uint32_t len, + const char *body) +{ + (void)len; + (void)body; + + 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, 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; + } +} + +/** 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; +} + +/** 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 */ + 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); + 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 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; + ed25519_public_key_t v3_pk; + uint32_t version; + + /* 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; + 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) == + REND_DESC_ID_V2_LEN_BASE32) { + /* 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 { + 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; + } + } + + if (version == HS_VERSION_TWO) { + 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. */ + 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: + 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; +} + +/** 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; +} + +/** 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: /* FALLSTHROUGH */ + 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; +} + +int +handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + /* 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. */ + + 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, "DROPOWNERSHIP")) { + if (handle_control_dropownership(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); + } + + 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..a417e10da3 --- /dev/null +++ b/src/feature/control/control_cmd.h @@ -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-2019, 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 + +int handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args); +void control_cmd_free_all(void); + +#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, char **err_msg_out); + +STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg, + int *created, char **err_msg_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_events.c b/src/feature/control/control_events.c new file mode 100644 index 0000000000..129776f49f --- /dev/null +++ b/src/feature/control/control_events.c @@ -0,0 +1,2317 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, 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/dircommon/directory.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerinfo.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" + +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_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); +} + +/** 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); + 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; +} + +/** 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; +} + +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; +} +/** 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; +} + +/** 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..0bdbb9cfd2 --- /dev/null +++ b/src/feature/control/control_events.h @@ -0,0 +1,351 @@ +/* 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 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" + +/** 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; + +#include "core/or/orconn_event.h" + +/** Used to indicate the type of a stream event passed to the controller. + * The various types are defined in control-spec.txt */ +typedef enum stream_status_event_t { + 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, 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); + +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..71f9d82163 --- /dev/null +++ b/src/feature/control/control_fmt.c @@ -0,0 +1,409 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control.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/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" + +/** 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 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; +} + +/** 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>. + */ +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>. */ +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) +{ + connection_write_str_to_buf("250 OK\r\n", conn); +} + +/** 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. */ +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. */ +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; +} + +/** 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..74545eb309 --- /dev/null +++ b/src/feature/control/control_fmt.h @@ -0,0 +1,36 @@ +/* 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 control_fmt.h + * \brief Header file for control_fmt.c. + **/ + +#ifndef TOR_CONTROL_FMT_H +#define TOR_CONTROL_FMT_H + +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); + +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); + +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); +const char *extract_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len); +const char *decode_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len); +void send_control_done(control_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..a7a85f2fdf --- /dev/null +++ b/src/feature/control/control_getinfo.c @@ -0,0 +1,1662 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, 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/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/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 "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/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; +} + +/** 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 */ + tor_mmap_t *mapped = networkstatus_map_cached_consensus("ns"); + 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; + } + } + } 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("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. */ +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; +} diff --git a/src/feature/control/control_getinfo.h b/src/feature/control/control_getinfo.h new file mode 100644 index 0000000000..d5a2feb3e0 --- /dev/null +++ b/src/feature/control/control_getinfo.h @@ -0,0 +1,57 @@ +/* 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 control.h + * \brief Header file for control.c. + **/ + +#ifndef TOR_CONTROL_GETINFO_H +#define TOR_CONTROL_GETINFO_H + +int handle_control_getinfo(control_connection_t *conn, uint32_t len, + const char *body); + +#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_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) */ diff --git a/src/feature/dirauth/voteflags.c b/src/feature/dirauth/voteflags.c index 4f7593a3e1..0a53c588d6 100644 --- a/src/feature/dirauth/voteflags.c +++ b/src/feature/dirauth/voteflags.c @@ -531,6 +531,20 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) node->is_running = answer; } +/* 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 or_options_t *options = 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>. <b>rs</b> is zeroed out before it is * set. @@ -597,9 +611,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, rs->is_staledesc = (ri->cache_info.published_on + DESC_IS_STALE_INTERVAL) < now; - if (options->AuthDirHasIPv6Connectivity == 1 && - !tor_addr_is_null(&ri->ipv6_addr) && - node->last_reachable6 >= now - REACHABLE_TIMEOUT) { + if (should_publish_node_ipv6(node, ri, now)) { /* 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); diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 62eb4c86c2..06bd298aca 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -1072,13 +1072,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 ? @@ -1329,19 +1327,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; } diff --git a/src/feature/dircache/dirserv.c b/src/feature/dircache/dirserv.c index 4be6836fe1..79400bf15f 100644 --- a/src/feature/dircache/dirserv.c +++ b/src/feature/dircache/dirserv.c @@ -583,11 +583,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; @@ -622,14 +620,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/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c index 70b6a20028..0b79b07799 100644 --- a/src/feature/dirclient/dirclient.c +++ b/src/feature/dirclient/dirclient.c @@ -14,7 +14,7 @@ #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" diff --git a/src/feature/hibernate/hibernate.c b/src/feature/hibernate/hibernate.c index 70c2b4f69f..7351e5e002 100644 --- a/src/feature/hibernate/hibernate.c +++ b/src/feature/hibernate/hibernate.c @@ -35,7 +35,7 @@ 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" diff --git a/src/feature/hibernate/hibernate.h b/src/feature/hibernate/hibernate.h index 3309ef0ce3..2e245f6ab1 100644 --- a/src/feature/hibernate/hibernate.h +++ b/src/feature/hibernate/hibernate.h @@ -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/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 597982b34e..1dae9c79c1 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -756,7 +756,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. */ diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index e3873d2f18..253c24d643 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -565,81 +565,6 @@ 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 * rendezvous point rp_node and the service's subcredential, populate the * already allocated intro1_data object with the needed key material and link @@ -662,10 +587,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); @@ -1044,9 +968,7 @@ 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; } diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index bd43ef6132..c34271efca 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -459,6 +459,24 @@ fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)) return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); } +/* 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 @@ -528,13 +546,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. */ @@ -753,24 +773,13 @@ 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; } @@ -1543,7 +1552,10 @@ 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; } strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32); diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index dadfa024b8..96a96755fd 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -44,6 +44,10 @@ typedef struct hs_client_service_authorization_t { void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); +void hs_client_launch_v3_desc_fetch( + const ed25519_public_key_t *onion_identity_pk, + const smartlist_t *hsdirs); + int hs_client_decode_descriptor( const char *desc_str, const ed25519_public_key_t *service_identity_pk, diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c index ebe49f09a5..b2227432d2 100644 --- a/src/feature/hs/hs_common.c +++ b/src/feature/hs/hs_common.c @@ -926,7 +926,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; @@ -1009,24 +1010,6 @@ 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 * 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 @@ -1697,6 +1680,12 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, tor_assert(lspecs); + 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: @@ -1730,6 +1719,12 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, /* Legacy ID is mandatory, and we require IPv4. */ if (!have_v4 || !have_legacy_id) { + bool both = !have_v4 && !have_legacy_id; + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Missing %s%s%s link specifier%s.", + !have_v4 ? "IPv4" : "", + both ? " and " : "", + !have_legacy_id ? "legacy ID" : "", + both ? "s" : ""); goto done; } @@ -1748,6 +1743,10 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, * release. */ } else { /* If we can't reach IPv4, return NULL. */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Received an IPv4 link specifier, " + "but the address is not reachable: %s:%u", + fmt_addr(&addr_v4), port_v4); goto done; } @@ -1755,7 +1754,7 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, 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? */ + * it is, are we allowed to extend to private addresses? */ if (!extend_info_addr_is_allowed(&addr_v4)) { log_fn(LOG_PROTOCOL_WARN, LD_REND, "Requested address is private and we are not allowed to extend to " @@ -1827,3 +1826,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..abf39fa431 100644 --- a/src/feature/hs/hs_common.h +++ b/src/feature/hs/hs_common.h @@ -217,8 +217,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)); @@ -262,6 +260,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_control.c b/src/feature/hs/hs_control.c index 9970fdd123..20a1061609 100644 --- a/src/feature/hs/hs_control.c +++ b/src/feature/hs/hs_control.c @@ -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" @@ -259,3 +260,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..b55e4c53c9 100644 --- a/src/feature/hs/hs_control.h +++ b/src/feature/hs/hs_control.h @@ -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 b09d50e010..8f7bdf86ef 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -324,12 +324,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); { @@ -1190,52 +1189,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; } @@ -2878,8 +2847,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); @@ -2972,69 +2941,6 @@ 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. */ void hs_descriptor_clear_intro_points(hs_descriptor_t *desc) @@ -3050,59 +2956,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..dbe0cb1c94 100644 --- a/src/feature/hs/hs_descriptor.h +++ b/src/feature/hs/hs_descriptor.h @@ -69,28 +69,10 @@ 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. */ 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. */ + * 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 @@ -261,12 +243,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,9 +275,6 @@ 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 * diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c index b28a5c2b80..c9cd3a0419 100644 --- a/src/feature/hs/hs_intropoint.c +++ b/src/feature/hs/hs_intropoint.c @@ -601,8 +601,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_service.c b/src/feature/hs/hs_service.c index b94dd9a481..e3d0043460 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -280,9 +280,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); @@ -426,23 +427,16 @@ service_intro_point_free_void(void *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. + * 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)); @@ -472,12 +466,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(); @@ -490,40 +489,9 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy, } } - if (ei == NULL) { - goto done; - } - - /* 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; @@ -656,16 +624,16 @@ get_objects_from_ident(const hs_ident_circuit_t *ident, * 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; } @@ -681,7 +649,7 @@ get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) 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); @@ -690,7 +658,8 @@ 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 @@ -1179,7 +1148,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; @@ -1556,7 +1526,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); @@ -1565,22 +1535,13 @@ 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 * object ip and the time now, setup the content of an already allocated * descriptor intro desc_ip. @@ -1615,9 +1576,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); @@ -2106,7 +2072,6 @@ static hs_service_intro_point_t * pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) { 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; @@ -2127,43 +2092,17 @@ 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; - } - - /* 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. */ + ip = service_intro_point_new(node); - /* 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; } diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index ec53f2f23b..8d7f773219 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -369,10 +369,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.h b/src/feature/hs/hs_stats.h index d89440faca..ca048e2123 100644 --- a/src/feature/hs/hs_stats.h +++ b/src/feature/hs/hs_stats.h @@ -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 diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index 93baa6e4e0..e2a1d6a9fa 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -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" diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index 023115978c..ea9f12367f 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -58,7 +58,7 @@ #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" diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 8b02dd9c66..f878d47fd7 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -49,7 +49,7 @@ #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/hs/hs_client.h" @@ -1189,6 +1189,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) diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 3420959618..a3d65347a8 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -77,6 +77,11 @@ 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); 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); diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c index d1220f553a..48f448ad1e 100644 --- a/src/feature/nodelist/routerlist.c +++ b/src/feature/nodelist/routerlist.c @@ -67,7 +67,7 @@ #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" diff --git a/src/feature/nodelist/torcert.c b/src/feature/nodelist/torcert.c index b0197e9f13..56f1a8ac9f 100644 --- a/src/feature/nodelist/torcert.c +++ b/src/feature/nodelist/torcert.c @@ -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..03d5bdca93 100644 --- a/src/feature/nodelist/torcert.h +++ b/src/feature/nodelist/torcert.h @@ -71,7 +71,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/relay/dns.c b/src/feature/relay/dns.c index fa0a1b5910..664edf96a9 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -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" diff --git a/src/feature/relay/ext_orport.c b/src/feature/relay/ext_orport.c index 8589efb48d..c343d19b8d 100644 --- a/src/feature/relay/ext_orport.c +++ b/src/feature/relay/ext_orport.c @@ -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" @@ -659,4 +659,3 @@ ext_orport_free_all(void) if (ext_or_auth_cookie) /* Free the auth cookie */ tor_free(ext_or_auth_cookie); } - diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index cdd032f78d..e5cf72ad18 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -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" @@ -152,6 +152,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); @@ -194,8 +196,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; @@ -269,11 +271,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 * @@ -374,8 +377,8 @@ assert_identity_keys_ok(void) /** 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())); @@ -1941,26 +1944,33 @@ get_my_declared_family(const or_options_t *options) return result; } -/** 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. +/** 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 @@ -2017,8 +2027,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()); @@ -2057,85 +2067,258 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) ri->declared_family = get_my_declared_family(options); + 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) { + router_update_routerinfo_from_extrainfo(ri, ei); + } + + result = router_dump_and_sign_routerinfo_descriptor_body(ri); + if (result < 0) + goto err; + if (ei) { - tor_assert(! - routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei, - &ri->cache_info, NULL)); + 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; @@ -2478,6 +2661,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, @@ -2930,9 +3117,14 @@ 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. */ +/** 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. + * + * Return 0 on success, negative on failure. */ int extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, crypto_pk_t *ident_key, @@ -2942,7 +3134,6 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, 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]; @@ -2957,7 +3148,6 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, 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, @@ -2983,21 +3173,25 @@ 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", + 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)); - 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); + } + 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) { @@ -3033,19 +3227,17 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, if (contents) smartlist_add(chunks, contents); } - } - - /* Add information about the pluggable transports we support. */ - if (options->ServerTransportPlugin) { - char *pluggable_transports = pt_get_extra_info_descriptor_string(); - if (pluggable_transports) - smartlist_add(chunks, pluggable_transports); - } - - if (should_record_bridge_info(options) && write_stats_to_extrainfo) { - const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); - if (bridge_stats) { - smartlist_add_strdup(chunks, bridge_stats); + /* Add information about the pluggable transports we support. */ + if (options->ServerTransportPlugin) { + char *pluggable_transports = pt_get_extra_info_descriptor_string(); + if (pluggable_transports) + smartlist_add(chunks, pluggable_transports); + } + if (should_record_bridge_info(options)) { + const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); + if (bridge_stats) { + smartlist_add_strdup(chunks, bridge_stats); + } } } @@ -3139,7 +3331,6 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, tor_free(s_dup); tor_free(ed_cert_line); extrainfo_free(ei_tmp); - tor_free(bandwidth_usage); return result; } diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index 60bc857ceb..55b9ef9e68 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -23,11 +23,12 @@ 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); +MOCK_DECL(crypto_pk_t *,get_server_identity_key,(void)); int server_identity_key_is_set(void); void set_client_identity_key(crypto_pk_t *k); crypto_pk_t *get_tlsclient_identity_key(void); @@ -114,7 +115,7 @@ 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); STATIC smartlist_t *get_my_declared_family(const or_options_t *options); @@ -123,8 +124,18 @@ STATIC smartlist_t *get_my_declared_family(const or_options_t *options); extern time_t desc_clean_since; extern const char *desc_dirty_reason; void set_server_identity_key_digest_testing(const uint8_t *digest); -#endif - -#endif +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 876f908d41..d965777ad6 100644 --- a/src/feature/relay/routerkeys.c +++ b/src/feature/relay/routerkeys.c @@ -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..cde07b52c3 100644 --- a/src/feature/relay/routerkeys.h +++ b/src/feature/relay/routerkeys.h @@ -7,8 +7,8 @@ #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/selftest.c b/src/feature/relay/selftest.c index 064eea6c46..eeddd09b63 100644 --- a/src/feature/relay/selftest.c +++ b/src/feature/relay/selftest.c @@ -26,7 +26,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" diff --git a/src/feature/rend/rendcache.c b/src/feature/rend/rendcache.c index fadfb43883..abeb150685 100644 --- a/src/feature/rend/rendcache.c +++ b/src/feature/rend/rendcache.c @@ -593,10 +593,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 +854,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; @@ -1005,4 +1006,3 @@ rend_cache_store_v2_desc_as_client(const char *desc, tor_free(intro_content); return retval; } - diff --git a/src/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index 4ca783c7c3..5a8b234544 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -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" diff --git a/src/feature/rend/rendcommon.c b/src/feature/rend/rendcommon.c index de48af795f..777de2984c 100644 --- a/src/feature/rend/rendcommon.c +++ b/src/feature/rend/rendcommon.c @@ -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; } diff --git a/src/feature/rend/rendparse.c b/src/feature/rend/rendparse.c index abd0feb448..a98cb3ad88 100644 --- a/src/feature/rend/rendparse.c +++ b/src/feature/rend/rendparse.c @@ -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/rendservice.c b/src/feature/rend/rendservice.c index 5ee084b0b7..57475a64b0 100644 --- a/src/feature/rend/rendservice.c +++ b/src/feature/rend/rendservice.c @@ -19,7 +19,7 @@ #include "core/or/policies.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_common.h" @@ -3012,6 +3012,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; @@ -3026,13 +3030,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'.", diff --git a/src/feature/stats/geoip_stats.c b/src/feature/stats/geoip_stats.c index 5119da19a0..6fb21f4f79 100644 --- a/src/feature/stats/geoip_stats.c +++ b/src/feature/stats/geoip_stats.c @@ -32,7 +32,7 @@ #include "ht.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/include.am b/src/include.am index 9070a69a03..77c126ba45 100644 --- a/src/include.am +++ b/src/include.am @@ -8,6 +8,7 @@ include src/lib/compress/include.am include src/lib/container/include.am include src/lib/crypt_ops/include.am include src/lib/defs/include.am +include src/lib/dispatch/include.am include src/lib/encoding/include.am include src/lib/evloop/include.am include src/lib/fdio/include.am @@ -24,6 +25,7 @@ include src/lib/malloc/include.am include src/lib/net/include.am include src/lib/osinfo/include.am include src/lib/process/include.am +include src/lib/pubsub/include.am include src/lib/sandbox/include.am include src/lib/string/include.am include src/lib/subsys/include.am diff --git a/src/lib/cc/compat_compiler.h b/src/lib/cc/compat_compiler.h index 3a0f307186..18b76cc1a1 100644 --- a/src/lib/cc/compat_compiler.h +++ b/src/lib/cc/compat_compiler.h @@ -217,4 +217,16 @@ /** Macro: Yields the number of elements in array x. */ #define ARRAY_LENGTH(x) ((sizeof(x)) / sizeof(x[0])) +/** + * "Eat" a semicolon that somebody puts at the end of a top-level macro. + * + * Frequently, we want to declare a macro that people will use at file scope, + * and we want to allow people to put a semicolon after the macro. + * + * This declaration of a struct can be repeated any number of times, and takes + * a trailing semicolon afterwards. + **/ +#define EAT_SEMICOLON \ + struct dummy_semicolon_eater__ + #endif /* !defined(TOR_COMPAT_H) */ diff --git a/src/lib/cc/torint.h b/src/lib/cc/torint.h index c9b2d329f2..9a66aada18 100644 --- a/src/lib/cc/torint.h +++ b/src/lib/cc/torint.h @@ -125,4 +125,13 @@ typedef int32_t ssize_t; /** Any size_t larger than this amount is likely to be an underflow. */ #define SIZE_T_CEILING ((size_t)(SSIZE_MAX-16)) +#if SIZEOF_INT > SIZEOF_VOID_P +#error "sizeof(int) > sizeof(void *) - Tor cannot be built on this platform!" +#endif + +#if SIZEOF_UNSIGNED_INT > SIZEOF_VOID_P +#error "sizeof(unsigned int) > sizeof(void *) - Tor cannot be built on this \ +platform!" +#endif + #endif /* !defined(TOR_TORINT_H) */ diff --git a/src/lib/container/include.am b/src/lib/container/include.am index 032e4033da..50d35e749b 100644 --- a/src/lib/container/include.am +++ b/src/lib/container/include.am @@ -8,6 +8,7 @@ endif src_lib_libtor_container_a_SOURCES = \ src/lib/container/bloomfilt.c \ src/lib/container/map.c \ + src/lib/container/namemap.c \ src/lib/container/order.c \ src/lib/container/smartlist.c @@ -21,5 +22,7 @@ noinst_HEADERS += \ src/lib/container/bloomfilt.h \ src/lib/container/handles.h \ src/lib/container/map.h \ + src/lib/container/namemap.h \ + src/lib/container/namemap_st.h \ src/lib/container/order.h \ src/lib/container/smartlist.h diff --git a/src/lib/container/namemap.c b/src/lib/container/namemap.c new file mode 100644 index 0000000000..a90057b32c --- /dev/null +++ b/src/lib/container/namemap.c @@ -0,0 +1,184 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "lib/container/smartlist.h" +#include "lib/container/namemap.h" +#include "lib/container/namemap_st.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include "ext/siphash.h" + +#include <string.h> + +/** Helper for namemap hashtable implementation: compare two entries. */ +static inline int +mapped_name_eq(const mapped_name_t *a, const mapped_name_t *b) +{ + return !strcmp(a->name, b->name); +} + +/** Helper for namemap hashtable implementation: hash an entry. */ +static inline unsigned +mapped_name_hash(const mapped_name_t *a) +{ + return (unsigned) siphash24g(a->name, strlen(a->name)); +} + +HT_PROTOTYPE(namemap_ht, mapped_name_t, node, mapped_name_hash, + mapped_name_eq) +HT_GENERATE2(namemap_ht, mapped_name_t, node, mapped_name_hash, + mapped_name_eq, 0.6, tor_reallocarray_, tor_free_) + +/** Set up an uninitialized <b>map</b>. */ +void +namemap_init(namemap_t *map) +{ + memset(map, 0, sizeof(*map)); + HT_INIT(namemap_ht, &map->ht); + map->names = smartlist_new(); +} + +/** Return the name that <b>map</b> associates with a given <b>id</b>, or + * NULL if there is no such name. */ +const char * +namemap_get_name(const namemap_t *map, unsigned id) +{ + if (map->names && id < (unsigned)smartlist_len(map->names)) { + mapped_name_t *name = smartlist_get(map->names, (int)id); + return name->name; + } else { + return NULL; + } +} + +/** + * Return the name that <b>map</b> associates with a given <b>id</b>, or a + * pointer to a statically allocated string describing the value of <b>id</b> + * if no such name exists. + **/ +const char * +namemap_fmt_name(const namemap_t *map, unsigned id) +{ + static char buf[32]; + + const char *name = namemap_get_name(map, id); + if (name) + return name; + + tor_snprintf(buf, sizeof(buf), "{%u}", id); + + return buf; +} + +/** + * Helper: As namemap_get_id(), but requires that <b>name</b> is + * <b>namelen</b> charaters long, and that <b>namelen</b> is no more than + * MAX_NAMEMAP_NAME_LEN. + */ +static unsigned +namemap_get_id_unchecked(const namemap_t *map, + const char *name, + size_t namelen) +{ + union { + mapped_name_t n; + char storage[MAX_NAMEMAP_NAME_LEN + sizeof(mapped_name_t) + 1]; + } u; + memcpy(u.n.name, name, namelen); + u.n.name[namelen] = 0; + const mapped_name_t *found = HT_FIND(namemap_ht, &map->ht, &u.n); + if (found) { + tor_assert(map->names); + tor_assert(smartlist_get(map->names, found->intval) == found); + return found->intval; + } + + return NAMEMAP_ERR; +} + +/** + * Return the identifier currently associated by <b>map</b> with the name + * <b>name</b>, or NAMEMAP_ERR if no such identifier exists. + **/ +unsigned +namemap_get_id(const namemap_t *map, + const char *name) +{ + size_t namelen = strlen(name); + if (namelen > MAX_NAMEMAP_NAME_LEN) { + return NAMEMAP_ERR; + } + + return namemap_get_id_unchecked(map, name, namelen); +} + +/** + * Return the identifier associated by <b>map</b> with the name + * <b>name</b>, allocating a new identifier in <b>map</b> if none exists. + * + * Return NAMEMAP_ERR if <b>name</b> is too long, or if there are no more + * identifiers we can allocate. + **/ +unsigned +namemap_get_or_create_id(namemap_t *map, + const char *name) +{ + size_t namelen = strlen(name); + if (namelen > MAX_NAMEMAP_NAME_LEN) { + return NAMEMAP_ERR; + } + + if (PREDICT_UNLIKELY(map->names == NULL)) + map->names = smartlist_new(); + + unsigned found = namemap_get_id_unchecked(map, name, namelen); + if (found != NAMEMAP_ERR) + return found; + + unsigned new_id = (unsigned)smartlist_len(map->names); + if (new_id == NAMEMAP_ERR) + return NAMEMAP_ERR; /* Can't allocate any more. */ + + mapped_name_t *insert = tor_malloc_zero( + offsetof(mapped_name_t, name) + namelen + 1); + memcpy(insert->name, name, namelen+1); + insert->intval = new_id; + + HT_INSERT(namemap_ht, &map->ht, insert); + smartlist_add(map->names, insert); + + return new_id; +} + +/** Return the number of entries in 'names' */ +size_t +namemap_get_size(const namemap_t *map) +{ + if (PREDICT_UNLIKELY(map->names == NULL)) + return 0; + + return smartlist_len(map->names); +} + +/** + * Release all storage held in <b>map</b>. + */ +void +namemap_clear(namemap_t *map) +{ + if (!map) + return; + + HT_CLEAR(namemap_ht, &map->ht); + if (map->names) { + SMARTLIST_FOREACH(map->names, mapped_name_t *, n, + tor_free(n)); + smartlist_free(map->names); + } + memset(map, 0, sizeof(*map)); +} diff --git a/src/lib/container/namemap.h b/src/lib/container/namemap.h new file mode 100644 index 0000000000..97792e13ba --- /dev/null +++ b/src/lib/container/namemap.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_NAMEMAP_H +#define TOR_NAMEMAP_H + +/** + * \file namemap.h + * + * \brief Header for namemap.c + **/ + +#include "lib/cc/compat_compiler.h" +#include "ext/ht.h" + +#include <stddef.h> + +typedef struct namemap_t namemap_t; + +/** Returned in place of an identifier when an error occurs. */ +#define NAMEMAP_ERR UINT_MAX + +void namemap_init(namemap_t *map); +const char *namemap_get_name(const namemap_t *map, unsigned id); +const char *namemap_fmt_name(const namemap_t *map, unsigned id); +unsigned namemap_get_id(const namemap_t *map, + const char *name); +unsigned namemap_get_or_create_id(namemap_t *map, + const char *name); +size_t namemap_get_size(const namemap_t *map); +void namemap_clear(namemap_t *map); + +#endif diff --git a/src/lib/container/namemap_st.h b/src/lib/container/namemap_st.h new file mode 100644 index 0000000000..5717352fa2 --- /dev/null +++ b/src/lib/container/namemap_st.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef NAMEMAP_ST_H +#define NAMEMAP_ST_H + +#include "lib/cc/compat_compiler.h" +#include "ext/ht.h" + +struct smartlist_t; + +/** Longest allowed name that's allowed in a namemap_t. */ +#define MAX_NAMEMAP_NAME_LEN 128 + +/** An entry inside a namemap_t. Maps a string to a numeric identifier. */ +typedef struct mapped_name_t { + HT_ENTRY(mapped_name_t) node; + unsigned intval; + char name[FLEXIBLE_ARRAY_MEMBER]; +} mapped_name_t; + +/** A structure that allocates small numeric identifiers for names and maps + * back and forth between them. */ +struct namemap_t { + HT_HEAD(namemap_ht, mapped_name_t) ht; + struct smartlist_t *names; +}; + +/** Macro to initialize a namemap. */ +#define NAMEMAP_INIT() { HT_INITIALIZER(), NULL } + +#endif diff --git a/src/lib/crypt_ops/crypto_digest.c b/src/lib/crypt_ops/crypto_digest.c index 26f06c6c79..9da135e9c4 100644 --- a/src/lib/crypt_ops/crypto_digest.c +++ b/src/lib/crypt_ops/crypto_digest.c @@ -23,171 +23,6 @@ #include "lib/arch/bytes.h" -#ifdef ENABLE_NSS -DISABLE_GCC_WARNING(strict-prototypes) -#include <pk11pub.h> -ENABLE_GCC_WARNING(strict-prototypes) -#else - -#include "lib/crypt_ops/crypto_openssl_mgt.h" - -DISABLE_GCC_WARNING(redundant-decls) - -#include <openssl/hmac.h> -#include <openssl/sha.h> - -ENABLE_GCC_WARNING(redundant-decls) -#endif - -#ifdef ENABLE_NSS -/** - * Convert a digest_algorithm_t (used by tor) to a HashType (used by NSS). - * On failure, return SEC_OID_UNKNOWN. */ -static SECOidTag -digest_alg_to_nss_oid(digest_algorithm_t alg) -{ - switch (alg) { - case DIGEST_SHA1: return SEC_OID_SHA1; - case DIGEST_SHA256: return SEC_OID_SHA256; - case DIGEST_SHA512: return SEC_OID_SHA512; - case DIGEST_SHA3_256: /* Fall through */ - case DIGEST_SHA3_512: /* Fall through */ - default: - return SEC_OID_UNKNOWN; - } -} - -/* Helper: get an unkeyed digest via pk11wrap */ -static int -digest_nss_internal(SECOidTag alg, - char *digest, unsigned len_out, - const char *msg, size_t msg_len) -{ - if (alg == SEC_OID_UNKNOWN) - return -1; - tor_assert(msg_len <= UINT_MAX); - - int rv = -1; - SECStatus s; - PK11Context *ctx = PK11_CreateDigestContext(alg); - if (!ctx) - return -1; - - s = PK11_DigestBegin(ctx); - if (s != SECSuccess) - goto done; - - s = PK11_DigestOp(ctx, (const unsigned char *)msg, (unsigned int)msg_len); - if (s != SECSuccess) - goto done; - - unsigned int len = 0; - s = PK11_DigestFinal(ctx, (unsigned char *)digest, &len, len_out); - if (s != SECSuccess) - goto done; - - rv = 0; - done: - PK11_DestroyContext(ctx, PR_TRUE); - return rv; -} - -/** True iff alg is implemented in our crypto library, and we want to use that - * implementation */ -static bool -library_supports_digest(digest_algorithm_t alg) -{ - switch (alg) { - case DIGEST_SHA1: /* Fall through */ - case DIGEST_SHA256: /* Fall through */ - case DIGEST_SHA512: /* Fall through */ - return true; - case DIGEST_SHA3_256: /* Fall through */ - case DIGEST_SHA3_512: /* Fall through */ - default: - return false; - } -} -#endif - -/* Crypto digest functions */ - -/** Compute the SHA1 digest of the <b>len</b> bytes on data stored in - * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. - * Return 0 on success, -1 on failure. - */ -MOCK_IMPL(int, -crypto_digest,(char *digest, const char *m, size_t len)) -{ - tor_assert(m); - tor_assert(digest); -#ifdef ENABLE_NSS - return digest_nss_internal(SEC_OID_SHA1, digest, DIGEST_LEN, m, len); -#else - if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL) { - return -1; - } -#endif - return 0; -} - -/** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>, - * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result - * into <b>digest</b>. Return 0 on success, -1 on failure. */ -int -crypto_digest256(char *digest, const char *m, size_t len, - digest_algorithm_t algorithm) -{ - tor_assert(m); - tor_assert(digest); - tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); - - int ret = 0; - if (algorithm == DIGEST_SHA256) { -#ifdef ENABLE_NSS - return digest_nss_internal(SEC_OID_SHA256, digest, DIGEST256_LEN, m, len); -#else - ret = (SHA256((const uint8_t*)m,len,(uint8_t*)digest) != NULL); -#endif - } else { - ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) - > -1); - } - - if (!ret) - return -1; - return 0; -} - -/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>, - * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result - * into <b>digest</b>. Return 0 on success, -1 on failure. */ -int -crypto_digest512(char *digest, const char *m, size_t len, - digest_algorithm_t algorithm) -{ - tor_assert(m); - tor_assert(digest); - tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); - - int ret = 0; - if (algorithm == DIGEST_SHA512) { -#ifdef ENABLE_NSS - return digest_nss_internal(SEC_OID_SHA512, digest, DIGEST512_LEN, m, len); -#else - ret = (SHA512((const unsigned char*)m,len,(unsigned char*)digest) - != NULL); -#endif - } else { - ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) - > -1); - } - - if (!ret) - return -1; - return 0; -} - /** Set the common_digests_t in <b>ds_out</b> to contain every digest on the * <b>len</b> bytes in <b>m</b> that we know how to compute. Return 0 on * success, -1 on failure. */ @@ -267,485 +102,6 @@ crypto_digest_algorithm_get_length(digest_algorithm_t alg) } } -/** Intermediate information about the digest of a stream of data. */ -struct crypto_digest_t { - digest_algorithm_t algorithm; /**< Which algorithm is in use? */ - /** State for the digest we're using. Only one member of the - * union is usable, depending on the value of <b>algorithm</b>. Note also - * that space for other members might not even be allocated! - */ - union { -#ifdef ENABLE_NSS - PK11Context *ctx; -#else - SHA_CTX sha1; /**< state for SHA1 */ - SHA256_CTX sha2; /**< state for SHA256 */ - SHA512_CTX sha512; /**< state for SHA512 */ -#endif - keccak_state sha3; /**< state for SHA3-[256,512] */ - } d; -}; - -#ifdef TOR_UNIT_TESTS - -digest_algorithm_t -crypto_digest_get_algorithm(crypto_digest_t *digest) -{ - tor_assert(digest); - - return digest->algorithm; -} - -#endif /* defined(TOR_UNIT_TESTS) */ - -/** - * Return the number of bytes we need to malloc in order to get a - * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe - * when we free one. - */ -static size_t -crypto_digest_alloc_bytes(digest_algorithm_t alg) -{ - /* Helper: returns the number of bytes in the 'f' field of 'st' */ -#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) - /* Gives the length of crypto_digest_t through the end of the field 'd' */ -#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ - STRUCT_FIELD_SIZE(crypto_digest_t, f)) - switch (alg) { -#ifdef ENABLE_NSS - case DIGEST_SHA1: /* Fall through */ - case DIGEST_SHA256: /* Fall through */ - case DIGEST_SHA512: - return END_OF_FIELD(d.ctx); -#else - case DIGEST_SHA1: - return END_OF_FIELD(d.sha1); - case DIGEST_SHA256: - return END_OF_FIELD(d.sha2); - case DIGEST_SHA512: - return END_OF_FIELD(d.sha512); -#endif - case DIGEST_SHA3_256: - case DIGEST_SHA3_512: - return END_OF_FIELD(d.sha3); - default: - tor_assert(0); // LCOV_EXCL_LINE - return 0; // LCOV_EXCL_LINE - } -#undef END_OF_FIELD -#undef STRUCT_FIELD_SIZE -} - -/** - * Internal function: create and return a new digest object for 'algorithm'. - * Does not typecheck the algorithm. - */ -static crypto_digest_t * -crypto_digest_new_internal(digest_algorithm_t algorithm) -{ - crypto_digest_t *r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); - r->algorithm = algorithm; - - switch (algorithm) - { -#ifdef ENABLE_NSS - case DIGEST_SHA1: /* fall through */ - case DIGEST_SHA256: /* fall through */ - case DIGEST_SHA512: - r->d.ctx = PK11_CreateDigestContext(digest_alg_to_nss_oid(algorithm)); - if (BUG(!r->d.ctx)) { - tor_free(r); - return NULL; - } - if (BUG(SECSuccess != PK11_DigestBegin(r->d.ctx))) { - crypto_digest_free(r); - return NULL; - } - break; -#else - case DIGEST_SHA1: - SHA1_Init(&r->d.sha1); - break; - case DIGEST_SHA256: - SHA256_Init(&r->d.sha2); - break; - case DIGEST_SHA512: - SHA512_Init(&r->d.sha512); - break; -#endif - case DIGEST_SHA3_256: - keccak_digest_init(&r->d.sha3, 256); - break; - case DIGEST_SHA3_512: - keccak_digest_init(&r->d.sha3, 512); - break; - default: - tor_assert_unreached(); - } - - return r; -} - -/** Allocate and return a new digest object to compute SHA1 digests. - */ -crypto_digest_t * -crypto_digest_new(void) -{ - return crypto_digest_new_internal(DIGEST_SHA1); -} - -/** Allocate and return a new digest object to compute 256-bit digests - * using <b>algorithm</b>. - * - * C_RUST_COUPLED: `external::crypto_digest::crypto_digest256_new` - * C_RUST_COUPLED: `crypto::digest::Sha256::default` - */ -crypto_digest_t * -crypto_digest256_new(digest_algorithm_t algorithm) -{ - tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); - return crypto_digest_new_internal(algorithm); -} - -/** Allocate and return a new digest object to compute 512-bit digests - * using <b>algorithm</b>. */ -crypto_digest_t * -crypto_digest512_new(digest_algorithm_t algorithm) -{ - tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); - return crypto_digest_new_internal(algorithm); -} - -/** Deallocate a digest object. - */ -void -crypto_digest_free_(crypto_digest_t *digest) -{ - if (!digest) - return; -#ifdef ENABLE_NSS - if (library_supports_digest(digest->algorithm)) { - PK11_DestroyContext(digest->d.ctx, PR_TRUE); - } -#endif - size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); - memwipe(digest, 0, bytes); - tor_free(digest); -} - -/** Add <b>len</b> bytes from <b>data</b> to the digest object. - * - * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_add_bytess` - * C_RUST_COUPLED: `crypto::digest::Sha256::process` - */ -void -crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, - size_t len) -{ - tor_assert(digest); - tor_assert(data); - /* Using the SHA*_*() calls directly means we don't support doing - * SHA in hardware. But so far the delay of getting the question - * to the hardware, and hearing the answer, is likely higher than - * just doing it ourselves. Hashes are fast. - */ - switch (digest->algorithm) { -#ifdef ENABLE_NSS - case DIGEST_SHA1: /* fall through */ - case DIGEST_SHA256: /* fall through */ - case DIGEST_SHA512: - tor_assert(len <= UINT_MAX); - SECStatus s = PK11_DigestOp(digest->d.ctx, - (const unsigned char *)data, - (unsigned int)len); - tor_assert(s == SECSuccess); - break; -#else - case DIGEST_SHA1: - SHA1_Update(&digest->d.sha1, (void*)data, len); - break; - case DIGEST_SHA256: - SHA256_Update(&digest->d.sha2, (void*)data, len); - break; - case DIGEST_SHA512: - SHA512_Update(&digest->d.sha512, (void*)data, len); - break; -#endif - case DIGEST_SHA3_256: /* FALLSTHROUGH */ - case DIGEST_SHA3_512: - keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); - break; - default: - /* LCOV_EXCL_START */ - tor_fragile_assert(); - break; - /* LCOV_EXCL_STOP */ - } -} - -/** Compute the hash of the data that has been passed to the digest - * object; write the first out_len bytes of the result to <b>out</b>. - * <b>out_len</b> must be \<= DIGEST512_LEN. - * - * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_get_digest` - * C_RUST_COUPLED: `impl digest::FixedOutput for Sha256` - */ -void -crypto_digest_get_digest(crypto_digest_t *digest, - char *out, size_t out_len) -{ - unsigned char r[DIGEST512_LEN]; - tor_assert(digest); - tor_assert(out); - tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); - - /* The SHA-3 code handles copying into a temporary ctx, and also can handle - * short output buffers by truncating appropriately. */ - if (digest->algorithm == DIGEST_SHA3_256 || - digest->algorithm == DIGEST_SHA3_512) { - keccak_digest_sum(&digest->d.sha3, (uint8_t *)out, out_len); - return; - } - -#ifdef ENABLE_NSS - /* Copy into a temporary buffer since DigestFinal (alters) the context */ - unsigned char buf[1024]; - unsigned int saved_len = 0; - unsigned rlen; - unsigned char *saved = PK11_SaveContextAlloc(digest->d.ctx, - buf, sizeof(buf), - &saved_len); - tor_assert(saved); - SECStatus s = PK11_DigestFinal(digest->d.ctx, r, &rlen, sizeof(r)); - tor_assert(s == SECSuccess); - tor_assert(rlen >= out_len); - s = PK11_RestoreContext(digest->d.ctx, saved, saved_len); - tor_assert(s == SECSuccess); - if (saved != buf) { - PORT_ZFree(saved, saved_len); - } -#else - const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); - crypto_digest_t tmpenv; - /* memcpy into a temporary ctx, since SHA*_Final clears the context */ - memcpy(&tmpenv, digest, alloc_bytes); - switch (digest->algorithm) { - case DIGEST_SHA1: - SHA1_Final(r, &tmpenv.d.sha1); - break; - case DIGEST_SHA256: - SHA256_Final(r, &tmpenv.d.sha2); - break; - case DIGEST_SHA512: - SHA512_Final(r, &tmpenv.d.sha512); - break; -//LCOV_EXCL_START - case DIGEST_SHA3_256: /* FALLSTHROUGH */ - case DIGEST_SHA3_512: - default: - log_warn(LD_BUG, "Handling unexpected algorithm %d", digest->algorithm); - /* This is fatal, because it should never happen. */ - tor_assert_unreached(); - break; -//LCOV_EXCL_STOP - } -#endif - memcpy(out, r, out_len); - memwipe(r, 0, sizeof(r)); -} - -/** Allocate and return a new digest object with the same state as - * <b>digest</b> - * - * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_dup` - * C_RUST_COUPLED: `impl Clone for crypto::digest::Sha256` - */ -crypto_digest_t * -crypto_digest_dup(const crypto_digest_t *digest) -{ - tor_assert(digest); - const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); - crypto_digest_t *result = tor_memdup(digest, alloc_bytes); -#ifdef ENABLE_NSS - if (library_supports_digest(digest->algorithm)) { - result->d.ctx = PK11_CloneContext(digest->d.ctx); - } -#endif - return result; -} - -/** Temporarily save the state of <b>digest</b> in <b>checkpoint</b>. - * Asserts that <b>digest</b> is a SHA1 digest object. - */ -void -crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, - const crypto_digest_t *digest) -{ - const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); - tor_assert(bytes <= sizeof(checkpoint->mem)); -#ifdef ENABLE_NSS - if (library_supports_digest(digest->algorithm)) { - unsigned char *allocated; - allocated = PK11_SaveContextAlloc(digest->d.ctx, - (unsigned char *)checkpoint->mem, - sizeof(checkpoint->mem), - &checkpoint->bytes_used); - /* No allocation is allowed here. */ - tor_assert(allocated == checkpoint->mem); - return; - } -#endif - memcpy(checkpoint->mem, digest, bytes); -} - -/** Restore the state of <b>digest</b> from <b>checkpoint</b>. - * Asserts that <b>digest</b> is a SHA1 digest object. Requires that the - * state was previously stored with crypto_digest_checkpoint() */ -void -crypto_digest_restore(crypto_digest_t *digest, - const crypto_digest_checkpoint_t *checkpoint) -{ - const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); -#ifdef ENABLE_NSS - if (library_supports_digest(digest->algorithm)) { - SECStatus s = PK11_RestoreContext(digest->d.ctx, - (unsigned char *)checkpoint->mem, - checkpoint->bytes_used); - tor_assert(s == SECSuccess); - return; - } -#endif - memcpy(digest, checkpoint->mem, bytes); -} - -/** Replace the state of the digest object <b>into</b> with the state - * of the digest object <b>from</b>. Requires that 'into' and 'from' - * have the same digest type. - */ -void -crypto_digest_assign(crypto_digest_t *into, - const crypto_digest_t *from) -{ - tor_assert(into); - tor_assert(from); - tor_assert(into->algorithm == from->algorithm); - const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); -#ifdef ENABLE_NSS - if (library_supports_digest(from->algorithm)) { - PK11_DestroyContext(into->d.ctx, PR_TRUE); - into->d.ctx = PK11_CloneContext(from->d.ctx); - return; - } -#endif - memcpy(into,from,alloc_bytes); -} - -/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest - * at <b>digest_out</b> to the hash of the concatenation of those strings, - * plus the optional string <b>append</b>, computed with the algorithm - * <b>alg</b>. - * <b>out_len</b> must be \<= DIGEST512_LEN. */ -void -crypto_digest_smartlist(char *digest_out, size_t len_out, - const smartlist_t *lst, - const char *append, - digest_algorithm_t alg) -{ - crypto_digest_smartlist_prefix(digest_out, len_out, NULL, lst, append, alg); -} - -/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest - * at <b>digest_out</b> to the hash of the concatenation of: the - * optional string <b>prepend</b>, those strings, - * and the optional string <b>append</b>, computed with the algorithm - * <b>alg</b>. - * <b>len_out</b> must be \<= DIGEST512_LEN. */ -void -crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, - const char *prepend, - const smartlist_t *lst, - const char *append, - digest_algorithm_t alg) -{ - crypto_digest_t *d = crypto_digest_new_internal(alg); - if (prepend) - crypto_digest_add_bytes(d, prepend, strlen(prepend)); - SMARTLIST_FOREACH(lst, const char *, cp, - crypto_digest_add_bytes(d, cp, strlen(cp))); - if (append) - crypto_digest_add_bytes(d, append, strlen(append)); - crypto_digest_get_digest(d, digest_out, len_out); - crypto_digest_free(d); -} - -/** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using - * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte - * result in <b>hmac_out</b>. Asserts on failure. - */ -void -crypto_hmac_sha256(char *hmac_out, - const char *key, size_t key_len, - const char *msg, size_t msg_len) -{ - /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ - tor_assert(key_len < INT_MAX); - tor_assert(msg_len < INT_MAX); - tor_assert(hmac_out); -#ifdef ENABLE_NSS - PK11SlotInfo *slot = NULL; - PK11SymKey *symKey = NULL; - PK11Context *hmac = NULL; - - int ok = 0; - SECStatus s; - SECItem keyItem, paramItem; - keyItem.data = (unsigned char *)key; - keyItem.len = (unsigned)key_len; - paramItem.type = siBuffer; - paramItem.data = NULL; - paramItem.len = 0; - - slot = PK11_GetBestSlot(CKM_SHA256_HMAC, NULL); - if (!slot) - goto done; - symKey = PK11_ImportSymKey(slot, CKM_SHA256_HMAC, - PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL); - if (!symKey) - goto done; - - hmac = PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, symKey, - ¶mItem); - if (!hmac) - goto done; - s = PK11_DigestBegin(hmac); - if (s != SECSuccess) - goto done; - s = PK11_DigestOp(hmac, (const unsigned char *)msg, (unsigned int)msg_len); - if (s != SECSuccess) - goto done; - unsigned int len=0; - s = PK11_DigestFinal(hmac, (unsigned char *)hmac_out, &len, DIGEST256_LEN); - if (s != SECSuccess || len != DIGEST256_LEN) - goto done; - ok = 1; - - done: - if (hmac) - PK11_DestroyContext(hmac, PR_TRUE); - if (symKey) - PK11_FreeSymKey(symKey); - if (slot) - PK11_FreeSlot(slot); - - tor_assert(ok); -#else - unsigned char *rv = NULL; - rv = HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, - (unsigned char*)hmac_out, NULL); - tor_assert(rv); -#endif -} - /** Compute a MAC using SHA3-256 of <b>msg_len</b> bytes in <b>msg</b> using a * <b>key</b> of length <b>key_len</b> and a <b>salt</b> of length * <b>salt_len</b>. Store the result of <b>len_out</b> bytes in in @@ -779,7 +135,23 @@ crypto_mac_sha3_256(uint8_t *mac_out, size_t len_out, /** Internal state for a eXtendable-Output Function (XOF). */ struct crypto_xof_t { +#ifdef OPENSSL_HAS_SHAKE3_EVP + /* XXXX We can't enable this yet, because OpenSSL's + * DigestFinalXOF function can't be called repeatedly on the same + * XOF. + * + * We could in theory use the undocumented SHA3_absorb and SHA3_squeeze + * functions, but let's not mess with undocumented OpenSSL internals any + * more than we have to. + * + * We could also revise our XOF code so that it only allows a single + * squeeze operation; we don't require streaming squeeze operations + * outside the tests yet. + */ + EVP_MD_CTX *ctx; +#else keccak_state s; +#endif }; /** Allocate a new XOF object backed by SHAKE-256. The security level @@ -792,7 +164,14 @@ crypto_xof_new(void) { crypto_xof_t *xof; xof = tor_malloc(sizeof(crypto_xof_t)); +#ifdef OPENSSL_HAS_SHAKE256 + xof->ctx = EVP_MD_CTX_new(); + tor_assert(xof->ctx); + int r = EVP_DigestInit(xof->ctx, EVP_shake256()); + tor_assert(r == 1); +#else keccak_xof_init(&xof->s, 256); +#endif return xof; } @@ -803,8 +182,13 @@ crypto_xof_new(void) void crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len) { +#ifdef OPENSSL_HAS_SHAKE256 + int r = EVP_DigestUpdate(xof->ctx, data, len); + tor_assert(r == 1); +#else int i = keccak_xof_absorb(&xof->s, data, len); tor_assert(i == 0); +#endif } /** Squeeze bytes out of a XOF object. Calling this routine will render @@ -813,8 +197,13 @@ crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len) void crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len) { +#ifdef OPENSSL_HAS_SHAKE256 + int r = EVP_DigestFinalXOF(xof->ctx, out, len); + tor_assert(r == 1); +#else int i = keccak_xof_squeeze(&xof->s, out, len); tor_assert(i == 0); +#endif } /** Cleanse and deallocate a XOF object. */ @@ -823,6 +212,34 @@ crypto_xof_free_(crypto_xof_t *xof) { if (!xof) return; +#ifdef OPENSSL_HAS_SHAKE256 + if (xof->ctx) + EVP_MD_CTX_free(xof->ctx); +#endif memwipe(xof, 0, sizeof(crypto_xof_t)); tor_free(xof); } + +/** Compute the XOF (SHAKE256) of a <b>input_len</b> bytes at <b>input</b>, + * putting <b>output_len</b> bytes at <b>output</b>. */ +void +crypto_xof(uint8_t *output, size_t output_len, + const uint8_t *input, size_t input_len) +{ +#ifdef OPENSSL_HAS_SHA3 + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + tor_assert(ctx); + int r = EVP_DigestInit(ctx, EVP_shake256()); + tor_assert(r == 1); + r = EVP_DigestUpdate(ctx, input, input_len); + tor_assert(r == 1); + r = EVP_DigestFinalXOF(ctx, output, output_len); + tor_assert(r == 1); + EVP_MD_CTX_free(ctx); +#else + crypto_xof_t *xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, input, input_len); + crypto_xof_squeeze_bytes(xof, output, output_len); + crypto_xof_free(xof); +#endif +} diff --git a/src/lib/crypt_ops/crypto_digest.h b/src/lib/crypt_ops/crypto_digest.h index 47e60ce617..5869db7800 100644 --- a/src/lib/crypt_ops/crypto_digest.h +++ b/src/lib/crypt_ops/crypto_digest.h @@ -124,6 +124,8 @@ void crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len); void crypto_xof_free_(crypto_xof_t *xof); #define crypto_xof_free(xof) \ FREE_AND_NULL(crypto_xof_t, crypto_xof_free_, (xof)) +void crypto_xof(uint8_t *output, size_t output_len, + const uint8_t *input, size_t input_len); #ifdef TOR_UNIT_TESTS digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest); diff --git a/src/lib/crypt_ops/crypto_digest_nss.c b/src/lib/crypt_ops/crypto_digest_nss.c new file mode 100644 index 0000000000..b73f0736fd --- /dev/null +++ b/src/lib/crypt_ops/crypto_digest_nss.c @@ -0,0 +1,560 @@ +/* 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 crypto_digest_nss.c + * \brief Block of functions related with digest and xof utilities and + * operations (NSS specific implementations). + **/ + +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include "keccak-tiny/keccak-tiny.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <pk11pub.h> +ENABLE_GCC_WARNING(strict-prototypes) + +/** + * Convert a digest_algorithm_t (used by tor) to a HashType (used by NSS). + * On failure, return SEC_OID_UNKNOWN. */ +static SECOidTag +digest_alg_to_nss_oid(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: return SEC_OID_SHA1; + case DIGEST_SHA256: return SEC_OID_SHA256; + case DIGEST_SHA512: return SEC_OID_SHA512; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return SEC_OID_UNKNOWN; + } +} + +/* Helper: get an unkeyed digest via pk11wrap */ +static int +digest_nss_internal(SECOidTag alg, + char *digest, unsigned len_out, + const char *msg, size_t msg_len) +{ + if (alg == SEC_OID_UNKNOWN) + return -1; + tor_assert(msg_len <= UINT_MAX); + + int rv = -1; + SECStatus s; + PK11Context *ctx = PK11_CreateDigestContext(alg); + if (!ctx) + return -1; + + s = PK11_DigestBegin(ctx); + if (s != SECSuccess) + goto done; + + s = PK11_DigestOp(ctx, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + + unsigned int len = 0; + s = PK11_DigestFinal(ctx, (unsigned char *)digest, &len, len_out); + if (s != SECSuccess) + goto done; + + rv = 0; + done: + PK11_DestroyContext(ctx, PR_TRUE); + return rv; +} + +/** True iff alg is implemented in our crypto library, and we want to use that + * implementation */ +static bool +library_supports_digest(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: /* Fall through */ + return true; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return false; + } +} + +/* Crypto digest functions */ + +/** Compute the SHA1 digest of the <b>len</b> bytes on data stored in + * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_digest,(char *digest, const char *m, size_t len)) +{ + tor_assert(m); + tor_assert(digest); + return digest_nss_internal(SEC_OID_SHA1, digest, DIGEST_LEN, m, len); +} + +/** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest256(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + + int ret = 0; + if (algorithm == DIGEST_SHA256) { + return digest_nss_internal(SEC_OID_SHA256, digest, DIGEST256_LEN, m, len); + } else { + ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) + > -1); + } + + if (!ret) + return -1; + return 0; +} + +/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + + int ret = 0; + if (algorithm == DIGEST_SHA512) { + return digest_nss_internal(SEC_OID_SHA512, digest, DIGEST512_LEN, m, len); + } else { + ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) + > -1); + } + + if (!ret) + return -1; + return 0; +} + +/** Intermediate information about the digest of a stream of data. */ +struct crypto_digest_t { + digest_algorithm_t algorithm; /**< Which algorithm is in use? */ + /** State for the digest we're using. Only one member of the + * union is usable, depending on the value of <b>algorithm</b>. Note also + * that space for other members might not even be allocated! + */ + union { + PK11Context *ctx; + keccak_state sha3; /**< state for SHA3-[256,512] */ + } d; +}; + +#ifdef TOR_UNIT_TESTS + +digest_algorithm_t +crypto_digest_get_algorithm(crypto_digest_t *digest) +{ + tor_assert(digest); + + return digest->algorithm; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Return the number of bytes we need to malloc in order to get a + * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe + * when we free one. + */ +static size_t +crypto_digest_alloc_bytes(digest_algorithm_t alg) +{ + /* Helper: returns the number of bytes in the 'f' field of 'st' */ +#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) + /* Gives the length of crypto_digest_t through the end of the field 'd' */ +#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ + STRUCT_FIELD_SIZE(crypto_digest_t, f)) + switch (alg) { + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: + return END_OF_FIELD(d.ctx); + case DIGEST_SHA3_256: + case DIGEST_SHA3_512: + return END_OF_FIELD(d.sha3); + default: + tor_assert(0); // LCOV_EXCL_LINE + return 0; // LCOV_EXCL_LINE + } +#undef END_OF_FIELD +#undef STRUCT_FIELD_SIZE +} + +/** + * Internal function: create and return a new digest object for 'algorithm'. + * Does not typecheck the algorithm. + */ +static crypto_digest_t * +crypto_digest_new_internal(digest_algorithm_t algorithm) +{ + crypto_digest_t *r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); + r->algorithm = algorithm; + + switch (algorithm) + { + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + r->d.ctx = PK11_CreateDigestContext(digest_alg_to_nss_oid(algorithm)); + if (BUG(!r->d.ctx)) { + tor_free(r); + return NULL; + } + if (BUG(SECSuccess != PK11_DigestBegin(r->d.ctx))) { + crypto_digest_free(r); + return NULL; + } + break; + case DIGEST_SHA3_256: + keccak_digest_init(&r->d.sha3, 256); + break; + case DIGEST_SHA3_512: + keccak_digest_init(&r->d.sha3, 512); + break; + default: + tor_assert_unreached(); + } + + return r; +} + +/** Allocate and return a new digest object to compute SHA1 digests. + */ +crypto_digest_t * +crypto_digest_new(void) +{ + return crypto_digest_new_internal(DIGEST_SHA1); +} + +/** Allocate and return a new digest object to compute 256-bit digests + * using <b>algorithm</b>. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest256_new` + * C_RUST_COUPLED: `crypto::digest::Sha256::default` + */ +crypto_digest_t * +crypto_digest256_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + return crypto_digest_new_internal(algorithm); +} + +/** Allocate and return a new digest object to compute 512-bit digests + * using <b>algorithm</b>. */ +crypto_digest_t * +crypto_digest512_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + return crypto_digest_new_internal(algorithm); +} + +/** Deallocate a digest object. + */ +void +crypto_digest_free_(crypto_digest_t *digest) +{ + if (!digest) + return; + if (library_supports_digest(digest->algorithm)) { + PK11_DestroyContext(digest->d.ctx, PR_TRUE); + } + size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + memwipe(digest, 0, bytes); + tor_free(digest); +} + +/** Add <b>len</b> bytes from <b>data</b> to the digest object. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_add_bytess` + * C_RUST_COUPLED: `crypto::digest::Sha256::process` + */ +void +crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, + size_t len) +{ + tor_assert(digest); + tor_assert(data); + /* Using the SHA*_*() calls directly means we don't support doing + * SHA in hardware. But so far the delay of getting the question + * to the hardware, and hearing the answer, is likely higher than + * just doing it ourselves. Hashes are fast. + */ + switch (digest->algorithm) { + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + tor_assert(len <= UINT_MAX); + SECStatus s = PK11_DigestOp(digest->d.ctx, + (const unsigned char *)data, + (unsigned int)len); + tor_assert(s == SECSuccess); + break; + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); + break; + default: + /* LCOV_EXCL_START */ + tor_fragile_assert(); + break; + /* LCOV_EXCL_STOP */ + } +} + +/** Compute the hash of the data that has been passed to the digest + * object; write the first out_len bytes of the result to <b>out</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_get_digest` + * C_RUST_COUPLED: `impl digest::FixedOutput for Sha256` + */ +void +crypto_digest_get_digest(crypto_digest_t *digest, + char *out, size_t out_len) +{ + unsigned char r[DIGEST512_LEN]; + tor_assert(digest); + tor_assert(out); + tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); + + /* The SHA-3 code handles copying into a temporary ctx, and also can handle + * short output buffers by truncating appropriately. */ + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { + keccak_digest_sum(&digest->d.sha3, (uint8_t *)out, out_len); + return; + } + + /* Copy into a temporary buffer since DigestFinal (alters) the context */ + unsigned char buf[1024]; + unsigned int saved_len = 0; + unsigned rlen; + unsigned char *saved = PK11_SaveContextAlloc(digest->d.ctx, + buf, sizeof(buf), + &saved_len); + tor_assert(saved); + SECStatus s = PK11_DigestFinal(digest->d.ctx, r, &rlen, sizeof(r)); + tor_assert(s == SECSuccess); + tor_assert(rlen >= out_len); + s = PK11_RestoreContext(digest->d.ctx, saved, saved_len); + tor_assert(s == SECSuccess); + + if (saved != buf) { + PORT_ZFree(saved, saved_len); + } + memcpy(out, r, out_len); + memwipe(r, 0, sizeof(r)); +} + +/** Allocate and return a new digest object with the same state as + * <b>digest</b> + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_dup` + * C_RUST_COUPLED: `impl Clone for crypto::digest::Sha256` + */ +crypto_digest_t * +crypto_digest_dup(const crypto_digest_t *digest) +{ + tor_assert(digest); + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t *result = tor_memdup(digest, alloc_bytes); + + if (library_supports_digest(digest->algorithm)) { + result->d.ctx = PK11_CloneContext(digest->d.ctx); + } + + return result; +} + +/** Temporarily save the state of <b>digest</b> in <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. + */ +void +crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, + const crypto_digest_t *digest) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + tor_assert(bytes <= sizeof(checkpoint->mem)); + if (library_supports_digest(digest->algorithm)) { + unsigned char *allocated; + allocated = PK11_SaveContextAlloc(digest->d.ctx, + (unsigned char *)checkpoint->mem, + sizeof(checkpoint->mem), + &checkpoint->bytes_used); + /* No allocation is allowed here. */ + tor_assert(allocated == checkpoint->mem); + return; + } + memcpy(checkpoint->mem, digest, bytes); +} + +/** Restore the state of <b>digest</b> from <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. Requires that the + * state was previously stored with crypto_digest_checkpoint() */ +void +crypto_digest_restore(crypto_digest_t *digest, + const crypto_digest_checkpoint_t *checkpoint) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + if (library_supports_digest(digest->algorithm)) { + SECStatus s = PK11_RestoreContext(digest->d.ctx, + (unsigned char *)checkpoint->mem, + checkpoint->bytes_used); + tor_assert(s == SECSuccess); + return; + } + memcpy(digest, checkpoint->mem, bytes); +} + +/** Replace the state of the digest object <b>into</b> with the state + * of the digest object <b>from</b>. Requires that 'into' and 'from' + * have the same digest type. + */ +void +crypto_digest_assign(crypto_digest_t *into, + const crypto_digest_t *from) +{ + tor_assert(into); + tor_assert(from); + tor_assert(into->algorithm == from->algorithm); + const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); + if (library_supports_digest(from->algorithm)) { + PK11_DestroyContext(into->d.ctx, PR_TRUE); + into->d.ctx = PK11_CloneContext(from->d.ctx); + return; + } + memcpy(into,from,alloc_bytes); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of those strings, + * plus the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist(char *digest_out, size_t len_out, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_smartlist_prefix(digest_out, len_out, NULL, lst, append, alg); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of: the + * optional string <b>prepend</b>, those strings, + * and the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>len_out</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, + const char *prepend, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_t *d = crypto_digest_new_internal(alg); + if (prepend) + crypto_digest_add_bytes(d, prepend, strlen(prepend)); + SMARTLIST_FOREACH(lst, const char *, cp, + crypto_digest_add_bytes(d, cp, strlen(cp))); + if (append) + crypto_digest_add_bytes(d, append, strlen(append)); + crypto_digest_get_digest(d, digest_out, len_out); + crypto_digest_free(d); +} + +/** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using + * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte + * result in <b>hmac_out</b>. Asserts on failure. + */ +void +crypto_hmac_sha256(char *hmac_out, + const char *key, size_t key_len, + const char *msg, size_t msg_len) +{ + /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ + tor_assert(key_len < INT_MAX); + tor_assert(msg_len < INT_MAX); + tor_assert(hmac_out); + + PK11SlotInfo *slot = NULL; + PK11SymKey *symKey = NULL; + PK11Context *hmac = NULL; + + int ok = 0; + SECStatus s; + SECItem keyItem, paramItem; + keyItem.data = (unsigned char *)key; + keyItem.len = (unsigned)key_len; + paramItem.type = siBuffer; + paramItem.data = NULL; + paramItem.len = 0; + + slot = PK11_GetBestSlot(CKM_SHA256_HMAC, NULL); + if (!slot) + goto done; + symKey = PK11_ImportSymKey(slot, CKM_SHA256_HMAC, + PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL); + if (!symKey) + goto done; + + hmac = PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, symKey, + ¶mItem); + if (!hmac) + goto done; + s = PK11_DigestBegin(hmac); + if (s != SECSuccess) + goto done; + s = PK11_DigestOp(hmac, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + unsigned int len=0; + s = PK11_DigestFinal(hmac, (unsigned char *)hmac_out, &len, DIGEST256_LEN); + if (s != SECSuccess || len != DIGEST256_LEN) + goto done; + ok = 1; + + done: + if (hmac) + PK11_DestroyContext(hmac, PR_TRUE); + if (symKey) + PK11_FreeSymKey(symKey); + if (slot) + PK11_FreeSlot(slot); + + tor_assert(ok); +} + diff --git a/src/lib/crypt_ops/crypto_digest_openssl.c b/src/lib/crypt_ops/crypto_digest_openssl.c new file mode 100644 index 0000000000..a1c92351fc --- /dev/null +++ b/src/lib/crypt_ops/crypto_digest_openssl.c @@ -0,0 +1,522 @@ +/* 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 crypto_digest_openssl.c + * \brief Block of functions related with digest and xof utilities and + * operations (OpenSSL specific implementations). + **/ + +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include "keccak-tiny/keccak-tiny.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" + +#include "lib/crypt_ops/crypto_openssl_mgt.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/hmac.h> +#include <openssl/sha.h> + +ENABLE_GCC_WARNING(redundant-decls) + +/* Crypto digest functions */ + +/** Compute the SHA1 digest of the <b>len</b> bytes on data stored in + * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_digest,(char *digest, const char *m, size_t len)) +{ + tor_assert(m); + tor_assert(digest); + if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL) { + return -1; + } + return 0; +} + +/** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest256(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + + int ret = 0; + if (algorithm == DIGEST_SHA256) { + ret = (SHA256((const uint8_t*)m,len,(uint8_t*)digest) != NULL); + } else { +#ifdef OPENSSL_HAS_SHA3 + unsigned int dlen = DIGEST256_LEN; + ret = EVP_Digest(m, len, (uint8_t*)digest, &dlen, EVP_sha3_256(), NULL); +#else + ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) + > -1); +#endif + } + + if (!ret) + return -1; + return 0; +} + +/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + + int ret = 0; + if (algorithm == DIGEST_SHA512) { + ret = (SHA512((const unsigned char*)m,len,(unsigned char*)digest) + != NULL); + } else { +#ifdef OPENSSL_HAS_SHA3 + unsigned int dlen = DIGEST512_LEN; + ret = EVP_Digest(m, len, (uint8_t*)digest, &dlen, EVP_sha3_512(), NULL); +#else + ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) + > -1); +#endif + } + + if (!ret) + return -1; + return 0; +} + +/** Intermediate information about the digest of a stream of data. */ +struct crypto_digest_t { + digest_algorithm_t algorithm; /**< Which algorithm is in use? */ + /** State for the digest we're using. Only one member of the + * union is usable, depending on the value of <b>algorithm</b>. Note also + * that space for other members might not even be allocated! + */ + union { + SHA_CTX sha1; /**< state for SHA1 */ + SHA256_CTX sha2; /**< state for SHA256 */ + SHA512_CTX sha512; /**< state for SHA512 */ +#ifdef OPENSSL_HAS_SHA3 + EVP_MD_CTX *md; +#else + keccak_state sha3; /**< state for SHA3-[256,512] */ +#endif + } d; +}; + +#ifdef TOR_UNIT_TESTS + +digest_algorithm_t +crypto_digest_get_algorithm(crypto_digest_t *digest) +{ + tor_assert(digest); + + return digest->algorithm; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Return the number of bytes we need to malloc in order to get a + * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe + * when we free one. + */ +static size_t +crypto_digest_alloc_bytes(digest_algorithm_t alg) +{ + /* Helper: returns the number of bytes in the 'f' field of 'st' */ +#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) + /* Gives the length of crypto_digest_t through the end of the field 'd' */ +#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ + STRUCT_FIELD_SIZE(crypto_digest_t, f)) + switch (alg) { + case DIGEST_SHA1: + return END_OF_FIELD(d.sha1); + case DIGEST_SHA256: + return END_OF_FIELD(d.sha2); + case DIGEST_SHA512: + return END_OF_FIELD(d.sha512); +#ifdef OPENSSL_HAS_SHA3 + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: + return END_OF_FIELD(d.md); +#else + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: + return END_OF_FIELD(d.sha3); +#endif + default: + tor_assert(0); // LCOV_EXCL_LINE + return 0; // LCOV_EXCL_LINE + } +#undef END_OF_FIELD +#undef STRUCT_FIELD_SIZE +} + +/** + * Internal function: create and return a new digest object for 'algorithm'. + * Does not typecheck the algorithm. + */ +static crypto_digest_t * +crypto_digest_new_internal(digest_algorithm_t algorithm) +{ + crypto_digest_t *r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); + r->algorithm = algorithm; + + switch (algorithm) + { + case DIGEST_SHA1: + SHA1_Init(&r->d.sha1); + break; + case DIGEST_SHA256: + SHA256_Init(&r->d.sha2); + break; + case DIGEST_SHA512: + SHA512_Init(&r->d.sha512); + break; +#ifdef OPENSSL_HAS_SHA3 + case DIGEST_SHA3_256: + r->d.md = EVP_MD_CTX_new(); + if (!EVP_DigestInit(r->d.md, EVP_sha3_256())) { + crypto_digest_free(r); + return NULL; + } + break; + case DIGEST_SHA3_512: + r->d.md = EVP_MD_CTX_new(); + if (!EVP_DigestInit(r->d.md, EVP_sha3_512())) { + crypto_digest_free(r); + return NULL; + } + break; +#else + case DIGEST_SHA3_256: + keccak_digest_init(&r->d.sha3, 256); + break; + case DIGEST_SHA3_512: + keccak_digest_init(&r->d.sha3, 512); + break; +#endif + default: + tor_assert_unreached(); + } + + return r; +} + +/** Allocate and return a new digest object to compute SHA1 digests. + */ +crypto_digest_t * +crypto_digest_new(void) +{ + return crypto_digest_new_internal(DIGEST_SHA1); +} + +/** Allocate and return a new digest object to compute 256-bit digests + * using <b>algorithm</b>. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest256_new` + * C_RUST_COUPLED: `crypto::digest::Sha256::default` + */ +crypto_digest_t * +crypto_digest256_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + return crypto_digest_new_internal(algorithm); +} + +/** Allocate and return a new digest object to compute 512-bit digests + * using <b>algorithm</b>. */ +crypto_digest_t * +crypto_digest512_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + return crypto_digest_new_internal(algorithm); +} + +/** Deallocate a digest object. + */ +void +crypto_digest_free_(crypto_digest_t *digest) +{ + if (!digest) + return; +#ifdef OPENSSL_HAS_SHA3 + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { + if (digest->d.md) { + EVP_MD_CTX_free(digest->d.md); + } + } +#endif + size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + memwipe(digest, 0, bytes); + tor_free(digest); +} + +/** Add <b>len</b> bytes from <b>data</b> to the digest object. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_add_bytess` + * C_RUST_COUPLED: `crypto::digest::Sha256::process` + */ +void +crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, + size_t len) +{ + tor_assert(digest); + tor_assert(data); + /* Using the SHA*_*() calls directly means we don't support doing + * SHA in hardware. But so far the delay of getting the question + * to the hardware, and hearing the answer, is likely higher than + * just doing it ourselves. Hashes are fast. + */ + switch (digest->algorithm) { + case DIGEST_SHA1: + SHA1_Update(&digest->d.sha1, (void*)data, len); + break; + case DIGEST_SHA256: + SHA256_Update(&digest->d.sha2, (void*)data, len); + break; + case DIGEST_SHA512: + SHA512_Update(&digest->d.sha512, (void*)data, len); + break; +#ifdef OPENSSL_HAS_SHA3 + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: { + int r = EVP_DigestUpdate(digest->d.md, data, len); + tor_assert(r); + } + break; +#else + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); + break; +#endif + default: + /* LCOV_EXCL_START */ + tor_fragile_assert(); + break; + /* LCOV_EXCL_STOP */ + } +} + +/** Compute the hash of the data that has been passed to the digest + * object; write the first out_len bytes of the result to <b>out</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_get_digest` + * C_RUST_COUPLED: `impl digest::FixedOutput for Sha256` + */ +void +crypto_digest_get_digest(crypto_digest_t *digest, + char *out, size_t out_len) +{ + unsigned char r[DIGEST512_LEN]; + tor_assert(digest); + tor_assert(out); + tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); + + /* The SHA-3 code handles copying into a temporary ctx, and also can handle + * short output buffers by truncating appropriately. */ + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { +#ifdef OPENSSL_HAS_SHA3 + unsigned dlen = (unsigned) + crypto_digest_algorithm_get_length(digest->algorithm); + EVP_MD_CTX *tmp = EVP_MD_CTX_new(); + EVP_MD_CTX_copy(tmp, digest->d.md); + memset(r, 0xff, sizeof(r)); + int res = EVP_DigestFinal(tmp, r, &dlen); + EVP_MD_CTX_free(tmp); + tor_assert(res == 1); + goto done; +#else + /* Tiny-Keccak handles copying into a temporary ctx, and also can handle + * short output buffers by truncating appropriately. */ + keccak_digest_sum(&digest->d.sha3, (uint8_t *)out, out_len); + return; +#endif + } + + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t tmpenv; + /* memcpy into a temporary ctx, since SHA*_Final clears the context */ + memcpy(&tmpenv, digest, alloc_bytes); + switch (digest->algorithm) { + case DIGEST_SHA1: + SHA1_Final(r, &tmpenv.d.sha1); + break; + case DIGEST_SHA256: + SHA256_Final(r, &tmpenv.d.sha2); + break; + case DIGEST_SHA512: + SHA512_Final(r, &tmpenv.d.sha512); + break; +//LCOV_EXCL_START + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + default: + log_warn(LD_BUG, "Handling unexpected algorithm %d", digest->algorithm); + /* This is fatal, because it should never happen. */ + tor_assert_unreached(); + break; +//LCOV_EXCL_STOP + } +#ifdef OPENSSL_HAS_SHA3 + done: +#endif + memcpy(out, r, out_len); + memwipe(r, 0, sizeof(r)); +} + +/** Allocate and return a new digest object with the same state as + * <b>digest</b> + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_dup` + * C_RUST_COUPLED: `impl Clone for crypto::digest::Sha256` + */ +crypto_digest_t * +crypto_digest_dup(const crypto_digest_t *digest) +{ + tor_assert(digest); + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t *result = tor_memdup(digest, alloc_bytes); + +#ifdef OPENSSL_HAS_SHA3 + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { + result->d.md = EVP_MD_CTX_new(); + EVP_MD_CTX_copy(result->d.md, digest->d.md); + } +#endif + return result; +} + +/** Temporarily save the state of <b>digest</b> in <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. + */ +void +crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, + const crypto_digest_t *digest) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + tor_assert(bytes <= sizeof(checkpoint->mem)); + memcpy(checkpoint->mem, digest, bytes); +} + +/** Restore the state of <b>digest</b> from <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. Requires that the + * state was previously stored with crypto_digest_checkpoint() */ +void +crypto_digest_restore(crypto_digest_t *digest, + const crypto_digest_checkpoint_t *checkpoint) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + memcpy(digest, checkpoint->mem, bytes); +} + +/** Replace the state of the digest object <b>into</b> with the state + * of the digest object <b>from</b>. Requires that 'into' and 'from' + * have the same digest type. + */ +void +crypto_digest_assign(crypto_digest_t *into, + const crypto_digest_t *from) +{ + tor_assert(into); + tor_assert(from); + tor_assert(into->algorithm == from->algorithm); + const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); + +#ifdef OPENSSL_HAS_SHA3 + if (from->algorithm == DIGEST_SHA3_256 || + from->algorithm == DIGEST_SHA3_512) { + EVP_MD_CTX_copy(into->d.md, from->d.md); + return; + } +#endif + + memcpy(into,from,alloc_bytes); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of those strings, + * plus the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist(char *digest_out, size_t len_out, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_smartlist_prefix(digest_out, len_out, NULL, lst, append, alg); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of: the + * optional string <b>prepend</b>, those strings, + * and the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>len_out</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, + const char *prepend, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_t *d = crypto_digest_new_internal(alg); + if (prepend) + crypto_digest_add_bytes(d, prepend, strlen(prepend)); + SMARTLIST_FOREACH(lst, const char *, cp, + crypto_digest_add_bytes(d, cp, strlen(cp))); + if (append) + crypto_digest_add_bytes(d, append, strlen(append)); + crypto_digest_get_digest(d, digest_out, len_out); + crypto_digest_free(d); +} + +/** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using + * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte + * result in <b>hmac_out</b>. Asserts on failure. + */ +void +crypto_hmac_sha256(char *hmac_out, + const char *key, size_t key_len, + const char *msg, size_t msg_len) +{ + /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ + tor_assert(key_len < INT_MAX); + tor_assert(msg_len < INT_MAX); + tor_assert(hmac_out); + unsigned char *rv = NULL; + rv = HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, + (unsigned char*)hmac_out, NULL); + tor_assert(rv); +} + diff --git a/src/lib/crypt_ops/crypto_init.c b/src/lib/crypt_ops/crypto_init.c index 4040085c76..cf491f32d1 100644 --- a/src/lib/crypt_ops/crypto_init.c +++ b/src/lib/crypt_ops/crypto_init.c @@ -12,6 +12,8 @@ #include "orconfig.h" +#define CRYPTO_PRIVATE + #include "lib/crypt_ops/crypto_init.h" #include "lib/crypt_ops/crypto_curve25519.h" @@ -69,6 +71,8 @@ crypto_early_init(void) if (crypto_init_siphash_key() < 0) return -1; + crypto_rand_fast_init(); + curve25519_init(); ed25519_init(); } @@ -111,6 +115,7 @@ crypto_thread_cleanup(void) #ifdef ENABLE_OPENSSL crypto_openssl_thread_cleanup(); #endif + destroy_thread_fast_rng(); } /** @@ -129,6 +134,8 @@ crypto_global_cleanup(void) crypto_nss_global_cleanup(); #endif + crypto_rand_fast_shutdown(); + crypto_early_initialized_ = 0; crypto_global_initialized_ = 0; have_seeded_siphash = 0; diff --git a/src/lib/crypt_ops/crypto_rand.h b/src/lib/crypt_ops/crypto_rand.h index 8a81a4acdc..c51d6a4480 100644 --- a/src/lib/crypt_ops/crypto_rand.h +++ b/src/lib/crypt_ops/crypto_rand.h @@ -66,8 +66,29 @@ void crypto_fast_rng_free_(crypto_fast_rng_t *); unsigned crypto_fast_rng_get_uint(crypto_fast_rng_t *rng, unsigned limit); uint64_t crypto_fast_rng_get_uint64(crypto_fast_rng_t *rng, uint64_t limit); +uint32_t crypto_fast_rng_get_u32(crypto_fast_rng_t *rng); +uint64_t crypto_fast_rng_uint64_range(crypto_fast_rng_t *rng, + uint64_t min, uint64_t max); double crypto_fast_rng_get_double(crypto_fast_rng_t *rng); +/** + * Using the fast_rng <b>rng</b>, yield true with probability + * 1/<b>n</b>. Otherwise yield false. + * + * <b>n</b> must not be zero. + **/ +#define crypto_fast_rng_one_in_n(rng, n) \ + (0 == (crypto_fast_rng_get_uint((rng), (n)))) + +crypto_fast_rng_t *get_thread_fast_rng(void); + +#ifdef CRYPTO_PRIVATE +/* These are only used from crypto_init.c */ +void destroy_thread_fast_rng(void); +void crypto_rand_fast_init(void); +void crypto_rand_fast_shutdown(void); +#endif + #if defined(TOR_UNIT_TESTS) /* Used for white-box testing */ size_t crypto_fast_rng_get_bytes_used_per_stream(void); diff --git a/src/lib/crypt_ops/crypto_rand_fast.c b/src/lib/crypt_ops/crypto_rand_fast.c index 34e763bf51..760e1025ed 100644 --- a/src/lib/crypt_ops/crypto_rand_fast.c +++ b/src/lib/crypt_ops/crypto_rand_fast.c @@ -33,6 +33,7 @@ */ #define CRYPTO_RAND_FAST_PRIVATE +#define CRYPTO_PRIVATE #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_cipher.h" @@ -41,6 +42,7 @@ #include "lib/intmath/cmp.h" #include "lib/cc/ctassert.h" #include "lib/malloc/map_anon.h" +#include "lib/thread/threads.h" #include "lib/log/util_bug.h" @@ -122,7 +124,8 @@ crypto_fast_rng_new(void) * long. * * Note that this object is NOT thread-safe. If you need a thread-safe - * prng, use crypto_rand(), or wrap this in a mutex. + * prng, you should probably look at get_thread_fast_rng(). Alternatively, + * use crypto_rand(), wrap this in a mutex. **/ crypto_fast_rng_t * crypto_fast_rng_new_from_seed(const uint8_t *seed) @@ -261,3 +264,65 @@ crypto_fast_rng_get_bytes_used_per_stream(void) return BUFLEN; } #endif + +/** + * Thread-local instance for our fast RNG. + **/ +static tor_threadlocal_t thread_rng; + +/** + * Return a per-thread fast RNG, initializing it if necessary. + * + * You do not need to free this yourself. + * + * It is NOT safe to share this value across threads. + **/ +crypto_fast_rng_t * +get_thread_fast_rng(void) +{ + crypto_fast_rng_t *rng = tor_threadlocal_get(&thread_rng); + + if (PREDICT_UNLIKELY(rng == NULL)) { + rng = crypto_fast_rng_new(); + tor_threadlocal_set(&thread_rng, rng); + } + + return rng; +} + +/** + * Used when a thread is exiting: free the per-thread fast RNG if needed. + * Invoked from the crypto subsystem's thread-cleanup code. + **/ +void +destroy_thread_fast_rng(void) +{ + crypto_fast_rng_t *rng = tor_threadlocal_get(&thread_rng); + if (!rng) + return; + crypto_fast_rng_free(rng); + tor_threadlocal_set(&thread_rng, NULL); +} + +/** + * Initialize the global thread-local key that will be used to keep track + * of per-thread fast RNG instances. Called from the crypto subsystem's + * initialization code. + **/ +void +crypto_rand_fast_init(void) +{ + tor_threadlocal_init(&thread_rng); +} + +/** + * Initialize the global thread-local key that will be used to keep track + * of per-thread fast RNG instances. Called from the crypto subsystem's + * shutdown code. + **/ +void +crypto_rand_fast_shutdown(void) +{ + destroy_thread_fast_rng(); + tor_threadlocal_destroy(&thread_rng); +} diff --git a/src/lib/crypt_ops/crypto_rand_numeric.c b/src/lib/crypt_ops/crypto_rand_numeric.c index d02c5cdcfa..ffbfa2d56c 100644 --- a/src/lib/crypt_ops/crypto_rand_numeric.c +++ b/src/lib/crypt_ops/crypto_rand_numeric.c @@ -155,7 +155,34 @@ crypto_fast_rng_get_uint64(crypto_fast_rng_t *rng, uint64_t limit) } /** - * As crypto_rand_, but extract the result from a crypto_fast_rng_t. + * As crypto_rand_u32, but extract the result from a crypto_fast_rng_t. + */ +uint32_t +crypto_fast_rng_get_u32(crypto_fast_rng_t *rng) +{ + uint32_t val; + crypto_fast_rng_getbytes(rng, (void*)&val, sizeof(val)); + return val; +} + +/** + * As crypto_rand_uint64_range(), but extract the result from a + * crypto_fast_rng_t. + */ +uint64_t +crypto_fast_rng_uint64_range(crypto_fast_rng_t *rng, + uint64_t min, uint64_t max) +{ + /* Handle corrupted input */ + if (BUG(min >= max)) { + return min; + } + + return min + crypto_fast_rng_get_uint64(rng, max - min); +} + +/** + * As crypto_rand_get_double() but extract the result from a crypto_fast_rng_t. */ double crypto_fast_rng_get_double(crypto_fast_rng_t *rng) @@ -164,3 +191,4 @@ crypto_fast_rng_get_double(crypto_fast_rng_t *rng) crypto_fast_rng_getbytes(rng, (void*)&u, sizeof(u)); return ((double)u) / UINT_MAX_AS_DOUBLE; } + diff --git a/src/lib/crypt_ops/include.am b/src/lib/crypt_ops/include.am index 4730440143..c90ef6eca8 100644 --- a/src/lib/crypt_ops/include.am +++ b/src/lib/crypt_ops/include.am @@ -27,12 +27,14 @@ src_lib_libtor_crypt_ops_a_SOURCES = \ if USE_NSS src_lib_libtor_crypt_ops_a_SOURCES += \ src/lib/crypt_ops/aes_nss.c \ + src/lib/crypt_ops/crypto_digest_nss.c \ src/lib/crypt_ops/crypto_dh_nss.c \ src/lib/crypt_ops/crypto_nss_mgt.c \ src/lib/crypt_ops/crypto_rsa_nss.c else src_lib_libtor_crypt_ops_a_SOURCES += \ src/lib/crypt_ops/aes_openssl.c \ + src/lib/crypt_ops/crypto_digest_openssl.c \ src/lib/crypt_ops/crypto_rsa_openssl.c endif diff --git a/src/lib/dispatch/.may_include b/src/lib/dispatch/.may_include new file mode 100644 index 0000000000..7f2df5859f --- /dev/null +++ b/src/lib/dispatch/.may_include @@ -0,0 +1,10 @@ +orconfig.h + +ext/tor_queue.h + +lib/cc/*.h +lib/container/*.h +lib/dispatch/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h diff --git a/src/lib/dispatch/dispatch.h b/src/lib/dispatch/dispatch.h new file mode 100644 index 0000000000..8e62e8f168 --- /dev/null +++ b/src/lib/dispatch/dispatch.h @@ -0,0 +1,114 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DISPATCH_H +#define TOR_DISPATCH_H + +#include "lib/dispatch/msgtypes.h" + +/** + * \file dispatch.h + * \brief Low-level APIs for message-passing system. + * + * This module implements message dispatch based on a set of short integer + * identifiers. For a higher-level interface, see pubsub.h. + * + * Each message is represented as a generic msg_t object, and is discriminated + * by its message_id_t. Messages are delivered by a dispatch_t object, which + * delivers each message to its recipients by a configured "channel". + * + * A "channel" is a means of delivering messages. Every message_id_t must + * be associated with exactly one channel, identified by channel_id_t. + * When a channel receives messages, a callback is invoked to either process + * the messages immediately, or to cause them to be processed later. + * + * Every message_id_t has zero or more associated receiver functions set up in + * the dispatch_t object. Once the dispatch_t object is created, receivers + * can be enabled or disabled [TODO], but not added or removed. + * + * Every message_id_t has an associated datatype, identified by a + * msg_type_id_t. These datatypes can be associated with functions to + * (for example) free them, or format them for debugging. + * + * To setup a dispatch_t object, first create a dispatch_cfg_t object, and + * configure messages with their types, channels, and receivers. Then, use + * dispatch_new() with that dispatch_cfg_t to create the dispatch_t object. + * + * (We use a two-phase contruction procedure here to enable better static + * reasoning about publish/subscribe relationships.) + * + * Once you have a dispatch_t, you can queue messages on it with + * dispatch_send*(), and cause those messages to be delivered with + * dispatch_flush(). + **/ + +/** + * A "dispatcher" is the highest-level object; it handles making sure that + * messages are received and delivered properly. Only the mainloop + * should handle this type directly. + */ +typedef struct dispatch_t dispatch_t; + +struct dispatch_cfg_t; + +dispatch_t *dispatch_new(const struct dispatch_cfg_t *cfg); + +/** + * Free a dispatcher. Tor does this at exit. + */ +#define dispatch_free(d) \ + FREE_AND_NULL(dispatch_t, dispatch_free_, (d)) + +void dispatch_free_(dispatch_t *); + +int dispatch_send(dispatch_t *d, + subsys_id_t sender, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + msg_aux_data_t auxdata); + +int dispatch_send_msg(dispatch_t *d, msg_t *m); + +int dispatch_send_msg_unchecked(dispatch_t *d, msg_t *m); + +/* Flush up to <b>max_msgs</b> currently pending messages from the + * dispatcher. Messages that are not pending when this function are + * called, are not flushed by this call. Return 0 on success, -1 on + * unrecoverable error. + */ +int dispatch_flush(dispatch_t *, channel_id_t chan, int max_msgs); + +/** + * Function callback type used to alert some other module when a channel's + * queue changes from empty to nonempty. + * + * Ex 1: To cause messages to be processed immediately on-stack, this callback + * should invoke dispatch_flush() directly. + * + * Ex 2: To cause messages to be processed very soon, from the event queue, + * this callback should schedule an event callback to run dispatch_flush(). + * + * Ex 3: To cause messages to be processed periodically, this function should + * do nothing, and a periodic event should invoke dispatch_flush(). + **/ +typedef void (*dispatch_alertfn_t)(struct dispatch_t *, + channel_id_t, void *); + +int dispatch_set_alert_fn(dispatch_t *d, channel_id_t chan, + dispatch_alertfn_t fn, void *userdata); + +#define dispatch_free_msg(d,msg) \ + STMT_BEGIN { \ + msg_t **msg_tmp_ptr__ = &(msg); \ + dispatch_free_msg_((d), *msg_tmp_ptr__); \ + *msg_tmp_ptr__= NULL; \ + } STMT_END +void dispatch_free_msg_(const dispatch_t *d, msg_t *msg); + +char *dispatch_fmt_msg_data(const dispatch_t *d, const msg_t *msg); + +#endif diff --git a/src/lib/dispatch/dispatch_cfg.c b/src/lib/dispatch/dispatch_cfg.c new file mode 100644 index 0000000000..b3a72ec22f --- /dev/null +++ b/src/lib/dispatch/dispatch_cfg.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dispatch_cfg.c + * \brief Create and configure a dispatch_cfg_t. + * + * A dispatch_cfg_t object is used to configure a set of messages and + * associated information before creating a dispatch_t. + */ + +#define DISPATCH_PRIVATE + +#include "orconfig.h" +#include "lib/dispatch/dispatch_cfg.h" +#include "lib/dispatch/dispatch_cfg_st.h" +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_st.h" + +#include "lib/container/smartlist.h" +#include "lib/malloc/malloc.h" + +/** + * Create and return a new dispatch_cfg_t. + **/ +dispatch_cfg_t * +dcfg_new(void) +{ + dispatch_cfg_t *cfg = tor_malloc(sizeof(dispatch_cfg_t)); + cfg->type_by_msg = smartlist_new(); + cfg->chan_by_msg = smartlist_new(); + cfg->fns_by_type = smartlist_new(); + cfg->recv_by_msg = smartlist_new(); + return cfg; +} + +/** + * Associate a message with a datatype. Return 0 on success, -1 if a + * different type was previously associated with the message ID. + **/ +int +dcfg_msg_set_type(dispatch_cfg_t *cfg, message_id_t msg, + msg_type_id_t type) +{ + smartlist_grow(cfg->type_by_msg, msg+1); + msg_type_id_t *oldval = smartlist_get(cfg->type_by_msg, msg); + if (oldval != NULL && *oldval != type) { + return -1; + } + if (!oldval) + smartlist_set(cfg->type_by_msg, msg, tor_memdup(&type, sizeof(type))); + return 0; +} + +/** + * Associate a message with a channel. Return 0 on success, -1 if a + * different channel was previously associated with the message ID. + **/ +int +dcfg_msg_set_chan(dispatch_cfg_t *cfg, message_id_t msg, + channel_id_t chan) +{ + smartlist_grow(cfg->chan_by_msg, msg+1); + channel_id_t *oldval = smartlist_get(cfg->chan_by_msg, msg); + if (oldval != NULL && *oldval != chan) { + return -1; + } + if (!oldval) + smartlist_set(cfg->chan_by_msg, msg, tor_memdup(&chan, sizeof(chan))); + return 0; +} + +/** + * Associate a set of functions with a datatype. Return 0 on success, -1 if + * different functions were previously associated with the type. + **/ +int +dcfg_type_set_fns(dispatch_cfg_t *cfg, msg_type_id_t type, + const dispatch_typefns_t *fns) +{ + smartlist_grow(cfg->fns_by_type, type+1); + dispatch_typefns_t *oldfns = smartlist_get(cfg->fns_by_type, type); + if (oldfns && (oldfns->free_fn != fns->free_fn || + oldfns->fmt_fn != fns->fmt_fn)) + return -1; + if (!oldfns) + smartlist_set(cfg->fns_by_type, type, tor_memdup(fns, sizeof(*fns))); + return 0; +} + +/** + * Associate a receiver with a message ID. Multiple receivers may be + * associated with a single messasge ID. + * + * Return 0 on success, on failure. + **/ +int +dcfg_add_recv(dispatch_cfg_t *cfg, message_id_t msg, + subsys_id_t sys, recv_fn_t fn) +{ + smartlist_grow(cfg->recv_by_msg, msg+1); + smartlist_t *receivers = smartlist_get(cfg->recv_by_msg, msg); + if (!receivers) { + receivers = smartlist_new(); + smartlist_set(cfg->recv_by_msg, msg, receivers); + } + + dispatch_rcv_t *rcv = tor_malloc(sizeof(dispatch_rcv_t)); + rcv->sys = sys; + rcv->enabled = true; + rcv->fn = fn; + smartlist_add(receivers, (void*)rcv); + return 0; +} + +/** Helper: release all storage held by <b>cfg</b>. */ +void +dcfg_free_(dispatch_cfg_t *cfg) +{ + if (!cfg) + return; + + SMARTLIST_FOREACH(cfg->type_by_msg, msg_type_id_t *, id, tor_free(id)); + SMARTLIST_FOREACH(cfg->chan_by_msg, channel_id_t *, id, tor_free(id)); + SMARTLIST_FOREACH(cfg->fns_by_type, dispatch_typefns_t *, f, tor_free(f)); + smartlist_free(cfg->type_by_msg); + smartlist_free(cfg->chan_by_msg); + smartlist_free(cfg->fns_by_type); + SMARTLIST_FOREACH_BEGIN(cfg->recv_by_msg, smartlist_t *, receivers) { + if (!receivers) + continue; + SMARTLIST_FOREACH(receivers, dispatch_rcv_t *, rcv, tor_free(rcv)); + smartlist_free(receivers); + } SMARTLIST_FOREACH_END(receivers); + smartlist_free(cfg->recv_by_msg); + + tor_free(cfg); +} diff --git a/src/lib/dispatch/dispatch_cfg.h b/src/lib/dispatch/dispatch_cfg.h new file mode 100644 index 0000000000..2c755e39bc --- /dev/null +++ b/src/lib/dispatch/dispatch_cfg.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DISPATCH_CFG_H +#define TOR_DISPATCH_CFG_H + +#include "lib/dispatch/msgtypes.h" + +/** + * A "dispatch_cfg" is the configuration used to set up a dispatcher. + * It is created and accessed with a set of dcfg_* functions, and then + * used with dispatcher_new() to make the dispatcher. + */ +typedef struct dispatch_cfg_t dispatch_cfg_t; + +dispatch_cfg_t *dcfg_new(void); + +int dcfg_msg_set_type(dispatch_cfg_t *cfg, message_id_t msg, + msg_type_id_t type); + +int dcfg_msg_set_chan(dispatch_cfg_t *cfg, message_id_t msg, + channel_id_t chan); + +int dcfg_type_set_fns(dispatch_cfg_t *cfg, msg_type_id_t type, + const dispatch_typefns_t *fns); + +int dcfg_add_recv(dispatch_cfg_t *cfg, message_id_t msg, + subsys_id_t sys, recv_fn_t fn); + +/** Free a dispatch_cfg_t. */ +#define dcfg_free(cfg) \ + FREE_AND_NULL(dispatch_cfg_t, dcfg_free_, (cfg)) + +void dcfg_free_(dispatch_cfg_t *cfg); + +#endif diff --git a/src/lib/dispatch/dispatch_cfg_st.h b/src/lib/dispatch/dispatch_cfg_st.h new file mode 100644 index 0000000000..d004fe5934 --- /dev/null +++ b/src/lib/dispatch/dispatch_cfg_st.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-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DISPATCH_CFG_ST_H +#define TOR_DISPATCH_CFG_ST_H + +struct smartlist_t; + +/* Information needed to create a dispatcher, but in a less efficient, more + * mutable format. */ +struct dispatch_cfg_t { + /** A list of msg_type_id_t (cast to void*), indexed by msg_t. */ + struct smartlist_t *type_by_msg; + /** A list of channel_id_t (cast to void*), indexed by msg_t. */ + struct smartlist_t *chan_by_msg; + /** A list of dispatch_rcv_t, indexed by msg_type_id_t. */ + struct smartlist_t *fns_by_type; + /** A list of dispatch_typefns_t, indexed by msg_t. */ + struct smartlist_t *recv_by_msg; +}; + +#endif diff --git a/src/lib/dispatch/dispatch_core.c b/src/lib/dispatch/dispatch_core.c new file mode 100644 index 0000000000..da54f9b437 --- /dev/null +++ b/src/lib/dispatch/dispatch_core.c @@ -0,0 +1,260 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dispatch_core.c + * \brief Core module for sending and receiving messages. + */ + +#define DISPATCH_PRIVATE +#include "orconfig.h" + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_st.h" +#include "lib/dispatch/dispatch_naming.h" + +#include "lib/malloc/malloc.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +/** + * Use <b>d</b> to drop all storage held for <b>msg</b>. + * + * (We need the dispatcher so we know how to free the auxiliary data.) + **/ +void +dispatch_free_msg_(const dispatch_t *d, msg_t *msg) +{ + if (!msg) + return; + + d->typefns[msg->type].free_fn(msg->aux_data__); + tor_free(msg); +} + +/** + * Format the auxiliary data held by msg. + **/ +char * +dispatch_fmt_msg_data(const dispatch_t *d, const msg_t *msg) +{ + if (!msg) + return NULL; + + return d->typefns[msg->type].fmt_fn(msg->aux_data__); +} + +/** + * Release all storage held by <b>d</b>. + **/ +void +dispatch_free_(dispatch_t *d) +{ + if (d == NULL) + return; + + size_t n_queues = d->n_queues; + for (size_t i = 0; i < n_queues; ++i) { + msg_t *m, *mtmp; + TOR_SIMPLEQ_FOREACH_SAFE(m, &d->queues[i].queue, next, mtmp) { + dispatch_free_msg(d, m); + } + } + + size_t n_msgs = d->n_msgs; + + for (size_t i = 0; i < n_msgs; ++i) { + tor_free(d->table[i]); + } + tor_free(d->table); + tor_free(d->typefns); + tor_free(d->queues); + + // This is the only time we will treat d->cfg as non-const. + //dispatch_cfg_free_((dispatch_items_t *) d->cfg); + + tor_free(d); +} + +/** + * Tell the dispatcher to call <b>fn</b> with <b>userdata</b> whenever + * <b>chan</b> becomes nonempty. Return 0 on success, -1 on error. + **/ +int +dispatch_set_alert_fn(dispatch_t *d, channel_id_t chan, + dispatch_alertfn_t fn, void *userdata) +{ + if (BUG(chan >= d->n_queues)) + return -1; + + dqueue_t *q = &d->queues[chan]; + q->alert_fn = fn; + q->alert_fn_arg = userdata; + return 0; +} + +/** + * Send a message on the appropriate channel notifying that channel if + * necessary. + * + * This function takes ownership of the auxiliary data; it can't be static or + * stack-allocated, and the caller is not allowed to use it afterwards. + * + * This function does not check the various vields of the message object for + * consistency. + **/ +int +dispatch_send(dispatch_t *d, + subsys_id_t sender, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + msg_aux_data_t auxdata) +{ + if (!d->table[msg]) { + /* Fast path: nobody wants this data. */ + + d->typefns[type].free_fn(auxdata); + return 0; + } + + msg_t *m = tor_malloc(sizeof(msg_t)); + + m->sender = sender; + m->channel = channel; + m->msg = msg; + m->type = type; + memcpy(&m->aux_data__, &auxdata, sizeof(msg_aux_data_t)); + + return dispatch_send_msg(d, m); +} + +int +dispatch_send_msg(dispatch_t *d, msg_t *m) +{ + if (BUG(!d)) + goto err; + if (BUG(!m)) + goto err; + if (BUG(m->channel >= d->n_queues)) + goto err; + if (BUG(m->msg >= d->n_msgs)) + goto err; + + dtbl_entry_t *ent = d->table[m->msg]; + if (ent) { + if (BUG(m->type != ent->type)) + goto err; + if (BUG(m->channel != ent->channel)) + goto err; + } + + return dispatch_send_msg_unchecked(d, m); + err: + /* Probably it isn't safe to free m, since type could be wrong. */ + return -1; +} + +/** + * Send a message on the appropriate queue, notifying that queue if necessary. + * + * This function takes ownership of the message object and its auxiliary data; + * it can't be static or stack-allocated, and the caller isn't allowed to use + * it afterwards. + * + * This function does not check the various fields of the message object for + * consistency, and can crash if they are out of range. Only functions that + * have already constructed the message in a safe way, or checked it for + * correctness themselves, should call this function. + **/ +int +dispatch_send_msg_unchecked(dispatch_t *d, msg_t *m) +{ + /* Find the right queue. */ + dqueue_t *q = &d->queues[m->channel]; + bool was_empty = TOR_SIMPLEQ_EMPTY(&q->queue); + + /* Append the message. */ + TOR_SIMPLEQ_INSERT_TAIL(&q->queue, m, next); + + if (debug_logging_enabled()) { + char *arg = dispatch_fmt_msg_data(d, m); + log_debug(LD_MESG, + "Queued: %s (%s) from %s, on %s.", + get_message_id_name(m->msg), + arg, + get_subsys_id_name(m->sender), + get_channel_id_name(m->channel)); + tor_free(arg); + } + + /* If we just made the queue nonempty for the first time, call the alert + * function. */ + if (was_empty) { + q->alert_fn(d, m->channel, q->alert_fn_arg); + } + + return 0; +} + +/** + * Run all of the callbacks on <b>d</b> associated with <b>m</b>. + **/ +static void +dispatcher_run_msg_cbs(const dispatch_t *d, msg_t *m) +{ + tor_assert(m->msg <= d->n_msgs); + dtbl_entry_t *ent = d->table[m->msg]; + int n_fns = ent->n_fns; + + if (debug_logging_enabled()) { + char *arg = dispatch_fmt_msg_data(d, m); + log_debug(LD_MESG, + "Delivering: %s (%s) from %s, on %s:", + get_message_id_name(m->msg), + arg, + get_subsys_id_name(m->sender), + get_channel_id_name(m->channel)); + tor_free(arg); + } + + int i; + for (i=0; i < n_fns; ++i) { + if (ent->rcv[i].enabled) { + log_debug(LD_MESG, " Delivering to %s.", + get_subsys_id_name(ent->rcv[i].sys)); + ent->rcv[i].fn(m); + } + } +} + +/** + * Run up to <b>max_msgs</b> callbacks for messages on the channel <b>ch</b> + * on the given dispatcher. Return 0 on success or recoverable failure, + * -1 on unrecoverable error. + **/ +int +dispatch_flush(dispatch_t *d, channel_id_t ch, int max_msgs) +{ + if (BUG(ch >= d->n_queues)) + return 0; + + int n_flushed = 0; + dqueue_t *q = &d->queues[ch]; + + while (n_flushed < max_msgs) { + msg_t *m = TOR_SIMPLEQ_FIRST(&q->queue); + if (!m) + break; + TOR_SIMPLEQ_REMOVE_HEAD(&q->queue, next); + dispatcher_run_msg_cbs(d, m); + dispatch_free_msg(d, m); + ++n_flushed; + } + + return 0; +} diff --git a/src/lib/dispatch/dispatch_naming.c b/src/lib/dispatch/dispatch_naming.c new file mode 100644 index 0000000000..83d9a2d604 --- /dev/null +++ b/src/lib/dispatch/dispatch_naming.c @@ -0,0 +1,63 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#include "lib/cc/compat_compiler.h" + +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/msgtypes.h" + +#include "lib/container/namemap.h" +#include "lib/container/namemap_st.h" + +#include "lib/log/util_bug.h" +#include "lib/log/log.h" + +#include <stdlib.h> + +/** Global namemap for message IDs. */ +static namemap_t message_id_map = NAMEMAP_INIT(); +/** Global namemap for subsystem IDs. */ +static namemap_t subsys_id_map = NAMEMAP_INIT(); +/** Global namemap for channel IDs. */ +static namemap_t channel_id_map = NAMEMAP_INIT(); +/** Global namemap for message type IDs. */ +static namemap_t msg_type_id_map = NAMEMAP_INIT(); + +void +dispatch_naming_init(void) +{ +} + +/* Helper macro: declare functions to map IDs to and from names for a given + * type in a namemap_t. + */ +#define DECLARE_ID_MAP_FNS(type) \ + type##_id_t \ + get_##type##_id(const char *name) \ + { \ + unsigned u = namemap_get_or_create_id(&type##_id_map, name); \ + tor_assert(u != NAMEMAP_ERR); \ + tor_assert(u != ERROR_ID); \ + return (type##_id_t) u; \ + } \ + const char * \ + get_##type##_id_name(type##_id_t id) \ + { \ + return namemap_fmt_name(&type##_id_map, id); \ + } \ + size_t \ + get_num_##type##_ids(void) \ + { \ + return namemap_get_size(&type##_id_map); \ + } \ + EAT_SEMICOLON + +DECLARE_ID_MAP_FNS(message); +DECLARE_ID_MAP_FNS(channel); +DECLARE_ID_MAP_FNS(subsys); +DECLARE_ID_MAP_FNS(msg_type); diff --git a/src/lib/dispatch/dispatch_naming.h b/src/lib/dispatch/dispatch_naming.h new file mode 100644 index 0000000000..c116d2184d --- /dev/null +++ b/src/lib/dispatch/dispatch_naming.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DISPATCH_NAMING_H +#define TOR_DISPATCH_NAMING_H + +#include "lib/dispatch/msgtypes.h" +#include <stddef.h> + +/** + * Return an existing channel ID by name, allocating the channel ID if + * if necessary. Returns ERROR_ID if we have run out of + * channels + */ +channel_id_t get_channel_id(const char *); +/** + * Return the name corresponding to a given channel ID. + **/ +const char *get_channel_id_name(channel_id_t); +/** + * Return the total number of _named_ channel IDs. + **/ +size_t get_num_channel_ids(void); + +/* As above, but for messages. */ +message_id_t get_message_id(const char *); +const char *get_message_id_name(message_id_t); +size_t get_num_message_ids(void); + +/* As above, but for subsystems */ +subsys_id_t get_subsys_id(const char *); +const char *get_subsys_id_name(subsys_id_t); +size_t get_num_subsys_ids(void); + +/* As above, but for types. Note that types additionally must be + * "defined", if any message is to use them. */ +msg_type_id_t get_msg_type_id(const char *); +const char *get_msg_type_id_name(msg_type_id_t); +size_t get_num_msg_type_ids(void); + +void dispatch_naming_init(void); + +#endif diff --git a/src/lib/dispatch/dispatch_new.c b/src/lib/dispatch/dispatch_new.c new file mode 100644 index 0000000000..b89ef43ea7 --- /dev/null +++ b/src/lib/dispatch/dispatch_new.c @@ -0,0 +1,174 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dispatch_new.c + * \brief Code to construct a dispatch_t from a dispatch_cfg_t. + **/ + +#define DISPATCH_PRIVATE +#include "orconfig.h" + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_st.h" +#include "lib/dispatch/dispatch_cfg.h" +#include "lib/dispatch/dispatch_cfg_st.h" + +#include "lib/cc/ctassert.h" +#include "lib/intmath/cmp.h" +#include "lib/malloc/malloc.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +/** Given a smartlist full of (possibly NULL) pointers to uint16_t values, + * return the largest value, or dflt if the list is empty. */ +static int +max_in_sl(const smartlist_t *sl, int dflt) +{ + uint16_t *maxptr = NULL; + SMARTLIST_FOREACH_BEGIN(sl, uint16_t *, u) { + if (!maxptr) + maxptr = u; + else if (*u > *maxptr) + maxptr = u; + } SMARTLIST_FOREACH_END(u); + + return maxptr ? *maxptr : dflt; +} + +/* The above function is only safe to call if we are sure that channel_id_t + * and msg_type_id_t are really uint16_t. They should be so defined in + * msgtypes.h, but let's be extra cautious. + */ +CTASSERT(sizeof(uint16_t) == sizeof(msg_type_id_t)); +CTASSERT(sizeof(uint16_t) == sizeof(channel_id_t)); + +/** Helper: Format an unformattable message auxiliary data item: just return a +* copy of the string <>. */ +static char * +type_fmt_nop(msg_aux_data_t arg) +{ + (void)arg; + return tor_strdup("<>"); +} + +/** Helper: Free an unfreeable message auxiliary data item: do nothing. */ +static void +type_free_nop(msg_aux_data_t arg) +{ + (void)arg; +} + +/** Type functions to use when no type functions are provided. */ +static dispatch_typefns_t nop_typefns = { + .free_fn = type_free_nop, + .fmt_fn = type_fmt_nop +}; + +/** + * Alert function to use when none is configured: do nothing. + **/ +static void +alert_fn_nop(dispatch_t *d, channel_id_t ch, void *arg) +{ + (void)d; + (void)ch; + (void)arg; +} + +/** + * Given a list of recvfn_t, create and return a new dtbl_entry_t mapping + * to each of those functions. + **/ +static dtbl_entry_t * +dtbl_entry_from_lst(smartlist_t *receivers) +{ + if (!receivers) + return NULL; + + size_t n_recv = smartlist_len(receivers); + dtbl_entry_t *ent; + ent = tor_malloc_zero(offsetof(dtbl_entry_t, rcv) + + sizeof(dispatch_rcv_t) * n_recv); + + ent->n_fns = n_recv; + + SMARTLIST_FOREACH_BEGIN(receivers, const dispatch_rcv_t *, rcv) { + memcpy(&ent->rcv[rcv_sl_idx], rcv, sizeof(*rcv)); + if (rcv->enabled) { + ++ent->n_enabled; + } + } SMARTLIST_FOREACH_END(rcv); + + return ent; +} + +/** Create and return a new dispatcher from a given dispatch_cfg_t. */ +dispatch_t * +dispatch_new(const dispatch_cfg_t *cfg) +{ + dispatch_t *d = tor_malloc_zero(sizeof(dispatch_t)); + + /* Any message that has a type or a receiver counts towards our messages */ + const size_t n_msgs = MAX(smartlist_len(cfg->type_by_msg), + smartlist_len(cfg->recv_by_msg)) + 1; + + /* Any channel that any message has counts towards the number of channels. */ + const size_t n_chans = (size_t) MAX(1, max_in_sl(cfg->chan_by_msg,0)) + 1; + + /* Any type that a message has, or that has functions, counts towards + * the number of types. */ + const size_t n_types = (size_t) MAX(max_in_sl(cfg->type_by_msg,0), + smartlist_len(cfg->fns_by_type)) + 1; + + d->n_msgs = n_msgs; + d->n_queues = n_chans; + d->n_types = n_types; + + /* Initialize the array of type-functions. */ + d->typefns = tor_calloc(n_types, sizeof(dispatch_typefns_t)); + for (size_t i = 0; i < n_types; ++i) { + /* Default to no-op for everything... */ + memcpy(&d->typefns[i], &nop_typefns, sizeof(dispatch_typefns_t)); + } + SMARTLIST_FOREACH_BEGIN(cfg->fns_by_type, dispatch_typefns_t *, fns) { + /* Set the functions if they are provided. */ + if (fns) { + if (fns->free_fn) + d->typefns[fns_sl_idx].free_fn = fns->free_fn; + if (fns->fmt_fn) + d->typefns[fns_sl_idx].fmt_fn = fns->fmt_fn; + } + } SMARTLIST_FOREACH_END(fns); + + /* Initialize the message queues: one for each channel. */ + d->queues = tor_calloc(d->n_queues, sizeof(dqueue_t)); + for (size_t i = 0; i < d->n_queues; ++i) { + TOR_SIMPLEQ_INIT(&d->queues[i].queue); + d->queues[i].alert_fn = alert_fn_nop; + } + + /* Build the dispatch tables mapping message IDs to receivers. */ + d->table = tor_calloc(d->n_msgs, sizeof(dtbl_entry_t *)); + SMARTLIST_FOREACH_BEGIN(cfg->recv_by_msg, smartlist_t *, rcv) { + d->table[rcv_sl_idx] = dtbl_entry_from_lst(rcv); + } SMARTLIST_FOREACH_END(rcv); + + /* Fill in the empty entries in the dispatch tables: + * types and channels for each message. */ + SMARTLIST_FOREACH_BEGIN(cfg->type_by_msg, msg_type_id_t *, type) { + if (d->table[type_sl_idx]) + d->table[type_sl_idx]->type = *type; + } SMARTLIST_FOREACH_END(type); + + SMARTLIST_FOREACH_BEGIN(cfg->chan_by_msg, channel_id_t *, chan) { + if (d->table[chan_sl_idx]) + d->table[chan_sl_idx]->channel = *chan; + } SMARTLIST_FOREACH_END(chan); + + return d; +} diff --git a/src/lib/dispatch/dispatch_st.h b/src/lib/dispatch/dispatch_st.h new file mode 100644 index 0000000000..568107b700 --- /dev/null +++ b/src/lib/dispatch/dispatch_st.h @@ -0,0 +1,108 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dispatch_st.h + * + * \brief private structures used for the dispatcher module + */ + +#ifndef TOR_DISPATCH_ST_H +#define TOR_DISPATCH_ST_H + +#ifdef DISPATCH_PRIVATE + +#include "lib/container/smartlist.h" + +/** + * Information about the recipient of a message. + **/ +typedef struct dispatch_rcv_t { + /** The subsystem receiving a message. */ + subsys_id_t sys; + /** True iff this recipient is enabled. */ + bool enabled; + /** The function that will handle the message. */ + recv_fn_t fn; +} dispatch_rcv_t; + +/** + * Information used by a dispatcher to handle and dispatch a single message + * ID. It maps that message ID to its type, channel, and list of receiver + * functions. + * + * This structure is used when the dispatcher is running. + **/ +typedef struct dtbl_entry_t { + /** The number of enabled non-stub subscribers for this message. + * + * Note that for now, this will be the same as <b>n_fns</b>, since there is + * no way to turn these subscribers on an off yet. */ + uint16_t n_enabled; + /** The channel that handles this message. */ + channel_id_t channel; + /** The associated C type for this message. */ + msg_type_id_t type; + /** + * The number of functions pointers for subscribers that receive this + * message, in rcv. */ + uint16_t n_fns; + /** + * The recipients for this message. + */ + dispatch_rcv_t rcv[FLEXIBLE_ARRAY_MEMBER]; +} dtbl_entry_t; + +/** + * A queue of messages for a given channel, used by a live dispatcher. + */ +typedef struct dqueue_t { + /** The queue of messages itself. */ + TOR_SIMPLEQ_HEAD( , msg_t) queue; + /** A function to be called when the queue becomes nonempty. */ + dispatch_alertfn_t alert_fn; + /** An argument for the alert_fn. */ + void *alert_fn_arg; +} dqueue_t ; + +/** + * A single dispatcher for cross-module messages. + */ +struct dispatch_t { + /** + * The length of <b>table</b>: the number of message IDs that this + * dispatcher can handle. + */ + size_t n_msgs; + /** + * The length of <b>queues</b>: the number of channels that this dispatcher + * has configured. + */ + size_t n_queues; + /** + * The length of <b>typefns</b>: the number of C type IDs that this + * dispatcher has configured. + */ + size_t n_types; + /** + * An array of message queues, indexed by channel ID. + */ + dqueue_t *queues; + /** + * An array of entries about how to handle particular message types, indexed + * by message ID. + */ + dtbl_entry_t **table; + /** + * An array of function tables for manipulating types, index by message + * type ID. + **/ + dispatch_typefns_t *typefns; +}; + +#endif + +#endif diff --git a/src/lib/dispatch/include.am b/src/lib/dispatch/include.am new file mode 100644 index 0000000000..4ec5b75cd1 --- /dev/null +++ b/src/lib/dispatch/include.am @@ -0,0 +1,25 @@ + +noinst_LIBRARIES += src/lib/libtor-dispatch.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-dispatch-testing.a +endif + +src_lib_libtor_dispatch_a_SOURCES = \ + src/lib/dispatch/dispatch_cfg.c \ + src/lib/dispatch/dispatch_core.c \ + src/lib/dispatch/dispatch_naming.c \ + src/lib/dispatch/dispatch_new.c + +src_lib_libtor_dispatch_testing_a_SOURCES = \ + $(src_lib_libtor_dispatch_a_SOURCES) +src_lib_libtor_dispatch_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_dispatch_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/dispatch/dispatch.h \ + src/lib/dispatch/dispatch_cfg.h \ + src/lib/dispatch/dispatch_cfg_st.h \ + src/lib/dispatch/dispatch_naming.h \ + src/lib/dispatch/dispatch_st.h \ + src/lib/dispatch/msgtypes.h diff --git a/src/lib/dispatch/msgtypes.h b/src/lib/dispatch/msgtypes.h new file mode 100644 index 0000000000..4e79e592a6 --- /dev/null +++ b/src/lib/dispatch/msgtypes.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file msgtypes.h + * \brief Types used for messages in the dispatcher code. + **/ + +#ifndef TOR_DISPATCH_MSGTYPES_H +#define TOR_DISPATCH_MSGTYPES_H + +#include <stdint.h> + +#include "ext/tor_queue.h" + +/** + * These types are aliases for subsystems, channels, and message IDs. + **/ +typedef uint16_t subsys_id_t; +typedef uint16_t channel_id_t; +typedef uint16_t message_id_t; + +/** + * This identifies a C type that can be sent along with a message. + **/ +typedef uint16_t msg_type_id_t; + +/** + * An ID value returned for *_type_t when none exists. + */ +#define ERROR_ID 65535 + +/** + * Auxiliary (untyped) data sent along with a message. + * + * We define this as a union of a pointer and a u64, so that the integer + * types will have the same range across platforms. + **/ +typedef union { + void *ptr; + uint64_t u64; +} msg_aux_data_t; + +/** + * Structure of a received message. + **/ +typedef struct msg_t { + TOR_SIMPLEQ_ENTRY(msg_t) next; + subsys_id_t sender; + channel_id_t channel; + message_id_t msg; + /** We could omit this field, since it is implicit in the message type, but + * IMO let's leave it in for safety. */ + msg_type_id_t type; + /** Untyped auxiliary data. You shouldn't have to mess with this + * directly. */ + msg_aux_data_t aux_data__; +} msg_t; + +/** + * A function that a subscriber uses to receive a message. + **/ +typedef void (*recv_fn_t)(const msg_t *m); + +/** + * Table of functions to use for a given C type. Any omitted (NULL) functions + * will be treated as no-ops. + **/ +typedef struct dispatch_typefns_t { + /** Release storage held for the auxiliary data of this type. */ + void (*free_fn)(msg_aux_data_t); + /** Format and return a newly allocated string describing the contents + * of this data element. */ + char *(*fmt_fn)(msg_aux_data_t); +} dispatch_typefns_t; + +#endif diff --git a/src/lib/encoding/binascii.c b/src/lib/encoding/binascii.c index de4d1648bb..187df34243 100644 --- a/src/lib/encoding/binascii.c +++ b/src/lib/encoding/binascii.c @@ -84,7 +84,7 @@ base32_encode(char *dest, size_t destlen, const char *src, size_t srclen) } /** Implements base32 decoding as in RFC 4648. - * Returns 0 if successful, -1 otherwise. + * Return the number of bytes decoded if successful; -1 otherwise. */ int base32_decode(char *dest, size_t destlen, const char *src, size_t srclen) @@ -147,7 +147,7 @@ base32_decode(char *dest, size_t destlen, const char *src, size_t srclen) memset(tmp, 0, srclen); /* on the heap, this should be safe */ tor_free(tmp); tmp = NULL; - return 0; + return i; } #define BASE64_OPENSSL_LINELEN 64 diff --git a/src/lib/evloop/workqueue.c b/src/lib/evloop/workqueue.c index b36a02da5e..015b694290 100644 --- a/src/lib/evloop/workqueue.c +++ b/src/lib/evloop/workqueue.c @@ -59,9 +59,6 @@ struct threadpool_s { * <b>p</b> is work[p]. */ work_tailq_t work[WORKQUEUE_N_PRIORITIES]; - /** Weak RNG, used to decide when to ignore priority. */ - tor_weak_rng_t weak_rng; - /** The current 'update generation' of the threadpool. Any thread that is * at an earlier generation needs to run the update function. */ unsigned generation; @@ -238,7 +235,7 @@ worker_thread_extract_next_work(workerthread_t *thread) this_queue = &pool->work[i]; if (!TOR_TAILQ_EMPTY(this_queue)) { queue = this_queue; - if (! tor_weak_random_one_in_n(&pool->weak_rng, + if (! crypto_fast_rng_one_in_n(get_thread_fast_rng(), thread->lower_priority_chance)) { /* Usually we'll just break now, so that we can get out of the loop * and use the queue where we found work. But with a small @@ -555,11 +552,6 @@ threadpool_new(int n_threads, for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { TOR_TAILQ_INIT(&pool->work[i]); } - { - unsigned seed; - crypto_rand((void*)&seed, sizeof(seed)); - tor_init_weak_random(&pool->weak_rng, seed); - } pool->new_thread_state_fn = new_thread_state_fn; pool->new_thread_state_arg = arg; diff --git a/src/lib/log/log.c b/src/lib/log/log.c index d21d8d1d41..84e3eafc27 100644 --- a/src/lib/log/log.c +++ b/src/lib/log/log.c @@ -49,6 +49,7 @@ #include "lib/wallclock/approx_time.h" #include "lib/wallclock/time_to_tm.h" #include "lib/fdio/fdio.h" +#include "lib/cc/ctassert.h" #ifdef HAVE_ANDROID_LOG_H #include <android/log.h> @@ -1268,9 +1269,12 @@ static const char *domain_list[] = { "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL", - "SCHED", "GUARD", "CONSDIFF", "DOS", "PROCESS", "PT", "BTRACK", NULL + "SCHED", "GUARD", "CONSDIFF", "DOS", "PROCESS", "PT", "BTRACK", "MESG", + NULL }; +CTASSERT(ARRAY_LENGTH(domain_list) == N_LOGGING_DOMAINS + 1); + /** Return a bitmask for the log domain for which <b>domain</b> is the name, * or 0 if there is no such name. */ static log_domain_mask_t diff --git a/src/lib/log/log.h b/src/lib/log/log.h index dbc1c47021..a381220af0 100644 --- a/src/lib/log/log.h +++ b/src/lib/log/log.h @@ -11,6 +11,7 @@ **/ #ifndef TOR_TORLOG_H +#define TOR_TORLOG_H #include <stdarg.h> #include "lib/cc/torint.h" @@ -113,8 +114,9 @@ #define LD_PT (1u<<27) /** Bootstrap tracker. */ #define LD_BTRACK (1u<<28) -/** Number of logging domains in the code. */ -#define N_LOGGING_DOMAINS 29 +/** Message-passing backend. */ +#define LD_MESG (1u<<29) +#define N_LOGGING_DOMAINS 30 /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ @@ -192,6 +194,15 @@ void tor_log_get_logfile_names(struct smartlist_t *out); extern int log_global_min_severity_; +static inline bool debug_logging_enabled(void); +/** + * Return true iff debug logging is enabled for at least one domain. + */ +static inline bool debug_logging_enabled(void) +{ + return PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG); +} + void log_fn_(int severity, log_domain_mask_t domain, const char *funcname, const char *format, ...) CHECK_PRINTF(4,5); @@ -221,8 +232,8 @@ void tor_log_string(int severity, log_domain_mask_t domain, log_fn_ratelim_(ratelim, severity, domain, __FUNCTION__, args) #define log_debug(domain, args...) \ STMT_BEGIN \ - if (PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG)) \ - log_fn_(LOG_DEBUG, domain, __FUNCTION__, args); \ + if (debug_logging_enabled()) \ + log_fn_(LOG_DEBUG, domain, __FUNCTION__, args); \ STMT_END #define log_info(domain, args...) \ log_fn_(LOG_INFO, domain, __FUNCTION__, args) @@ -239,8 +250,8 @@ void tor_log_string(int severity, log_domain_mask_t domain, #define log_debug(domain, args, ...) \ STMT_BEGIN \ - if (PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG)) \ - log_fn_(LOG_DEBUG, domain, __FUNCTION__, args, ##__VA_ARGS__); \ + if (debug_logging_enabled()) \ + log_fn_(LOG_DEBUG, domain, __FUNCTION__, args, ##__VA_ARGS__); \ STMT_END #define log_info(domain, args,...) \ log_fn_(LOG_INFO, domain, __FUNCTION__, args, ##__VA_ARGS__) @@ -278,5 +289,4 @@ MOCK_DECL(STATIC void, logv, (int severity, log_domain_mask_t domain, va_list ap) CHECK_PRINTF(5,0)); #endif -# define TOR_TORLOG_H #endif /* !defined(TOR_TORLOG_H) */ diff --git a/src/lib/log/util_bug.c b/src/lib/log/util_bug.c index f42d2d2ab4..65ab7bc9c6 100644 --- a/src/lib/log/util_bug.c +++ b/src/lib/log/util_bug.c @@ -69,25 +69,45 @@ tor_set_failed_assertion_callback(void (*fn)(void)) /** Helper for tor_assert: report the assertion failure. */ void +CHECK_PRINTF(5, 6) tor_assertion_failed_(const char *fname, unsigned int line, - const char *func, const char *expr) + const char *func, const char *expr, + const char *fmt, ...) { - char buf[256]; + char *buf = NULL; + char *extra = NULL; + va_list ap; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + if (fmt) { + va_start(ap,fmt); + tor_vasprintf(&extra, fmt, ap); + va_end(ap); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + log_err(LD_BUG, "%s:%u: %s: Assertion %s failed; aborting.", fname, line, func, expr); - tor_snprintf(buf, sizeof(buf), - "Assertion %s failed in %s at %s:%u", - expr, func, fname, line); + tor_asprintf(&buf, "Assertion %s failed in %s at %s:%u: %s", + expr, func, fname, line, extra ? extra : ""); + tor_free(extra); log_backtrace(LOG_ERR, LD_BUG, buf); + tor_free(buf); } /** Helper for tor_assert_nonfatal: report the assertion failure. */ void +CHECK_PRINTF(6, 7) tor_bug_occurred_(const char *fname, unsigned int line, const char *func, const char *expr, - int once) + int once, const char *fmt, ...) { - char buf[256]; + char *buf = NULL; const char *once_str = once ? " (Future instances of this warning will be silenced.)": ""; if (! expr) { @@ -97,7 +117,7 @@ tor_bug_occurred_(const char *fname, unsigned int line, } log_warn(LD_BUG, "%s:%u: %s: This line should not have been reached.%s", fname, line, func, once_str); - tor_snprintf(buf, sizeof(buf), + tor_asprintf(&buf, "Line unexpectedly reached at %s at %s:%u", func, fname, line); } else { @@ -105,13 +125,32 @@ tor_bug_occurred_(const char *fname, unsigned int line, add_captured_bug(expr); return; } + + va_list ap; + char *extra = NULL; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + if (fmt) { + va_start(ap,fmt); + tor_vasprintf(&extra, fmt, ap); + va_end(ap); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + log_warn(LD_BUG, "%s:%u: %s: Non-fatal assertion %s failed.%s", fname, line, func, expr, once_str); - tor_snprintf(buf, sizeof(buf), - "Non-fatal assertion %s failed in %s at %s:%u", - expr, func, fname, line); + tor_asprintf(&buf, "Non-fatal assertion %s failed in %s at %s:%u%s%s", + expr, func, fname, line, fmt ? " : " : "", + extra ? extra : ""); + tor_free(extra); } log_backtrace(LOG_WARN, LD_BUG, buf); + tor_free(buf); #ifdef TOR_UNIT_TESTS if (failed_assertion_cb) { diff --git a/src/lib/log/util_bug.h b/src/lib/log/util_bug.h index 18d40bbf39..63c5309c98 100644 --- a/src/lib/log/util_bug.h +++ b/src/lib/log/util_bug.h @@ -92,13 +92,20 @@ #define tor_assert(a) STMT_BEGIN \ (void)(a); \ STMT_END +#define tor_assertf(a, fmt, ...) STMT_BEGIN \ + (void)(a); \ + (void)(fmt); \ + STMT_END #else /** Like assert(3), but send assertion failures to the log as well as to * stderr. */ -#define tor_assert(expr) STMT_BEGIN \ +#define tor_assert(expr) tor_assertf(expr, NULL) + +#define tor_assertf(expr, fmt, ...) STMT_BEGIN \ if (ASSERT_PREDICT_LIKELY_(expr)) { \ } else { \ - tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr); \ + tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr, \ + fmt, ##__VA_ARGS__); \ abort(); \ } STMT_END #endif /* defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) */ @@ -106,7 +113,7 @@ #define tor_assert_unreached() \ STMT_BEGIN { \ tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, \ - "line should be unreached"); \ + "line should be unreached", NULL); \ abort(); \ } STMT_END @@ -136,6 +143,7 @@ #ifdef ALL_BUGS_ARE_FATAL #define tor_assert_nonfatal_unreached() tor_assert(0) #define tor_assert_nonfatal(cond) tor_assert((cond)) +#define tor_assertf_nonfatal(cond, fmt, ...) tor_assertf(cond, fmt, ...) #define tor_assert_nonfatal_unreached_once() tor_assert(0) #define tor_assert_nonfatal_once(cond) tor_assert((cond)) #define BUG(cond) \ @@ -146,24 +154,35 @@ #elif defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) #define tor_assert_nonfatal_unreached() STMT_NIL #define tor_assert_nonfatal(cond) ((void)(cond)) +#define tor_assertf_nonfatal(cond, fmt, ...) STMT_BEGIN \ + (void)cond; \ + (void)fmt; \ + STMT_END #define tor_assert_nonfatal_unreached_once() STMT_NIL #define tor_assert_nonfatal_once(cond) ((void)(cond)) #define BUG(cond) (ASSERT_PREDICT_UNLIKELY_(cond) ? 1 : 0) #else /* Normal case, !ALL_BUGS_ARE_FATAL, !DISABLE_ASSERTS_IN_UNIT_TESTS */ #define tor_assert_nonfatal_unreached() STMT_BEGIN \ - tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 0); \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 0, NULL); \ STMT_END #define tor_assert_nonfatal(cond) STMT_BEGIN \ if (ASSERT_PREDICT_LIKELY_(cond)) { \ } else { \ - tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 0); \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 0, NULL);\ + } \ + STMT_END +#define tor_assertf_nonfatal(cond, fmt, ...) STMT_BEGIN \ + if (ASSERT_PREDICT_UNLIKELY_(cond)) { \ + } else { \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 0, \ + fmt, ##__VA_ARGS__); \ } \ STMT_END #define tor_assert_nonfatal_unreached_once() STMT_BEGIN \ static int warning_logged__ = 0; \ if (!warning_logged__) { \ warning_logged__ = 1; \ - tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 1); \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 1, NULL); \ } \ STMT_END #define tor_assert_nonfatal_once(cond) STMT_BEGIN \ @@ -171,12 +190,12 @@ if (ASSERT_PREDICT_LIKELY_(cond)) { \ } else if (!warning_logged__) { \ warning_logged__ = 1; \ - tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 1); \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 1, NULL);\ } \ STMT_END #define BUG(cond) \ (ASSERT_PREDICT_UNLIKELY_(cond) ? \ - (tor_bug_occurred_(SHORT_FILE__,__LINE__,__func__,"!("#cond")",0), 1) \ + (tor_bug_occurred_(SHORT_FILE__,__LINE__,__func__,"!("#cond")",1,NULL),1) \ : 0) #endif /* defined(ALL_BUGS_ARE_FATAL) || ... */ @@ -188,7 +207,7 @@ if (bool_result && !var) { \ var = 1; \ tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, \ - "!("#cond")", 1); \ + "!("#cond")", 1, NULL); \ } \ bool_result; } )) #else /* !(defined(__GNUC__)) */ @@ -198,7 +217,7 @@ (var ? 1 : \ (var=1, \ tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, \ - "!("#cond")", 1), \ + "!("#cond")", 1, NULL), \ 1)) \ : 0) #endif /* defined(__GNUC__) */ @@ -221,10 +240,11 @@ #define tor_fragile_assert() tor_assert_nonfatal_unreached_once() void tor_assertion_failed_(const char *fname, unsigned int line, - const char *func, const char *expr); + const char *func, const char *expr, + const char *fmt, ...); void tor_bug_occurred_(const char *fname, unsigned int line, const char *func, const char *expr, - int once); + int once, const char *fmt, ...); #ifdef _WIN32 #define SHORT_FILE__ (tor_fix_source_file(__FILE__)) diff --git a/src/lib/malloc/map_anon.c b/src/lib/malloc/map_anon.c index 2fc6e89ea2..5dac5256a6 100644 --- a/src/lib/malloc/map_anon.c +++ b/src/lib/malloc/map_anon.c @@ -107,6 +107,29 @@ nodump_mem(void *mem, size_t sz) #endif } +#ifdef TOR_UNIT_TESTS +static unsigned last_anon_map_noinherit = ~0; +/* Testing helper: return the outcome of the last call to noinherit_mem(): + * 0 if it did no good; 1 if it caused the memory not to be inherited, and + * 2 if it caused the memory to be cleared on fork */ +unsigned +get_last_anon_map_noinherit(void) +{ + return last_anon_map_noinherit; +} +static void +set_last_anon_map_noinherit(unsigned f) +{ + last_anon_map_noinherit = f; +} +#else +static void +set_last_anon_map_noinherit(unsigned f) +{ + (void)f; +} +#endif + /** * Helper: try to prevent the <b>sz</b> bytes at <b>mem</b> from being * accessible in child processes -- ideally by having them set to 0 after a @@ -117,13 +140,20 @@ nodump_mem(void *mem, size_t sz) static int noinherit_mem(void *mem, size_t sz) { + set_last_anon_map_noinherit(0); #ifdef FLAG_ZERO int r = MINHERIT(mem, sz, FLAG_ZERO); - if (r == 0) + if (r == 0) { + set_last_anon_map_noinherit(2); return 0; + } #endif #ifdef FLAG_NOINHERIT - return MINHERIT(mem, sz, FLAG_NOINHERIT); + int r2 = MINHERIT(mem, sz, FLAG_NOINHERIT); + if (r2 == 0) { + set_last_anon_map_noinherit(1); + } + return r2; #else (void)mem; (void)sz; diff --git a/src/lib/malloc/map_anon.h b/src/lib/malloc/map_anon.h index cc5797e4ec..395145bd71 100644 --- a/src/lib/malloc/map_anon.h +++ b/src/lib/malloc/map_anon.h @@ -34,4 +34,8 @@ void *tor_mmap_anonymous(size_t sz, unsigned flags); void tor_munmap_anonymous(void *mapping, size_t sz); +#ifdef TOR_UNIT_TESTS +unsigned get_last_anon_map_noinherit(void); +#endif + #endif /* !defined(TOR_MAP_ANON_H) */ diff --git a/src/lib/math/prob_distr.c b/src/lib/math/prob_distr.c index c952dadc06..d44dc28265 100644 --- a/src/lib/math/prob_distr.c +++ b/src/lib/math/prob_distr.c @@ -46,26 +46,27 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/cc/ctassert.h" +#include "lib/log/util_bug.h" #include <float.h> #include <math.h> #include <stddef.h> -/** Validators for downcasting macros below */ -#define validate_container_of(PTR, TYPE, FIELD) \ - (0 * sizeof((PTR) - &((TYPE *)(((char *)(PTR)) - \ - offsetof(TYPE, FIELD)))->FIELD)) -#define validate_const_container_of(PTR, TYPE, FIELD) \ - (0 * sizeof((PTR) - &((const TYPE *)(((const char *)(PTR)) - \ - offsetof(TYPE, FIELD)))->FIELD)) -/** Downcasting macro */ -#define container_of(PTR, TYPE, FIELD) \ - ((TYPE *)(((char *)(PTR)) - offsetof(TYPE, FIELD)) \ - + validate_container_of(PTR, TYPE, FIELD)) -/** Constified downcasting macro */ -#define const_container_of(PTR, TYPE, FIELD) \ - ((const TYPE *)(((const char *)(PTR)) - offsetof(TYPE, FIELD)) \ - + validate_const_container_of(PTR, TYPE, FIELD)) +/** Declare a function that downcasts from a generic dist struct to the actual + * subtype probablity distribution it represents. */ +#define DECLARE_PROB_DISTR_DOWNCAST_FN(name) \ + static inline \ + const struct name * \ + dist_to_const_##name(const struct dist *obj) { \ + tor_assert(obj->ops == &name##_ops); \ + return SUBTYPE_P(obj, struct name, base); \ + } +DECLARE_PROB_DISTR_DOWNCAST_FN(uniform) +DECLARE_PROB_DISTR_DOWNCAST_FN(geometric) +DECLARE_PROB_DISTR_DOWNCAST_FN(logistic) +DECLARE_PROB_DISTR_DOWNCAST_FN(log_logistic) +DECLARE_PROB_DISTR_DOWNCAST_FN(genpareto) +DECLARE_PROB_DISTR_DOWNCAST_FN(weibull) /** * Count number of one bits in 32-bit word. @@ -458,7 +459,7 @@ random_uniform_01(void) * system is broken. */ z = 0; - while ((x = crypto_rand_u32()) == 0) { + while ((x = crypto_fast_rng_get_u32(get_thread_fast_rng())) == 0) { if (z >= 1088) /* Your bit sampler is broken. Go home. */ return 0; @@ -472,8 +473,8 @@ random_uniform_01(void) * occur only with measure zero in the uniform distribution on * [0, 1]. */ - hi = crypto_rand_u32() | UINT32_C(0x80000000); - lo = crypto_rand_u32() | UINT32_C(0x00000001); + hi = crypto_fast_rng_get_u32(get_thread_fast_rng()) | UINT32_C(0x80000000); + lo = crypto_fast_rng_get_u32(get_thread_fast_rng()) | UINT32_C(0x00000001); /* Round to nearest scaled significand in [2^63, 2^64]. */ s = hi*(double)4294967296 + lo; @@ -1315,40 +1316,48 @@ sample_geometric(uint32_t s, double p0, double p) /** Public API for probability distributions: * - * For each probability distribution we define each public functions - * (sample/cdf/sf/icdf/isf) as part of its dist_ops structure. + * These are wrapper functions on top of the various probability distribution + * operations using the generic <b>dist</b> structure. + + * These are the functions that should be used by consumers of this API. */ +/** Returns the name of the distribution in <b>dist</b>. */ const char * dist_name(const struct dist *dist) { return dist->ops->name; } +/* Sample a value from <b>dist</b> and return it. */ double dist_sample(const struct dist *dist) { return dist->ops->sample(dist); } +/** Compute the CDF of <b>dist</b> at <b>x</b>. */ double dist_cdf(const struct dist *dist, double x) { return dist->ops->cdf(dist, x); } +/** Compute the SF (Survival function) of <b>dist</b> at <b>x</b>. */ double dist_sf(const struct dist *dist, double x) { return dist->ops->sf(dist, x); } +/** Compute the iCDF (Inverse CDF) of <b>dist</b> at <b>x</b>. */ double dist_icdf(const struct dist *dist, double p) { return dist->ops->icdf(dist, p); } +/** Compute the iSF (Inverse Survival function) of <b>dist</b> at <b>x</b>. */ double dist_isf(const struct dist *dist, double p) { @@ -1360,8 +1369,7 @@ dist_isf(const struct dist *dist, double p) static double uniform_sample(const struct dist *dist) { - const struct uniform *U = const_container_of(dist, struct uniform, - base); + const struct uniform *U = dist_to_const_uniform(dist); double p0 = random_uniform_01(); return sample_uniform_interval(p0, U->a, U->b); @@ -1370,9 +1378,7 @@ uniform_sample(const struct dist *dist) static double uniform_cdf(const struct dist *dist, double x) { - const struct uniform *U = const_container_of(dist, struct uniform, - base); - + const struct uniform *U = dist_to_const_uniform(dist); if (x < U->a) return 0; else if (x < U->b) @@ -1384,8 +1390,7 @@ uniform_cdf(const struct dist *dist, double x) static double uniform_sf(const struct dist *dist, double x) { - const struct uniform *U = const_container_of(dist, struct uniform, - base); + const struct uniform *U = dist_to_const_uniform(dist); if (x > U->b) return 0; @@ -1398,8 +1403,7 @@ uniform_sf(const struct dist *dist, double x) static double uniform_icdf(const struct dist *dist, double p) { - const struct uniform *U = const_container_of(dist, struct uniform, - base); + const struct uniform *U = dist_to_const_uniform(dist); double w = U->b - U->a; return (p < 0.5 ? (U->a + w*p) : (U->b - w*(1 - p))); @@ -1408,8 +1412,7 @@ uniform_icdf(const struct dist *dist, double p) static double uniform_isf(const struct dist *dist, double p) { - const struct uniform *U = const_container_of(dist, struct uniform, - base); + const struct uniform *U = dist_to_const_uniform(dist); double w = U->b - U->a; return (p < 0.5 ? (U->b - w*p) : (U->a + w*(1 - p))); @@ -1424,14 +1427,17 @@ const struct dist_ops uniform_ops = { .isf = uniform_isf, }; +/*******************************************************************/ + +/** Private functions for each probability distribution. */ + /** Functions for logistic distribution: */ static double logistic_sample(const struct dist *dist) { - const struct logistic *L = const_container_of(dist, struct logistic, - base); - uint32_t s = crypto_rand_u32(); + const struct logistic *L = dist_to_const_logistic(dist); + uint32_t s = crypto_fast_rng_get_u32(get_thread_fast_rng()); double t = random_uniform_01(); double p0 = random_uniform_01(); @@ -1441,36 +1447,28 @@ logistic_sample(const struct dist *dist) static double logistic_cdf(const struct dist *dist, double x) { - const struct logistic *L = const_container_of(dist, struct logistic, - base); - + const struct logistic *L = dist_to_const_logistic(dist); return cdf_logistic(x, L->mu, L->sigma); } static double logistic_sf(const struct dist *dist, double x) { - const struct logistic *L = const_container_of(dist, struct logistic, - base); - + const struct logistic *L = dist_to_const_logistic(dist); return sf_logistic(x, L->mu, L->sigma); } static double logistic_icdf(const struct dist *dist, double p) { - const struct logistic *L = const_container_of(dist, struct logistic, - base); - + const struct logistic *L = dist_to_const_logistic(dist); return icdf_logistic(p, L->mu, L->sigma); } static double logistic_isf(const struct dist *dist, double p) { - const struct logistic *L = const_container_of(dist, struct logistic, - base); - + const struct logistic *L = dist_to_const_logistic(dist); return isf_logistic(p, L->mu, L->sigma); } @@ -1488,9 +1486,8 @@ const struct dist_ops logistic_ops = { static double log_logistic_sample(const struct dist *dist) { - const struct log_logistic *LL = const_container_of(dist, struct - log_logistic, base); - uint32_t s = crypto_rand_u32(); + const struct log_logistic *LL = dist_to_const_log_logistic(dist); + uint32_t s = crypto_fast_rng_get_u32(get_thread_fast_rng()); double p0 = random_uniform_01(); return sample_log_logistic_scaleshape(s, p0, LL->alpha, LL->beta); @@ -1499,36 +1496,28 @@ log_logistic_sample(const struct dist *dist) static double log_logistic_cdf(const struct dist *dist, double x) { - const struct log_logistic *LL = const_container_of(dist, - struct log_logistic, base); - + const struct log_logistic *LL = dist_to_const_log_logistic(dist); return cdf_log_logistic(x, LL->alpha, LL->beta); } static double log_logistic_sf(const struct dist *dist, double x) { - const struct log_logistic *LL = const_container_of(dist, - struct log_logistic, base); - + const struct log_logistic *LL = dist_to_const_log_logistic(dist); return sf_log_logistic(x, LL->alpha, LL->beta); } static double log_logistic_icdf(const struct dist *dist, double p) { - const struct log_logistic *LL = const_container_of(dist, - struct log_logistic, base); - + const struct log_logistic *LL = dist_to_const_log_logistic(dist); return icdf_log_logistic(p, LL->alpha, LL->beta); } static double log_logistic_isf(const struct dist *dist, double p) { - const struct log_logistic *LL = const_container_of(dist, - struct log_logistic, base); - + const struct log_logistic *LL = dist_to_const_log_logistic(dist); return isf_log_logistic(p, LL->alpha, LL->beta); } @@ -1546,9 +1535,8 @@ const struct dist_ops log_logistic_ops = { static double weibull_sample(const struct dist *dist) { - const struct weibull *W = const_container_of(dist, struct weibull, - base); - uint32_t s = crypto_rand_u32(); + const struct weibull *W = dist_to_const_weibull(dist); + uint32_t s = crypto_fast_rng_get_u32(get_thread_fast_rng()); double p0 = random_uniform_01(); return sample_weibull(s, p0, W->lambda, W->k); @@ -1557,36 +1545,28 @@ weibull_sample(const struct dist *dist) static double weibull_cdf(const struct dist *dist, double x) { - const struct weibull *W = const_container_of(dist, struct weibull, - base); - + const struct weibull *W = dist_to_const_weibull(dist); return cdf_weibull(x, W->lambda, W->k); } static double weibull_sf(const struct dist *dist, double x) { - const struct weibull *W = const_container_of(dist, struct weibull, - base); - + const struct weibull *W = dist_to_const_weibull(dist); return sf_weibull(x, W->lambda, W->k); } static double weibull_icdf(const struct dist *dist, double p) { - const struct weibull *W = const_container_of(dist, struct weibull, - base); - + const struct weibull *W = dist_to_const_weibull(dist); return icdf_weibull(p, W->lambda, W->k); } static double weibull_isf(const struct dist *dist, double p) { - const struct weibull *W = const_container_of(dist, struct weibull, - base); - + const struct weibull *W = dist_to_const_weibull(dist); return isf_weibull(p, W->lambda, W->k); } @@ -1604,9 +1584,8 @@ const struct dist_ops weibull_ops = { static double genpareto_sample(const struct dist *dist) { - const struct genpareto *GP = const_container_of(dist, struct genpareto, - base); - uint32_t s = crypto_rand_u32(); + const struct genpareto *GP = dist_to_const_genpareto(dist); + uint32_t s = crypto_fast_rng_get_u32(get_thread_fast_rng()); double p0 = random_uniform_01(); return sample_genpareto_locscale(s, p0, GP->mu, GP->sigma, GP->xi); @@ -1615,36 +1594,28 @@ genpareto_sample(const struct dist *dist) static double genpareto_cdf(const struct dist *dist, double x) { - const struct genpareto *GP = const_container_of(dist, struct genpareto, - base); - + const struct genpareto *GP = dist_to_const_genpareto(dist); return cdf_genpareto(x, GP->mu, GP->sigma, GP->xi); } static double genpareto_sf(const struct dist *dist, double x) { - const struct genpareto *GP = const_container_of(dist, struct genpareto, - base); - + const struct genpareto *GP = dist_to_const_genpareto(dist); return sf_genpareto(x, GP->mu, GP->sigma, GP->xi); } static double genpareto_icdf(const struct dist *dist, double p) { - const struct genpareto *GP = const_container_of(dist, struct genpareto, - base); - + const struct genpareto *GP = dist_to_const_genpareto(dist); return icdf_genpareto(p, GP->mu, GP->sigma, GP->xi); } static double genpareto_isf(const struct dist *dist, double p) { - const struct genpareto *GP = const_container_of(dist, struct genpareto, - base); - + const struct genpareto *GP = dist_to_const_genpareto(dist); return isf_genpareto(p, GP->mu, GP->sigma, GP->xi); } @@ -1662,8 +1633,8 @@ const struct dist_ops genpareto_ops = { static double geometric_sample(const struct dist *dist) { - const struct geometric *G = const_container_of(dist, struct geometric, base); - uint32_t s = crypto_rand_u32(); + const struct geometric *G = dist_to_const_geometric(dist); + uint32_t s = crypto_fast_rng_get_u32(get_thread_fast_rng()); double p0 = random_uniform_01(); return sample_geometric(s, p0, G->p); @@ -1672,7 +1643,7 @@ geometric_sample(const struct dist *dist) static double geometric_cdf(const struct dist *dist, double x) { - const struct geometric *G = const_container_of(dist, struct geometric, base); + const struct geometric *G = dist_to_const_geometric(dist); if (x < 1) return 0; @@ -1683,7 +1654,7 @@ geometric_cdf(const struct dist *dist, double x) static double geometric_sf(const struct dist *dist, double x) { - const struct geometric *G = const_container_of(dist, struct geometric, base); + const struct geometric *G = dist_to_const_geometric(dist); if (x < 1) return 0; @@ -1694,7 +1665,7 @@ geometric_sf(const struct dist *dist, double x) static double geometric_icdf(const struct dist *dist, double p) { - const struct geometric *G = const_container_of(dist, struct geometric, base); + const struct geometric *G = dist_to_const_geometric(dist); return log1p(-p)/log1p(-G->p); } @@ -1702,7 +1673,7 @@ geometric_icdf(const struct dist *dist, double p) static double geometric_isf(const struct dist *dist, double p) { - const struct geometric *G = const_container_of(dist, struct geometric, base); + const struct geometric *G = dist_to_const_geometric(dist); return log(p)/log1p(-G->p); } diff --git a/src/lib/math/prob_distr.h b/src/lib/math/prob_distr.h index 66acb796fd..2eb935e4a8 100644 --- a/src/lib/math/prob_distr.h +++ b/src/lib/math/prob_distr.h @@ -19,10 +19,100 @@ struct dist { const struct dist_ops *ops; }; +/** + * Untyped initializer element for struct dist using the specified + * struct dist_ops pointer. Don't actually use this directly -- use + * the type-specific macro built out of DIST_BASE_TYPED below -- but if + * you did use this directly, it would be something like: + * + * struct weibull mydist = { + * DIST_BASE(&weibull_ops), + * .lambda = ..., + * .k = ..., + * }; + * + * Note there is NO COMPILER FEEDBACK if you accidentally do something + * like + * + * struct geometric mydist = { + * DIST_BASE(&weibull_ops), + * ... + * }; + */ #define DIST_BASE(OPS) { .ops = (OPS) } + +/** A compile-time type-checking macro for use with DIST_BASE_TYPED. + * + * This macro works by checking that &OBJ is a pointer type that is the same + * type (except for qualifiers) as (const TYPE *)&OBJ. It's a C constraint + * violation (which requires a diagnostic) if two pointers are different types + * and are subtracted. The sizeof() forces compile-time evaluation, and the + * multiplication by zero is to discard the result of the sizeof() from the + * expression. + * + * We define this conditionally to suppress false positives from + * Coverity, which gets confused by the sizeof business. + */ +#ifdef __COVERITY___ +#define TYPE_CHECK_OBJ(OPS, OBJ, TYPE) 0 +#else +#define TYPE_CHECK_OBJ(OPS, OBJ, TYPE) \ + (0*sizeof(&(OBJ) - (const TYPE *)&(OBJ))) +#endif + +/** +* Typed initializer element for struct dist using the specified struct +* dist_ops pointer. Don't actually use this directly -- use a +* type-specific macro built out of it -- but if you did use this +* directly, it would be something like: +* +* struct weibull mydist = { +* DIST_BASE_TYPED(&weibull_ops, mydist, struct weibull), +* .lambda = ..., +* .k = ..., +* }; +* +* If you want to define a distribution type, define a canonical set of +* operations and define a type-specific initializer element like so: +* +* struct foo { +* struct dist base; +* int omega; +* double tau; +* double phi; +* }; +* +* struct dist_ops foo_ops = ...; +* +* #define FOO(OBJ) DIST_BASE_TYPED(&foo_ops, OBJ, struct foo) +* +* Then users can do: +* +* struct foo mydist = { +* FOO(mydist), +* .omega = ..., +* .tau = ..., +* .phi = ..., +* }; +* +* If you accidentally write +* +* struct bar mydist = { +* FOO(mydist), +* ... +* }; +* +* then the compiler will report a type mismatch in the sizeof +* expression, which otherwise evaporates at runtime. +*/ #define DIST_BASE_TYPED(OPS, OBJ, TYPE) \ - DIST_BASE((OPS) + 0*sizeof(&(OBJ) - (const TYPE *)&(OBJ))) + DIST_BASE((OPS) + TYPE_CHECK_OBJ(OPS,OBJ,TYPE)) +/** + * Generic operations on distributions. These simply defer to the + * corresponding dist_ops function. In the parlance of C++, these call + * virtual member functions. + */ const char *dist_name(const struct dist *); double dist_sample(const struct dist *); double dist_cdf(const struct dist *, double x); @@ -30,6 +120,11 @@ double dist_sf(const struct dist *, double x); double dist_icdf(const struct dist *, double p); double dist_isf(const struct dist *, double p); +/** + * Set of operations on a potentially parametric family of + * distributions. In the parlance of C++, this would be called a + * `vtable' and the members are virtual member functions. + */ struct dist_ops { const char *name; double (*sample)(const struct dist *); diff --git a/src/lib/pubsub/.may_include b/src/lib/pubsub/.may_include new file mode 100644 index 0000000000..5623492f00 --- /dev/null +++ b/src/lib/pubsub/.may_include @@ -0,0 +1,10 @@ +orconfig.h + +lib/cc/*.h +lib/container/*.h +lib/dispatch/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h +lib/pubsub/*.h +lib/string/*.h diff --git a/src/lib/pubsub/include.am b/src/lib/pubsub/include.am new file mode 100644 index 0000000000..c0ec13d039 --- /dev/null +++ b/src/lib/pubsub/include.am @@ -0,0 +1,26 @@ + +noinst_LIBRARIES += src/lib/libtor-pubsub.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-pubsub-testing.a +endif + +src_lib_libtor_pubsub_a_SOURCES = \ + src/lib/pubsub/pubsub_build.c \ + src/lib/pubsub/pubsub_check.c \ + src/lib/pubsub/pubsub_publish.c + +src_lib_libtor_pubsub_testing_a_SOURCES = \ + $(src_lib_libtor_pubsub_a_SOURCES) +src_lib_libtor_pubsub_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_pubsub_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/pubsub/pub_binding_st.h \ + src/lib/pubsub/pubsub.h \ + src/lib/pubsub/pubsub_build.h \ + src/lib/pubsub/pubsub_builder_st.h \ + src/lib/pubsub/pubsub_connect.h \ + src/lib/pubsub/pubsub_flags.h \ + src/lib/pubsub/pubsub_macros.h \ + src/lib/pubsub/pubsub_publish.h diff --git a/src/lib/pubsub/pub_binding_st.h b/src/lib/pubsub/pub_binding_st.h new file mode 100644 index 0000000000..4f5df8ff38 --- /dev/null +++ b/src/lib/pubsub/pub_binding_st.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pub_binding_st.h + * @brief Declaration of pub_binding_t. + * + * This is an internal type for the pubsub implementation. + */ + +#ifndef TOR_PUB_BINDING_ST_H +#define TOR_PUB_BINDING_ST_H + +#include "lib/dispatch/msgtypes.h" +struct dispatch_t; + +/** + * A pub_binding_t is an opaque object that subsystems use to publish + * messages. The DISPATCH_ADD_PUB*() macros set it up. + **/ +typedef struct pub_binding_t { + /** + * A pointer to a configured dispatch_t object. This is filled in + * when the dispatch_t is finally constructed. + **/ + struct dispatch_t *dispatch_ptr; + /** + * A template for the msg_t fields that are filled in for this message. + * This is copied into outgoing messages, ensuring that their fields are set + * corretly. + **/ + msg_t msg_template; +} pub_binding_t; + +#endif diff --git a/src/lib/pubsub/pubsub.h b/src/lib/pubsub/pubsub.h new file mode 100644 index 0000000000..08e3f42f7c --- /dev/null +++ b/src/lib/pubsub/pubsub.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub.h + * @brief Header for OO publish-subscribe functionality. + * + * This module provides a wrapper around the "dispatch" module, + * ensuring type-safety and allowing us to do static analysis on + * publication and subscriptions. + * + * With this module, we enforce: + * <ul> + * <li>that every message has (potential) publishers and subscribers; + * <li>that every message is published and subscribed from the correct + * channels, with the correct type ID, every time it is published. + * <li>that type IDs correspond to a single C type, and that the C types are + * used correctly. + * <li>that when a message is published or subscribed, it is done with + * a correct subsystem identifier + * </ul> + * + * We do this by making "publication requests" and "subscription requests" + * into objects, and doing some computation on them before we create + * a dispatch_t with them. + * + * Rather than using the dispatch module directly, a publishing module + * receives a "binding" object that it uses to send messages with the right + * settings. + * + * Most users of this module will want to use this header, and the + * pubsub_macros.h header for convenience. + */ + +/* + * + * Overview: Messages are sent over channels. Before sending a message on a + * channel, or receiving a message on a channel, a subsystem needs to register + * that it publishes, or subscribes, to that message, on that channel. + * + * Messages, channels, and subsystems are represented internally as short + * integers, though they are associated with human-readable strings for + * initialization and debugging. + * + * When registering for a message, a subsystem must say whether it is an + * exclusive publisher/subscriber to that message type, or whether other + * subsystems may also publish/subscribe to it. + * + * All messages and their publishers/subscribers must be registered early in + * the initialization process. + * + * By default, it is an error for a message type to have publishers and no + * subscribers on a channel, or subscribers and no publishers on a channel. + * + * A subsystem may register for a message with a note that delivery or + * production is disabled -- for example, because the subsystem is + * disabled at compile-time. It is not an error for a message type to + * have all of its publishers or subscribers disabled. + * + * After a message is sent, it is delivered to every recipient. This + * delivery happens from the top level of the event loop; it may be + * interleaved with network events, timers, etc. + * + * Messages may have associated data. This data is typed, and is owned + * by the message. Strings, byte-arrays, and integers have built-in + * support. Other types may be added. If objects are to be sent, + * they should be identified by handle. If an object requires cleanup, + * it should be declared with an associated free function. + * + * Semantically, if two subsystems communicate only by this kind of + * message passing, neither is considered to depend on the other, though + * both are considered to have a dependency on the message and on any + * types it contains. + * + * (Or generational index?) + */ +#ifndef TOR_PUBSUB_PUBSUB_H +#define TOR_PUBSUB_PUBSUB_H + +#include "lib/pubsub/pub_binding_st.h" +#include "lib/pubsub/pubsub_connect.h" +#include "lib/pubsub/pubsub_flags.h" +#include "lib/pubsub/pubsub_macros.h" +#include "lib/pubsub/pubsub_publish.h" + +#endif diff --git a/src/lib/pubsub/pubsub_build.c b/src/lib/pubsub/pubsub_build.c new file mode 100644 index 0000000000..e44b7d76ec --- /dev/null +++ b/src/lib/pubsub/pubsub_build.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-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_build.c + * @brief Construct a dispatch_t in safer, more OO way. + **/ + +#define PUBSUB_PRIVATE + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_cfg.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/msgtypes.h" +#include "lib/pubsub/pubsub_flags.h" +#include "lib/pubsub/pub_binding_st.h" +#include "lib/pubsub/pubsub_build.h" +#include "lib/pubsub/pubsub_builder_st.h" +#include "lib/pubsub/pubsub_connect.h" + +#include "lib/container/smartlist.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + + #include <string.h> + +/** Construct and return a new empty pubsub_items_t. */ +static pubsub_items_t * +pubsub_items_new(void) +{ + pubsub_items_t *cfg = tor_malloc_zero(sizeof(*cfg)); + cfg->items = smartlist_new(); + cfg->type_items = smartlist_new(); + return cfg; +} + +/** Release all storage held in a pubsub_items_t. */ +void +pubsub_items_free_(pubsub_items_t *cfg) +{ + if (! cfg) + return; + SMARTLIST_FOREACH(cfg->items, pubsub_cfg_t *, item, tor_free(item)); + SMARTLIST_FOREACH(cfg->type_items, + pubsub_type_cfg_t *, item, tor_free(item)); + smartlist_free(cfg->items); + smartlist_free(cfg->type_items); + tor_free(cfg); +} + +/** Construct and return a new pubsub_builder_t. */ +pubsub_builder_t * +pubsub_builder_new(void) +{ + dispatch_naming_init(); + + pubsub_builder_t *pb = tor_malloc_zero(sizeof(*pb)); + pb->cfg = dcfg_new(); + pb->items = pubsub_items_new(); + return pb; +} + +/** + * Release all storage held by a pubsub_builder_t. + * + * You'll (mostly) only want to call this function on an error case: if you're + * constructing a dispatch_t instead, you should call + * pubsub_builder_finalize() to consume the pubsub_builder_t. + */ +void +pubsub_builder_free_(pubsub_builder_t *pb) +{ + if (pb == NULL) + return; + pubsub_items_free(pb->items); + dcfg_free(pb->cfg); + tor_free(pb); +} + +/** + * Create and return a pubsub_connector_t for the subsystem with ID + * <b>subsys</b> to use in adding publications, subscriptions, and types to + * <b>builder</b>. + **/ +pubsub_connector_t * +pubsub_connector_for_subsystem(pubsub_builder_t *builder, + subsys_id_t subsys) +{ + tor_assert(builder); + ++builder->n_connectors; + + pubsub_connector_t *con = tor_malloc_zero(sizeof(*con)); + + con->builder = builder; + con->subsys_id = subsys; + + return con; +} + +/** + * Release all storage held by a pubsub_connector_t. + **/ +void +pubsub_connector_free_(pubsub_connector_t *con) +{ + if (!con) + return; + + if (con->builder) { + --con->builder->n_connectors; + tor_assert(con->builder->n_connectors >= 0); + } + tor_free(con); +} + +/** + * Use <b>con</b> to add a request for being able to publish messages of type + * <b>msg</b> with auxiliary data of <b>type</b> on <b>channel</b>. + **/ +int +pubsub_add_pub_(pubsub_connector_t *con, + pub_binding_t *out, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + unsigned flags, + const char *file, + unsigned line) +{ + pubsub_cfg_t *cfg = tor_malloc_zero(sizeof(*cfg)); + + memset(out, 0, sizeof(*out)); + cfg->is_publish = true; + + out->msg_template.sender = cfg->subsys = con->subsys_id; + out->msg_template.channel = cfg->channel = channel; + out->msg_template.msg = cfg->msg = msg; + out->msg_template.type = cfg->type = type; + + cfg->flags = flags; + cfg->added_by_file = file; + cfg->added_by_line = line; + + /* We're grabbing a pointer to the pub_binding_t so we can tell it about + * the dispatcher later on. + */ + cfg->pub_binding = out; + + smartlist_add(con->builder->items->items, cfg); + + if (dcfg_msg_set_type(con->builder->cfg, msg, type) < 0) + goto err; + if (dcfg_msg_set_chan(con->builder->cfg, msg, channel) < 0) + goto err; + + return 0; + err: + ++con->builder->n_errors; + return -1; +} + +/** + * Use <b>con</b> to add a request for being able to publish messages of type + * <b>msg</b> with auxiliary data of <b>type</b> on <b>channel</b>, + * passing them to the callback in <b>recv_fn</b>. + **/ +int +pubsub_add_sub_(pubsub_connector_t *con, + recv_fn_t recv_fn, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + unsigned flags, + const char *file, + unsigned line) +{ + pubsub_cfg_t *cfg = tor_malloc_zero(sizeof(*cfg)); + + cfg->is_publish = false; + cfg->subsys = con->subsys_id; + cfg->channel = channel; + cfg->msg = msg; + cfg->type = type; + cfg->flags = flags; + cfg->added_by_file = file; + cfg->added_by_line = line; + + cfg->recv_fn = recv_fn; + + smartlist_add(con->builder->items->items, cfg); + + if (dcfg_msg_set_type(con->builder->cfg, msg, type) < 0) + goto err; + if (dcfg_msg_set_chan(con->builder->cfg, msg, channel) < 0) + goto err; + if (! (flags & DISP_FLAG_STUB)) { + if (dcfg_add_recv(con->builder->cfg, msg, cfg->subsys, recv_fn) < 0) + goto err; + } + + return 0; + err: + ++con->builder->n_errors; + return -1; +} + +/** + * Use <b>con</b> to define the functions to use for manipulating the type + * <b>type</b>. Any function pointers left as NULL will be implemented as + * no-ops. + **/ +int +pubsub_connector_register_type_(pubsub_connector_t *con, + msg_type_id_t type, + dispatch_typefns_t *fns, + const char *file, + unsigned line) +{ + pubsub_type_cfg_t *cfg = tor_malloc_zero(sizeof(*cfg)); + cfg->type = type; + memcpy(&cfg->fns, fns, sizeof(*fns)); + cfg->subsys = con->subsys_id; + cfg->added_by_file = file; + cfg->added_by_line = line; + + smartlist_add(con->builder->items->type_items, cfg); + + if (dcfg_type_set_fns(con->builder->cfg, type, fns) < 0) + goto err; + + return 0; + err: + ++con->builder->n_errors; + return -1; +} + +/** + * Initialize the dispatch_ptr field in every relevant publish binding + * for <b>d</b>. + */ +static void +pubsub_items_install_bindings(pubsub_items_t *items, + dispatch_t *d) +{ + SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, cfg) { + if (cfg->pub_binding) { + // XXXX we could skip this for STUB publishers, and for any publishers + // XXXX where all subscribers are STUB. + cfg->pub_binding->dispatch_ptr = d; + } + } SMARTLIST_FOREACH_END(cfg); +} + +/** + * Remove the dispatch_ptr fields for all the relevant publish bindings + * in <b>items</b>. The prevents subsequent dispatch_pub_() calls from + * sending messages to a dispatcher that has been freed. + **/ +void +pubsub_items_clear_bindings(pubsub_items_t *items) +{ + SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, cfg) { + if (cfg->pub_binding) { + cfg->pub_binding->dispatch_ptr = NULL; + } + } SMARTLIST_FOREACH_END(cfg); +} + +/** + * Create a new dispatcher as configured in a pubsub_builder_t. + * + * Consumes and frees its input. + **/ +dispatch_t * +pubsub_builder_finalize(pubsub_builder_t *builder, + pubsub_items_t **items_out) +{ + dispatch_t *dispatcher = NULL; + tor_assert_nonfatal(builder->n_connectors == 0); + + if (pubsub_builder_check(builder) < 0) + goto err; + + if (builder->n_errors) { + log_warn(LD_GENERAL, "At least one error occurred previously when " + "configuring the dispatcher."); + goto err; + } + + dispatcher = dispatch_new(builder->cfg); + + if (!dispatcher) + goto err; + + pubsub_items_install_bindings(builder->items, dispatcher); + if (items_out) { + *items_out = builder->items; + builder->items = NULL; /* Prevent free */ + } + + err: + pubsub_builder_free(builder); + return dispatcher; +} diff --git a/src/lib/pubsub/pubsub_build.h b/src/lib/pubsub/pubsub_build.h new file mode 100644 index 0000000000..93aad50b28 --- /dev/null +++ b/src/lib/pubsub/pubsub_build.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_build.h + * @brief Header used for constructing the OO publish-subscribe facility. + * + * (See pubsub.h for more general information on this API.) + **/ + +#ifndef TOR_PUBSUB_BUILD_H +#define TOR_PUBSUB_BUILD_H + +#include "lib/dispatch/msgtypes.h" + +struct dispatch_t; +struct pubsub_connector_t; + +/** + * A "dispatch builder" is an incomplete dispatcher, used when + * registering messages. It does not have the same integrity guarantees + * as a dispatcher. It cannot actually handle messages itself: once all + * subsystems have registered, it is converted into a dispatch_t. + **/ +typedef struct pubsub_builder_t pubsub_builder_t; + +/** + * A "pubsub items" holds the configuration items used to configure a + * pubsub_builder. After the builder is finalized, this field is extracted, + * and used later to tear down pointers that enable publishing. + **/ +typedef struct pubsub_items_t pubsub_items_t; + +/** + * Create a new pubsub_builder. This should only happen in the + * main-init code. + */ +pubsub_builder_t *pubsub_builder_new(void); + +/** DOCDOC */ +int pubsub_builder_check(pubsub_builder_t *); + +/** + * Free a pubsub builder. This should only happen on error paths, where + * we have decided not to construct a dispatcher for some reason. + */ +#define pubsub_builder_free(db) \ + FREE_AND_NULL(pubsub_builder_t, pubsub_builder_free_, (db)) + +/** Internal implementation of pubsub_builder_free(). */ +void pubsub_builder_free_(pubsub_builder_t *); + +/** + * Create a pubsub connector that a single subsystem will use to + * register its messages. The main-init code does this during susbsystem + * initialization. + */ +struct pubsub_connector_t *pubsub_connector_for_subsystem(pubsub_builder_t *, + subsys_id_t); + +/** + * The main-init code does this after subsystem initialization. + */ +#define pubsub_connector_free(c) \ + FREE_AND_NULL(struct pubsub_connector_t, pubsub_connector_free_, (c)) + +void pubsub_connector_free_(struct pubsub_connector_t *); + +/** + * Constructs a dispatcher from a dispatch_builder, after checking that the + * invariances on the messages, channels, and connections have been + * respected. + * + * This should happen after every subsystem has initialized, and before + * entering the mainloop. + */ +struct dispatch_t *pubsub_builder_finalize(pubsub_builder_t *, + pubsub_items_t **items_out); + +/** + * Clear all pub_binding_t backpointers in <b>items</b>. + **/ +void pubsub_items_clear_bindings(pubsub_items_t *items); + +#define pubsub_items_free(cfg) \ + FREE_AND_NULL(pubsub_items_t, pubsub_items_free_, (cfg)) +void pubsub_items_free_(pubsub_items_t *cfg); + +#endif diff --git a/src/lib/pubsub/pubsub_builder_st.h b/src/lib/pubsub/pubsub_builder_st.h new file mode 100644 index 0000000000..cedeb02b16 --- /dev/null +++ b/src/lib/pubsub/pubsub_builder_st.h @@ -0,0 +1,161 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_builder_st.h + * + * @brief private structures used for configuring dispatchers and messages. + */ + +#ifndef TOR_PUBSUB_BUILDER_ST_H +#define TOR_PUBSUB_BUILDER_ST_H + +#ifdef PUBSUB_PRIVATE + +#include <stdbool.h> +#include <stddef.h> + +struct dispatch_cfg_t; +struct smartlist_t; +struct pub_binding_t; + +/** + * Configuration for a single publication or subscription request. + * + * These can be stored while the dispatcher is in use, but are only used for + * setup, teardown, and debugging. + * + * There are various fields in this request describing the message; all of + * them must match other descriptions of the message, or a bug has occurred. + **/ +typedef struct pubsub_cfg_t { + /** True if this is a publishing request; false for a subscribing request. */ + bool is_publish; + /** The system making this request. */ + subsys_id_t subsys; + /** The channel on which the message is to be sent. */ + channel_id_t channel; + /** The message ID to be sent or received. */ + message_id_t msg; + /** The C type associated with the message. */ + msg_type_id_t type; + /** One or more DISP_FLAGS_* items, combined with bitwise OR. */ + unsigned flags; + + /** + * Publishing only: a pub_binding object that will receive the binding for + * this request. We will finish filling this in when the dispatcher is + * constructed, so that the subsystem can publish then and not before. + */ + struct pub_binding_t *pub_binding; + + /** + * Subscribing only: a function to receive message objects for this request. + */ + recv_fn_t recv_fn; + + /** The file from which this message was configured */ + const char *added_by_file; + /** The line at which this message was configured */ + unsigned added_by_line; +} pubsub_cfg_t; + +/** + * Configuration request for a single C type. + * + * These are stored while the dispatcher is in use, but are only used for + * setup, teardown, and debugging. + **/ +typedef struct pubsub_type_cfg_t { + /** + * The identifier for this type. + */ + msg_type_id_t type; + /** + * Functions to use when manipulating the type. + */ + dispatch_typefns_t fns; + + /** The subsystem that configured this type. */ + subsys_id_t subsys; + /** The file from which this type was configured */ + const char *added_by_file; + /** The line at which this type was configured */ + unsigned added_by_line; +} pubsub_type_cfg_t; + +/** + * The set of configuration requests for a dispatcher, as made by various + * subsystems. + **/ +struct pubsub_items_t { + /** List of pubsub_cfg_t. */ + struct smartlist_t *items; + /** List of pubsub_type_cfg_t. */ + struct smartlist_t *type_items; +}; + +/** + * Type used to construct a dispatcher. We use this type to build up the + * configuration for a dispatcher, and then pass ownership of that + * configuration to the newly constructed dispatcher. + **/ +struct pubsub_builder_t { + /** Number of outstanding pubsub_connector_t objects pointing to this + * pubsub_builder_t. */ + int n_connectors; + /** Number of errors encountered while constructing this object so far. */ + int n_errors; + /** In-progress configuration that we're constructing, as a list of the + * requests that have been made. */ + struct pubsub_items_t *items; + /** In-progress configuration that we're constructing, in a form that can + * be converted to a dispatch_t. */ + struct dispatch_cfg_t *cfg; +}; + +/** + * Type given to a subsystem when adding connections to a pubsub_builder_t. + * We use this type to force each subsystem to get blamed for the + * publications, subscriptions, and types that it adds. + **/ +struct pubsub_connector_t { + /** The pubsub_builder that this connector refers to. */ + struct pubsub_builder_t *builder; + /** The subsystem that has been given this connector. */ + subsys_id_t subsys_id; +}; + +/** + * Helper structure used when constructing a dispatcher that sorts the + * pubsub_cfg_t objects in various ways. + **/ +typedef struct pubsub_adjmap_t { + /* XXXX The next three fields are currently constructed but not yet + * XXXX used. I believe we'll want them in the future, though. -nickm + */ + /** Number of subsystems; length of the *_by_subsys arrays. */ + size_t n_subsystems; + /** Array of lists of publisher pubsub_cfg_t objects, indexed by + * subsystem. */ + struct smartlist_t **pub_by_subsys; + /** Array of lists of subscriber pubsub_cfg_t objects, indexed by + * subsystem. */ + struct smartlist_t **sub_by_subsys; + + /** Number of message IDs; length of the *_by_msg arrays. */ + size_t n_msgs; + /** Array of lists of publisher pubsub_cfg_t objects, indexed by + * message ID. */ + struct smartlist_t **pub_by_msg; + /** Array of lists of subscriber pubsub_cfg_t objects, indexed by + * message ID. */ + struct smartlist_t **sub_by_msg; +} pubsub_adjmap_t; + +#endif + +#endif diff --git a/src/lib/pubsub/pubsub_check.c b/src/lib/pubsub/pubsub_check.c new file mode 100644 index 0000000000..a3c22d4f25 --- /dev/null +++ b/src/lib/pubsub/pubsub_check.c @@ -0,0 +1,428 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_check.c + * @brief Enforce various requirements on a pubsub_builder. + **/ + +#define PUBSUB_PRIVATE + +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/msgtypes.h" +#include "lib/pubsub/pubsub_flags.h" +#include "lib/pubsub/pubsub_builder_st.h" +#include "lib/pubsub/pubsub_build.h" + +#include "lib/container/bitarray.h" +#include "lib/container/smartlist.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/compat_string.h" + +#include <string.h> + +static void pubsub_adjmap_add(pubsub_adjmap_t *map, + const pubsub_cfg_t *item); + +/** + * Helper: contruct and return a new pubsub_adjacency_map from <b>cfg</b>. + * Return NULL on error. + **/ +static pubsub_adjmap_t * +pubsub_build_adjacency_map(const pubsub_items_t *cfg) +{ + pubsub_adjmap_t *map = tor_malloc_zero(sizeof(*map)); + const size_t n_subsystems = get_num_subsys_ids(); + const size_t n_msgs = get_num_message_ids(); + + map->n_subsystems = n_subsystems; + map->n_msgs = n_msgs; + + map->pub_by_subsys = tor_calloc(n_subsystems, sizeof(smartlist_t*)); + map->sub_by_subsys = tor_calloc(n_subsystems, sizeof(smartlist_t*)); + map->pub_by_msg = tor_calloc(n_msgs, sizeof(smartlist_t*)); + map->sub_by_msg = tor_calloc(n_msgs, sizeof(smartlist_t*)); + + SMARTLIST_FOREACH_BEGIN(cfg->items, const pubsub_cfg_t *, item) { + pubsub_adjmap_add(map, item); + } SMARTLIST_FOREACH_END(item); + + return map; +} + +/** + * Helper: add a single pubsub_cfg_t to an adjacency map. + **/ +static void +pubsub_adjmap_add(pubsub_adjmap_t *map, + const pubsub_cfg_t *item) +{ + smartlist_t **by_subsys; + smartlist_t **by_msg; + + tor_assert(item->subsys < map->n_subsystems); + tor_assert(item->msg < map->n_msgs); + + if (item->is_publish) { + by_subsys = &map->pub_by_subsys[item->subsys]; + by_msg = &map->pub_by_msg[item->msg]; + } else { + by_subsys = &map->sub_by_subsys[item->subsys]; + by_msg = &map->sub_by_msg[item->msg]; + } + + if (! *by_subsys) + *by_subsys = smartlist_new(); + if (! *by_msg) + *by_msg = smartlist_new(); + smartlist_add(*by_subsys, (void*) item); + smartlist_add(*by_msg, (void *) item); +} + +/** + * Release all storage held by m and set m to NULL. + **/ +#define pubsub_adjmap_free(m) \ + FREE_AND_NULL(pubsub_adjmap_t, pubsub_adjmap_free_, m) + +/** + * Free every element of an <b>n</b>-element array of smartlists, then + * free the array itself. + **/ +static void +pubsub_adjmap_free_helper(smartlist_t **lsts, size_t n) +{ + if (!lsts) + return; + + for (unsigned i = 0; i < n; ++i) { + smartlist_free(lsts[i]); + } + tor_free(lsts); +} + +/** + * Release all storage held by <b>map</b>. + **/ +static void +pubsub_adjmap_free_(pubsub_adjmap_t *map) +{ + if (!map) + return; + pubsub_adjmap_free_helper(map->pub_by_subsys, map->n_subsystems); + pubsub_adjmap_free_helper(map->sub_by_subsys, map->n_subsystems); + pubsub_adjmap_free_helper(map->pub_by_msg, map->n_msgs); + pubsub_adjmap_free_helper(map->sub_by_msg, map->n_msgs); + tor_free(map); +} + +/** + * Helper: return the length of <b>sl</b>, or 0 if sl is NULL. + **/ +static int +smartlist_len_opt(const smartlist_t *sl) +{ + if (sl) + return smartlist_len(sl); + else + return 0; +} + +/** Return a pointer to a statically allocated string encoding the + * dispatcher flags in <b>flags</b>. */ +static const char * +format_flags(unsigned flags) +{ + static char buf[32]; + buf[0] = 0; + if (flags & DISP_FLAG_EXCL) { + strlcat(buf, " EXCL", sizeof(buf)); + } + if (flags & DISP_FLAG_STUB) { + strlcat(buf, " STUB", sizeof(buf)); + } + return buf[0] ? buf+1 : buf; +} + +/** + * Log a message containing a description of <b>cfg</b> at severity, prefixed + * by the string <b>prefix</b>. + */ +static void +pubsub_cfg_dump(const pubsub_cfg_t *cfg, int severity, const char *prefix) +{ + tor_assert(prefix); + + tor_log(severity, LD_MESG, + "%s%s %s: %s{%s} on %s (%s) <%u %u %u %u %x> [%s:%d]", + prefix, + get_subsys_id_name(cfg->subsys), + cfg->is_publish ? "PUB" : "SUB", + get_message_id_name(cfg->msg), + get_msg_type_id_name(cfg->type), + get_channel_id_name(cfg->channel), + format_flags(cfg->flags), + cfg->subsys, cfg->msg, cfg->type, cfg->channel, cfg->flags, + cfg->added_by_file, cfg->added_by_line); +} + +/** + * Helper: fill a bitarray <b>out</b> with entries corresponding to the + * subsystems listed in <b>items</b>. If any subsystem is listed more than + * once, log a warning. Return 0 on success, -1 on failure. + **/ +static int +get_message_bitarray(const pubsub_adjmap_t *map, + message_id_t msg, + const smartlist_t *items, + const char *operation, + bitarray_t **out) +{ + bool ok = true; + *out = bitarray_init_zero((unsigned)map->n_subsystems); + if (! items) + return 0; + + SMARTLIST_FOREACH_BEGIN(items, const pubsub_cfg_t *, cfg) { + if (bitarray_is_set(*out, cfg->subsys)) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" is configured to be %s by subsystem " + "\"%s\" more than once.", + get_message_id_name(msg), operation, + get_subsys_id_name(cfg->subsys)); + ok = false; + } + bitarray_set(*out, cfg->subsys); + } SMARTLIST_FOREACH_END(cfg); + + return ok ? 0 : -1; +} + +/** + * Helper for lint_message: check that all the pubsub_cfg_t items in the two + * respective smartlists obey our local graph topology rules. + * + * (Right now this is just a matter of "each subsystem only + * publishes/subscribes once; no subsystem is a publisher and subscriber for + * the same message.") + * + * Return 0 on success, -1 on failure. + **/ +static int +lint_message_graph(const pubsub_adjmap_t *map, + message_id_t msg, + const smartlist_t *pub, + const smartlist_t *sub) +{ + bitarray_t *published_by = NULL; + bitarray_t *subscribed_by = NULL; + bool ok = true; + + if (get_message_bitarray(map, msg, pub, "published", &published_by) < 0) + ok = false; + if (get_message_bitarray(map, msg, sub, "subscribed", &subscribed_by) < 0) + ok = false; + + /* Check whether any subsystem is publishing and subscribing the same + * message. [??] + */ + for (unsigned i = 0; i < map->n_subsystems; ++i) { + if (bitarray_is_set(published_by, i) && + bitarray_is_set(subscribed_by, i)) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" is published and subscribed by the same " + "subsystem \"%s\".", + get_message_id_name(msg), + get_subsys_id_name(i)); + ok = false; + } + } + + bitarray_free(published_by); + bitarray_free(subscribed_by); + + return ok ? 0 : -1; +} + +/** + * Helper for lint_message: check that all the pubsub_cfg_t items in the two + * respective smartlists have compatible flags, channels, and types. + **/ +static int +lint_message_consistency(message_id_t msg, + const smartlist_t *pub, + const smartlist_t *sub) +{ + if (!smartlist_len_opt(pub) && !smartlist_len_opt(sub)) + return 0; // LCOV_EXCL_LINE -- this was already checked. + + /* The 'all' list has the publishers and the subscribers. */ + smartlist_t *all = smartlist_new(); + if (pub) + smartlist_add_all(all, pub); + if (sub) + smartlist_add_all(all, sub); + + const pubsub_cfg_t *item0 = smartlist_get(all, 0); + + /* Indicates which subsystems we've found publishing/subscribing here. */ + bool pub_excl = false, sub_excl = false, chan_same = true, type_same = true; + + /* Simple message consistency properties across messages. + */ + SMARTLIST_FOREACH_BEGIN(all, const pubsub_cfg_t *, cfg) { + chan_same &= (cfg->channel == item0->channel); + type_same &= (cfg->type == item0->type); + if (cfg->is_publish) + pub_excl |= (cfg->flags & DISP_FLAG_EXCL) != 0; + else + sub_excl |= (cfg->flags & DISP_FLAG_EXCL) != 0; + } SMARTLIST_FOREACH_END(cfg); + + bool ok = true; + + if (! chan_same) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" is associated with multiple inconsistent " + "channels.", + get_message_id_name(msg)); + ok = false; + } + if (! type_same) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" is associated with multiple inconsistent " + "message types.", + get_message_id_name(msg)); + ok = false; + } + + /* Enforce exclusive-ness for publishers and subscribers that have asked for + * it. + */ + if (pub_excl && smartlist_len_opt(pub) > 1) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" has multiple publishers, but at least one is " + "marked as exclusive.", + get_message_id_name(msg)); + ok = false; + } + if (sub_excl && smartlist_len_opt(sub) > 1) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" has multiple subscribers, but at least one is " + "marked as exclusive.", + get_message_id_name(msg)); + ok = false; + } + + smartlist_free(all); + + return ok ? 0 : -1; +} + +/** + * Check whether there are any errors or inconsistencies for the message + * described by <b>msg</b> in <b>map</b>. If there are problems, log about + * them, and return -1. Otherwise return 0. + **/ +static int +lint_message(const pubsub_adjmap_t *map, message_id_t msg) +{ + /* NOTE: Some of the checks in this function are maybe over-zealous, and we + * might not want to have them forever. I've marked them with [?] below. + */ + if (BUG(msg >= map->n_msgs)) + return 0; // LCOV_EXCL_LINE + + const smartlist_t *pub = map->pub_by_msg[msg]; + const smartlist_t *sub = map->sub_by_msg[msg]; + + const size_t n_pub = smartlist_len_opt(pub); + const size_t n_sub = smartlist_len_opt(sub); + + if (n_pub == 0 && n_sub == 0) { + log_info(LD_MESG, "Nobody is publishing or subscribing to message " + "\"%s\".", + get_message_id_name(msg)); + return 0; // No publishers or subscribers: nothing to do. + } + /* We'll set this to false if there are any problems. */ + bool ok = true; + + /* First make sure that if there are publishers, there are subscribers. */ + if (n_pub == 0) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" has subscribers, but no publishers.", + get_message_id_name(msg)); + ok = false; + } else if (n_sub == 0) { + log_warn(LD_MESG|LD_BUG, + "Message \"%s\" has publishers, but no subscribers.", + get_message_id_name(msg)); + ok = false; + } + + /* Check the message graph topology. */ + if (lint_message_graph(map, msg, pub, sub) < 0) + ok = false; + + /* Check whether the messages have the same fields set on them. */ + if (lint_message_consistency(msg, pub, sub) < 0) + ok = false; + + if (!ok) { + /* There was a problem -- let's log all the publishers and subscribers on + * this message */ + if (pub) { + SMARTLIST_FOREACH(pub, pubsub_cfg_t *, cfg, + pubsub_cfg_dump(cfg, LOG_WARN, " ")); + } + if (sub) { + SMARTLIST_FOREACH(sub, pubsub_cfg_t *, cfg, + pubsub_cfg_dump(cfg, LOG_WARN, " ")); + } + } + + return ok ? 0 : -1; +} + +/** + * Check all the messages in <b>map</b> for consistency. Return 0 on success, + * -1 on problems. + **/ +static int +pubsub_adjmap_check(const pubsub_adjmap_t *map) +{ + bool all_ok = true; + for (unsigned i = 0; i < map->n_msgs; ++i) { + if (lint_message(map, i) < 0) { + all_ok = false; + } + } + return all_ok ? 0 : -1; +} + +/** + * Check builder for consistency and various constraints. Return 0 on success, + * -1 on failure. + **/ +int +pubsub_builder_check(pubsub_builder_t *builder) +{ + pubsub_adjmap_t *map = pubsub_build_adjacency_map(builder->items); + int rv = -1; + + if (!map) + goto err; // should be impossible + + if (pubsub_adjmap_check(map) < 0) + goto err; + + rv = 0; + err: + pubsub_adjmap_free(map); + return rv; +} diff --git a/src/lib/pubsub/pubsub_connect.h b/src/lib/pubsub/pubsub_connect.h new file mode 100644 index 0000000000..bdcb33d2f5 --- /dev/null +++ b/src/lib/pubsub/pubsub_connect.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_connect.h + * @brief Header for functions that add relationships to a pubsub builder. + * + * These functions are used by modules that need to add publication and + * subscription requests. Most users will want to call these functions + * indirectly, via the macros in pubsub_macros.h. + **/ + +#ifndef TOR_PUBSUB_CONNECT_H +#define TOR_PUBSUB_CONNECT_H + +#include "lib/dispatch/msgtypes.h" + +struct pub_binding_t; +/** + * A "dispatch connector" is a view of the dispatcher that a subsystem + * uses while initializing itself. It is specific to the subsystem, and + * ensures that each subsystem doesn't need to identify itself + * repeatedly while registering its messages. + **/ +typedef struct pubsub_connector_t pubsub_connector_t; + +int pubsub_add_pub_(struct pubsub_connector_t *con, + struct pub_binding_t *out, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + unsigned flags, + const char *file, + unsigned line); + +int pubsub_add_sub_(struct pubsub_connector_t *con, + recv_fn_t recv_fn, + channel_id_t channel, + message_id_t msg, + msg_type_id_t type, + unsigned flags, + const char *file, + unsigned line); + +int pubsub_connector_register_type_(struct pubsub_connector_t *, + msg_type_id_t, + dispatch_typefns_t *, + const char *file, + unsigned line); + +#endif diff --git a/src/lib/pubsub/pubsub_flags.h b/src/lib/pubsub/pubsub_flags.h new file mode 100644 index 0000000000..d07a06be7b --- /dev/null +++ b/src/lib/pubsub/pubsub_flags.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-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_flags.h + * @brief Flags that can be set on publish/subscribe messages. + **/ + +#ifndef TOR_PUBSUB_FLAGS_H +#define TOR_PUBSUB_FLAGS_H + +/** + * Flag for registering a message: declare that no other module is allowed to + * publish this message if we are publishing it, or subscribe to it if we are + * subscribing to it. + */ +#define DISP_FLAG_EXCL (1u<<0) + +/** + * Flag for registering a message: declare that this message is a stub, and we + * will not actually publish/subscribe it, but that the dispatcher should + * treat us as if we did when typechecking. + * + * We use this so that messages aren't treated as "dangling" if they are + * potentially used by some other build of Tor. + */ +#define DISP_FLAG_STUB (1u<<1) + +#endif diff --git a/src/lib/pubsub/pubsub_macros.h b/src/lib/pubsub/pubsub_macros.h new file mode 100644 index 0000000000..d091e40dfa --- /dev/null +++ b/src/lib/pubsub/pubsub_macros.h @@ -0,0 +1,373 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file pubsub_macros.h + * \brief Macros to help with the publish/subscribe dispatch API. + * + * The dispatch API allows different subsystems of Tor to communicate with + * another asynchronously via a shared "message" system. Some subsystems + * declare that they publish a given message, and others declare that they + * subscribe to it. Both subsystems depend on the message, but not upon one + * another. + * + * To declare a message, use DECLARE_MESSAGE() (for messages that take their + * data as a pointer) or DECLARE_MESSAGE_INT() (for messages that take their + * data as an integer. For example, you might say + * + * DECLARE_MESSAGE(new_circuit, circ, circuit_handle_t *); + * or + * DECLARE_MESSAGE_INT(shutdown_requested, boolean, bool); + * + * Every message has a unique name, a "type name" that the dispatch system + * uses to manage associated data, and a C type name. You'll want to put + * these declarations in a header, to be included by all publishers and all + * subscribers. + * + * When a subsystem wants to publish a message, it uses DECLARE_PUBLISH() at + * file scope to create necessary static functions. Then, in its subsystem + * initialization (in the "bind to dispatcher" callback) (TODO: name this + * properly!), it calls DISPATCH_ADD_PUB() to tell the dispatcher about its + * intent to publish. When it actually wants to publish, it uses the + * PUBLISH() macro. For example: + * + * // At file scope + * DECLARE_PUBLISH(shutdown_requested); + * + * static void bind_to_dispatcher(pubsub_connector_t *con) + * { + * DISPATCH_ADD_PUB(con, mainchannel, shutdown_requested); + * } + * + * // somewhere in a function + * { + * PUBLISH(shutdown_requested, true); + * } + * + * When a subsystem wants to subscribe to a message, it uses + * DECLARE_SUBSCRIBE() at file scope to declare static functions. It must + * declare a hook function that receives the message type. Then, in its "bind + * to dispatcher" function, it calls DISPATCHER_ADD_SUB() to tell the + * dispatcher about its intent to subscribe. When another module publishes + * the message, the dispatcher will call the provided hook function. + * + * // At file scope. The first argument is the message that you're + * // subscribing to; the second argument is the hook function to declare. + * DECLARE_SUBSCRIBE(shutdown_requested, on_shutdown_req_cb); + * + * // You need to declare this function. + * static void on_shutdown_req_cb(const msg_t *msg, + * bool value) + * { + * // (do something here.) + * } + * + * static void bind_to_dispatcher(pubsub_connector_t *con) + * { + * DISPATCH_ADD_SUB(con, mainchannel, shutdown_requested); + * } + * + * Where did these types come from? Somewhere in the code, you need to call + * DISPATCH_REGISTER_TYPE() to make sure that the dispatcher can manage the + * message auxiliary data. It associates a vtbl-like structure with the + * type name, so that the dispatcher knows how to manipulate the type you're + * giving it. + * + * For example, the "boolean" type we're using above could be defined as: + * + * static char *boolean_fmt(msg_aux_data_t d) + * { + * // This is used for debugging and dumping messages. + * if (d.u64) + * return tor_strdup("true"); + * else + * return tor_strdup("false"); + * } + * + * static void boolean_free(msg_aux_data_t d) + * { + * // We don't actually need to do anything to free a boolean. + * // We could use "NULL" instead of this function, but I'm including + * // it as an example. + * } + * + * static void bind_to_dispatcher(pubsub_connector_t *con) + * { + * dispatch_typefns_t boolean_fns = { + * .fmt_fn = boolean_fmt, + * .free_fn = boolean_free, + * }; + * DISPATCH_REGISTER_TYPE(con, boolean, &boolean_fns); + * } + * + * + * + * So, how does this all work? (You can stop reading here, unless you're + * debugging something.) + * + * When you declare a message in a header with DECLARE_MESSAGE() or + * DECLARE_MESSAGE_INT(), it creates five things: + * + * * two typedefs for the message argument (constant and non-constant + * variants). + * * a constant string to hold the declared message type name + * * two inline functions, to coerce the message argument type to and from + * a "msg_aux_data_t" union. + * + * All of these declarations have names based on the message name. + * + * Later, when you say DECLARE_PUBLISH() or DECLARE_SUBSCRIBE(), we use the + * elements defined by DECLARE_MESSAGE() to make sure that the publish + * function takes the correct argument type, and that the subscription hook is + * declared with the right argument type. + **/ + +#ifndef TOR_DISPATCH_MSG_H +#define TOR_DISPATCH_MSG_H + +#include "lib/cc/compat_compiler.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/pubsub/pub_binding_st.h" +#include "lib/pubsub/pubsub_connect.h" +#include "lib/pubsub/pubsub_flags.h" +#include "lib/pubsub/pubsub_publish.h" + +/* Implemenation notes: + * + * For a messagename "foo", the DECLARE_MESSAGE*() macros must declare: + * + * msg_arg_type__foo -- a typedef for the argument type of the foo message. + * msg_arg_consttype__foo -- a typedef for the const argument type of the + * foo message. + * msg_arg_name__foo[] -- a static string constant holding the unique + * identifier for the type of the foo message. + * msg_arg_get__foo() -- an inline function taking a msg_aux_data_t and + * returning the C data type. + * msg_arg_set__foo() -- an inline function taking a msg_aux_data_t and + * the C type, setting the msg_aux_data_t to hold the C type. + * + * For a messagename "foo", the DECLARE_PUBLISH() macro must declare: + * + * pub_binding__foo -- A static pub_binding_t object used to send messages + * from this module. + * publish_fn__foo -- A function taking an argument of the appropriate + * C type, to be invoked by PUBLISH(). + * + * For a messagename "foo", the DECLARE_SUBSCRIBE() macro must declare: + * + * hookfn -- A user-provided function name, with the correct signature. + * recv_fn__foo -- A wrapper callback that takes a msg_t *, and calls + * hookfn with the appropriate arguments. + */ + +/* Macro to declare common elements shared by DECLARE_MESSAGE and + * DECLARE_MESSAGE_INT. Don't call this directly. + * + * Note that the "msg_arg_name" string constant is defined in each + * translation unit. This might be undesirable; we can tweak it in the + * future if need be. + */ +#define DECLARE_MESSAGE_COMMON__(messagename, typename, c_type) \ + typedef c_type msg_arg_type__ ##messagename; \ + typedef const c_type msg_arg_consttype__ ##messagename; \ + ATTR_UNUSED static const char msg_arg_name__ ##messagename[] = # typename; + +/** + * Use this macro in a header to declare the existence of a given message, + * taking a pointer as auxiliary data. + * + * "messagename" is a unique identifier for the message; it must be a valid + * C identifier. + * + * "typename" is a unique identifier for the type of the auxiliary data. + * It needs to be defined somewhere in Tor, using + * "DISPATCH_REGISTER_TYPE." + * + * "c_ptr_type" is a C pointer type (like "char *" or "struct foo *"). + * The "*" needs to be included. + */ +#define DECLARE_MESSAGE(messagename, typename, c_ptr_type) \ + DECLARE_MESSAGE_COMMON__(messagename, typename, c_ptr_type) \ + ATTR_UNUSED static inline c_ptr_type \ + msg_arg_get__ ##messagename(msg_aux_data_t m) \ + { \ + return m.ptr; \ + } \ + ATTR_UNUSED static inline void \ + msg_arg_set__ ##messagename(msg_aux_data_t *m, c_ptr_type v) \ + { \ + m->ptr = v; \ + } \ + EAT_SEMICOLON + +/** + * Use this macro in a header to declare the existence of a given message, + * taking an integer as auxiliary data. + * + * "messagename" is a unique identifier for the message; it must be a valid + * C identifier. + * + * "typename" is a unique identifier for the type of the auxiliary data. It + * needs to be defined somewhere in Tor, using "DISPATCH_REGISTER_TYPE." + * + * "c_type" is a C integer type, like "int" or "bool". It needs to fit inside + * a uint64_t. + */ +#define DECLARE_MESSAGE_INT(messagename, typename, c_type) \ + DECLARE_MESSAGE_COMMON__(messagename, typename, c_type) \ + ATTR_UNUSED static inline c_type \ + msg_arg_get__ ##messagename(msg_aux_data_t m) \ + { \ + return (c_type)m.u64; \ + } \ + ATTR_UNUSED static inline void \ + msg_arg_set__ ##messagename(msg_aux_data_t *m, c_type v) \ + { \ + m->u64 = (uint64_t)v; \ + } \ + EAT_SEMICOLON + +/** + * Use this macro inside a C module declare that we'll be publishing a given + * message type from within this module. + * + * It creates necessary functions and wrappers to publish a message whose + * unique identifier is "messagename". + * + * Before you use this, you need to include the header where DECLARE_MESSAGE*() + * was used for this message. + * + * You can only use this once per message in each subsystem. + */ +#define DECLARE_PUBLISH(messagename) \ + static pub_binding_t pub_binding__ ##messagename; \ + static void \ + publish_fn__ ##messagename(msg_arg_type__ ##messagename arg) \ + { \ + msg_aux_data_t data; \ + msg_arg_set__ ##messagename(&data, arg); \ + pubsub_pub_(&pub_binding__ ##messagename, data); \ + } \ + EAT_SEMICOLON + +/** + * Use this macro inside a C file to declare that we're subscribing to a + * given message and associating it with a given "hook function". It + * declares the hook function static, and helps with strong typing. + * + * Before you use this, you need to include the header where + * DECLARE_MESSAGE*() was used for the message whose unique identifier is + * "messagename". + * + * You will need to define a function with the name that you provide for + * "hookfn". The type of this function will be: + * static void hookfn(const msg_t *, const c_type) + * where c_type is the c type that you declared in the header. + * + * You can only use this once per message in each subsystem. + */ +#define DECLARE_SUBSCRIBE(messagename, hookfn) \ + static void hookfn(const msg_t *, \ + const msg_arg_consttype__ ##messagename); \ + static void recv_fn__ ## messagename(const msg_t *m) \ + { \ + msg_arg_type__ ## messagename arg; \ + arg = msg_arg_get__ ##messagename(m->aux_data__); \ + hookfn(m, arg); \ + } \ + EAT_SEMICOLON + +/** + * Add a fake use of the publish function for 'messagename', so that + * the compiler does not call it unused. + */ +#define DISPATCH__FAKE_USE_OF_PUBFN_(messagename) \ + ( 0 ? (publish_fn__ ##messagename((msg_arg_type__##messagename)0), 1) \ + : 1) + +/* + * This macro is for internal use. It backs DISPATCH_ADD_PUB*() + */ +#define DISPATCH_ADD_PUB_(connector, channel, messagename, flags) \ + ( \ + DISPATCH__FAKE_USE_OF_PUBFN_(messagename), \ + pubsub_add_pub_((connector), \ + &pub_binding__ ##messagename, \ + get_channel_id(# channel), \ + get_message_id(# messagename), \ + get_msg_type_id(msg_arg_name__ ## messagename), \ + (flags), \ + __FILE__, \ + __LINE__) \ + ) + +/** + * Use a given connector and channel name to declare that this subsystem will + * publish a given message type. + * + * Call this macro from within the add_subscriptions() function of a module. + */ +#define DISPATCH_ADD_PUB(connector, channel, messagename) \ + DISPATCH_ADD_PUB_(connector, channel, messagename, 0) + +/** + * Use a given connector and channel name to declare that this subsystem will + * publish a given message type, and that no other subsystem is allowed to. + * + * Call this macro from within the add_subscriptions() function of a module. + */ +#define DISPATCH_ADD_PUB_EXCL(connector, channel, messagename) \ + DISPATCH_ADD_PUB_(connector, channel, messagename, DISP_FLAG_EXCL) + +/* + * This macro is for internal use. It backs DISPATCH_ADD_SUB*() + */ +#define DISPATCH_ADD_SUB_(connector, channel, messagename, flags) \ + pubsub_add_sub_((connector), \ + recv_fn__ ##messagename, \ + get_channel_id(#channel), \ + get_message_id(# messagename), \ + get_msg_type_id(msg_arg_name__ ##messagename), \ + (flags), \ + __FILE__, \ + __LINE__) +/* + * Use a given connector and channel name to declare that this subsystem will + * receive a given message type. + * + * Call this macro from within the add_subscriptions() function of a module. + */ +#define DISPATCH_ADD_SUB(connector, channel, messagename) \ + DISPATCH_ADD_SUB_(connector, channel, messagename, 0) +/** + * Use a given connector and channel name to declare that this subsystem will + * receive a given message type, and that no other subsystem is allowed to do + * so. + * + * Call this macro from within the add_subscriptions() function of a module. + */ +#define DISPATCH_ADD_SUB_EXCL(connector, channel, messagename) \ + DISPATCH_ADD_SUB_(connector, channel, messagename, DISP_FLAG_EXCL) + +/** + * Publish a given message with a given argument. (Takes ownership of the + * argument if it is a pointer.) + */ +#define PUBLISH(messagename, arg) \ + publish_fn__ ##messagename(arg) + +/** + * Use a given connector to declare that the functions to be used to manipuate + * a certain C type. + **/ +#define DISPATCH_REGISTER_TYPE(con, type, fns) \ + pubsub_connector_register_type_((con), \ + get_msg_type_id(#type), \ + (fns), \ + __FILE__, \ + __LINE__) + +#endif diff --git a/src/lib/pubsub/pubsub_publish.c b/src/lib/pubsub/pubsub_publish.c new file mode 100644 index 0000000000..454a335a78 --- /dev/null +++ b/src/lib/pubsub/pubsub_publish.c @@ -0,0 +1,72 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file pubsub_publish.c + * @brief Header for functions to publish using a pub_binding_t. + **/ + +#define PUBSUB_PRIVATE +#define DISPATCH_PRIVATE +#include "orconfig.h" + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_st.h" + +#include "lib/pubsub/pub_binding_st.h" +#include "lib/pubsub/pubsub_publish.h" + +#include "lib/malloc/malloc.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +/** + * Publish a message from the publication binding <b>pub</b> using the + * auxiliary data <b>auxdata</b>. + * + * Return 0 on success, -1 on failure. + **/ +int +pubsub_pub_(const pub_binding_t *pub, msg_aux_data_t auxdata) +{ + dispatch_t *d = pub->dispatch_ptr; + if (BUG(! d)) { + /* Tried to publish a message before the dispatcher was configured. */ + /* (Without a dispatcher, we don't know how to free auxdata.) */ + return -1; + } + + if (BUG(pub->msg_template.type >= d->n_types)) { + /* The type associated with this message is not known to the dispatcher. */ + /* (Without a correct type, we don't know how to free auxdata.) */ + return -1; + } + + if (BUG(pub->msg_template.msg >= d->n_msgs) || + BUG(pub->msg_template.channel >= d->n_queues)) { + /* The message ID or channel ID was out of bounds. */ + // LCOV_EXCL_START + d->typefns[pub->msg_template.type].free_fn(auxdata); + return -1; + // LCOV_EXCL_STOP + } + + if (! d->table[pub->msg_template.msg]) { + /* Fast path: nobody wants this data. */ + + // XXXX Faster path: we could store this in the pub_binding_t. + d->typefns[pub->msg_template.type].free_fn(auxdata); + return 0; + } + + /* Construct the message object */ + msg_t *m = tor_malloc(sizeof(msg_t)); + memcpy(m, &pub->msg_template, sizeof(msg_t)); + m->aux_data__ = auxdata; + + return dispatch_send_msg_unchecked(d, m); +} diff --git a/src/lib/pubsub/pubsub_publish.h b/src/lib/pubsub/pubsub_publish.h new file mode 100644 index 0000000000..0250fd0760 --- /dev/null +++ b/src/lib/pubsub/pubsub_publish.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PUBSUB_PUBLISH_H +#define TOR_PUBSUB_PUBLISH_H + +#include "lib/dispatch/msgtypes.h" +struct pub_binding_t; + +int pubsub_pub_(const struct pub_binding_t *pub, msg_aux_data_t auxdata); + +#endif diff --git a/src/lib/smartlist_core/smartlist_core.c b/src/lib/smartlist_core/smartlist_core.c index ac85a6cc84..5947e76271 100644 --- a/src/lib/smartlist_core/smartlist_core.c +++ b/src/lib/smartlist_core/smartlist_core.c @@ -88,6 +88,30 @@ smartlist_ensure_capacity(smartlist_t *sl, size_t size) #undef MAX_CAPACITY } +/** Expand <b>sl</b> so that its length is at least <b>new_size</b>, + * filling in previously unused entries with NULL> + * + * Do nothing if <b>sl</b> already had at least <b>new_size</b> elements. + */ +void +smartlist_grow(smartlist_t *sl, size_t new_size) +{ + smartlist_ensure_capacity(sl, new_size); + + if (new_size > (size_t)sl->num_used) { + /* This memset() should be a no-op: everything else in the smartlist code + * tries to make sure that unused entries are always NULL. Still, that is + * meant as a safety mechanism, so let's clear the memory here. + */ + memset(sl->list + sl->num_used, 0, + sizeof(void *) * (new_size - sl->num_used)); + + /* This cast is safe, since we already asserted that we were below + * MAX_CAPACITY in smartlist_ensure_capacity(). */ + sl->num_used = (int)new_size; + } +} + /** Append element to the end of the list. */ void smartlist_add(smartlist_t *sl, void *element) diff --git a/src/lib/smartlist_core/smartlist_core.h b/src/lib/smartlist_core/smartlist_core.h index a7fbaa099b..a1a195f312 100644 --- a/src/lib/smartlist_core/smartlist_core.h +++ b/src/lib/smartlist_core/smartlist_core.h @@ -43,6 +43,7 @@ void smartlist_clear(smartlist_t *sl); void smartlist_add(smartlist_t *sl, void *element); void smartlist_add_all(smartlist_t *sl, const smartlist_t *s2); void smartlist_add_strdup(struct smartlist_t *sl, const char *string); +void smartlist_grow(smartlist_t *sl, size_t new_size); void smartlist_remove(smartlist_t *sl, const void *element); void smartlist_remove_keeporder(smartlist_t *sl, const void *element); diff --git a/src/lib/subsys/subsys.h b/src/lib/subsys/subsys.h index 241ad7829c..d78bb4a602 100644 --- a/src/lib/subsys/subsys.h +++ b/src/lib/subsys/subsys.h @@ -8,7 +8,7 @@ #include <stdbool.h> -struct dispatch_connector_t; +struct pubsub_connector_t; /** * A subsystem is a part of Tor that is initialized, shut down, configured, @@ -58,7 +58,7 @@ typedef struct subsys_fns_t { /** * Connect a subsystem to the message dispatch system. **/ - int (*add_pubsub)(struct dispatch_connector_t *); + int (*add_pubsub)(struct pubsub_connector_t *); /** * Perform any necessary pre-fork cleanup. This function may not fail. diff --git a/src/test/fuzz/fuzz_strops.c b/src/test/fuzz/fuzz_strops.c index 64a6453050..a37cbb5be8 100644 --- a/src/test/fuzz/fuzz_strops.c +++ b/src/test/fuzz/fuzz_strops.c @@ -86,15 +86,13 @@ b16_enc(const chunk_t *inp) return ch; } -#if 0 static chunk_t * b32_dec(const chunk_t *inp) { chunk_t *ch = chunk_new(inp->len);//XXXX int r = base32_decode((char *)ch->buf, ch->len, (char *)inp->buf, inp->len); if (r >= 0) { - ch->len = r; // XXXX we need some way to get the actual length of - // XXXX the output here. + ch->len = r; } else { chunk_free(ch); } @@ -108,7 +106,6 @@ b32_enc(const chunk_t *inp) ch->len = strlen((char *) ch->buf); return ch; } -#endif static chunk_t * b64_dec(const chunk_t *inp) @@ -222,10 +219,7 @@ fuzz_main(const uint8_t *stdin_buf, size_t data_size) ENCODE_ROUNDTRIP(b16_enc, b16_dec, chunk_free_); break; case 1: - /* - XXXX see notes above about our base-32 functions. ENCODE_ROUNDTRIP(b32_enc, b32_dec, chunk_free_); - */ break; case 2: ENCODE_ROUNDTRIP(b64_enc, b64_dec, chunk_free_); diff --git a/src/test/fuzz_static_testcases.sh b/src/test/fuzz_static_testcases.sh index f7b3adffb1..b883352402 100755 --- a/src/test/fuzz_static_testcases.sh +++ b/src/test/fuzz_static_testcases.sh @@ -14,7 +14,7 @@ fi for fuzzer in "${builddir:-.}"/src/test/fuzz/fuzz-* ; do - f=`basename $fuzzer` + f=$(basename "$fuzzer") case="${f#fuzz-}" if [ -d "${TOR_FUZZ_CORPORA}/${case}" ]; then echo "Running tests for ${case}" diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index f2ae8398df..c57bdc730b 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -21,26 +21,35 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, /* For a usable intro point we need at least two link specifiers: One legacy * keyid and one ipv4 */ { - hs_desc_link_specifier_t *ls_legacy = tor_malloc_zero(sizeof(*ls_legacy)); - hs_desc_link_specifier_t *ls_v4 = tor_malloc_zero(sizeof(*ls_v4)); - ls_legacy->type = LS_LEGACY_ID; - memcpy(ls_legacy->u.legacy_id, "0299F268FCA9D55CD157976D39AE92B4B455B3A8", - DIGEST_LEN); - ls_v4->u.ap.port = 9001; - int family = tor_addr_parse(&ls_v4->u.ap.addr, addr); + tor_addr_t a; + tor_addr_make_unspec(&a); + link_specifier_t *ls_legacy = link_specifier_new(); + link_specifier_t *ls_ip = link_specifier_new(); + link_specifier_set_ls_type(ls_legacy, LS_LEGACY_ID); + memset(link_specifier_getarray_un_legacy_id(ls_legacy), 'C', + link_specifier_getlen_un_legacy_id(ls_legacy)); + int family = tor_addr_parse(&a, addr); switch (family) { case AF_INET: - ls_v4->type = LS_IPV4; + link_specifier_set_ls_type(ls_ip, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls_ip, tor_addr_to_ipv4h(&a)); + link_specifier_set_un_ipv4_port(ls_ip, 9001); break; case AF_INET6: - ls_v4->type = LS_IPV6; + link_specifier_set_ls_type(ls_ip, LS_IPV6); + memcpy(link_specifier_getarray_un_ipv6_addr(ls_ip), + tor_addr_to_in6_addr8(&a), + link_specifier_getlen_un_ipv6_addr(ls_ip)); + link_specifier_set_un_ipv6_port(ls_ip, 9001); break; default: - /* Stop the test, not suppose to have an error. */ - tt_int_op(family, OP_EQ, AF_INET); + /* Stop the test, not supposed to have an error. + * Compare with -1 to show the actual family. + */ + tt_int_op(family, OP_EQ, -1); } smartlist_add(ip->link_specifiers, ls_legacy); - smartlist_add(ip->link_specifiers, ls_v4); + smartlist_add(ip->link_specifiers, ls_ip); } ret = ed25519_keypair_generate(&auth_kp, 0); @@ -153,8 +162,11 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, /* Add four intro points. */ smartlist_add(desc->encrypted_data.intro_points, hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0)); +/* IPv6-only introduction points are not supported yet, see #23588 */ +#if 0 smartlist_add(desc->encrypted_data.intro_points, hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0)); +#endif smartlist_add(desc->encrypted_data.intro_points, hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1)); smartlist_add(desc->encrypted_data.intro_points, @@ -202,7 +214,6 @@ void hs_helper_desc_equal(const hs_descriptor_t *desc1, const hs_descriptor_t *desc2) { - char *addr1 = NULL, *addr2 = NULL; /* Plaintext data section. */ tt_int_op(desc1->plaintext_data.version, OP_EQ, desc2->plaintext_data.version); @@ -291,35 +302,57 @@ hs_helper_desc_equal(const hs_descriptor_t *desc1, tt_int_op(smartlist_len(ip1->link_specifiers), ==, smartlist_len(ip2->link_specifiers)); for (int j = 0; j < smartlist_len(ip1->link_specifiers); j++) { - hs_desc_link_specifier_t *ls1 = smartlist_get(ip1->link_specifiers, j), - *ls2 = smartlist_get(ip2->link_specifiers, j); - tt_int_op(ls1->type, ==, ls2->type); - switch (ls1->type) { + link_specifier_t *ls1 = smartlist_get(ip1->link_specifiers, j), + *ls2 = smartlist_get(ip2->link_specifiers, j); + tt_int_op(link_specifier_get_ls_type(ls1), ==, + link_specifier_get_ls_type(ls2)); + switch (link_specifier_get_ls_type(ls1)) { case LS_IPV4: + { + uint32_t addr1 = link_specifier_get_un_ipv4_addr(ls1); + uint32_t addr2 = link_specifier_get_un_ipv4_addr(ls2); + tt_int_op(addr1, OP_EQ, addr2); + uint16_t port1 = link_specifier_get_un_ipv4_port(ls1); + uint16_t port2 = link_specifier_get_un_ipv4_port(ls2); + tt_int_op(port1, ==, port2); + } + break; case LS_IPV6: { - addr1 = tor_addr_to_str_dup(&ls1->u.ap.addr); - addr2 = tor_addr_to_str_dup(&ls2->u.ap.addr); - tt_str_op(addr1, OP_EQ, addr2); - tor_free(addr1); - tor_free(addr2); - tt_int_op(ls1->u.ap.port, ==, ls2->u.ap.port); + const uint8_t *addr1 = + link_specifier_getconstarray_un_ipv6_addr(ls1); + const uint8_t *addr2 = + link_specifier_getconstarray_un_ipv6_addr(ls2); + tt_int_op(link_specifier_getlen_un_ipv6_addr(ls1), OP_EQ, + link_specifier_getlen_un_ipv6_addr(ls2)); + tt_mem_op(addr1, OP_EQ, addr2, + link_specifier_getlen_un_ipv6_addr(ls1)); + uint16_t port1 = link_specifier_get_un_ipv6_port(ls1); + uint16_t port2 = link_specifier_get_un_ipv6_port(ls2); + tt_int_op(port1, ==, port2); } break; case LS_LEGACY_ID: - tt_mem_op(ls1->u.legacy_id, OP_EQ, ls2->u.legacy_id, - sizeof(ls1->u.legacy_id)); + { + const uint8_t *id1 = + link_specifier_getconstarray_un_legacy_id(ls1); + const uint8_t *id2 = + link_specifier_getconstarray_un_legacy_id(ls2); + tt_int_op(link_specifier_getlen_un_legacy_id(ls1), OP_EQ, + link_specifier_getlen_un_legacy_id(ls2)); + tt_mem_op(id1, OP_EQ, id2, + link_specifier_getlen_un_legacy_id(ls1)); + } break; default: /* Unknown type, caught it and print its value. */ - tt_int_op(ls1->type, OP_EQ, -1); + tt_int_op(link_specifier_get_ls_type(ls1), OP_EQ, -1); } } } } done: - tor_free(addr1); - tor_free(addr2); + ; } diff --git a/src/test/include.am b/src/test/include.am index d585c2a38a..497aa320a4 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -126,6 +126,7 @@ src_test_test_SOURCES += \ src/test/test_dir.c \ src/test/test_dir_common.c \ src/test/test_dir_handle_get.c \ + src/test/test_dispatch.c \ src/test/test_dos.c \ src/test/test_entryconn.c \ src/test/test_entrynodes.c \ @@ -150,6 +151,7 @@ src_test_test_SOURCES += \ src/test/test_logging.c \ src/test/test_mainloop.c \ src/test/test_microdesc.c \ + src/test/test_namemap.c \ src/test/test_netinfo.c \ src/test/test_nodelist.c \ src/test/test_oom.c \ @@ -165,6 +167,8 @@ src_test_test_SOURCES += \ src/test/test_proto_misc.c \ src/test/test_protover.c \ src/test/test_pt.c \ + src/test/test_pubsub_build.c \ + src/test/test_pubsub_msg.c \ src/test/test_relay.c \ src/test/test_relaycell.c \ src/test/test_relaycrypt.c \ @@ -211,6 +215,8 @@ src_test_test_slow_SOURCES += \ src/test/test_crypto_slow.c \ src/test/test_process_slow.c \ src/test/test_prob_distr.c \ + src/test/ptr_helpers.c \ + src/test/test_ptr_slow.c \ src/test/testing_common.c \ src/test/testing_rsakeys.c \ src/ext/tinytest.c @@ -314,6 +320,7 @@ noinst_HEADERS+= \ src/test/log_test_helpers.h \ src/test/rend_test_helpers.h \ src/test/test.h \ + src/test/ptr_helpers.h \ src/test/test_helpers.h \ src/test/test_dir_common.h \ src/test/test_connection.h \ diff --git a/src/test/ope_ref.py b/src/test/ope_ref.py index f9bd97c546..b2f7012563 100644 --- a/src/test/ope_ref.py +++ b/src/test/ope_ref.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # Copyright 2018-2019, The Tor Project, Inc. See LICENSE for licensing info. # Reference implementation for our rudimentary OPE code, used to diff --git a/src/test/ptr_helpers.c b/src/test/ptr_helpers.c new file mode 100644 index 0000000000..296238feeb --- /dev/null +++ b/src/test/ptr_helpers.c @@ -0,0 +1,50 @@ +/* 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 */ + +#include "ptr_helpers.h" + +/** + * Cast <b> (inptr_t value) to a void pointer. + */ +void * +cast_intptr_to_voidstar(intptr_t x) +{ + void *r = (void *)x; + + return r; +} + +/** + * Cast x (void pointer) to inptr_t value. + */ +intptr_t +cast_voidstar_to_intptr(void *x) +{ + intptr_t r = (intptr_t)x; + + return r; +} + +/** + * Cast x (uinptr_t value) to void pointer. + */ +void * +cast_uintptr_to_voidstar(uintptr_t x) +{ + void *r = (void *)x; + + return r; +} + +/** + * Cast x (void pointer) to uinptr_t value. + */ +uintptr_t +cast_voidstar_to_uintptr(void *x) +{ + uintptr_t r = (uintptr_t)x; + + return r; +} diff --git a/src/test/ptr_helpers.h b/src/test/ptr_helpers.h new file mode 100644 index 0000000000..67776b1006 --- /dev/null +++ b/src/test/ptr_helpers.h @@ -0,0 +1,23 @@ +/* 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 */ + +#ifndef TOR_PTR_HELPERS_H +#define TOR_PTR_HELPERS_H + +#include <stdint.h> + +void * +cast_intptr_to_voidstar(intptr_t x); + +intptr_t +cast_voidstar_to_intptr(void *x); + +void * +cast_uintptr_to_voidstar(uintptr_t x); + +uintptr_t +cast_voidstar_to_uintptr(void *x); + +#endif diff --git a/src/test/test-network.sh b/src/test/test-network.sh index b7a9f1b3c0..4d56e83806 100755 --- a/src/test/test-network.sh +++ b/src/test/test-network.sh @@ -1,11 +1,11 @@ -#!/bin/sh +#!/usr/bin/env bash # This script calls the equivalent script in chutney/tools # If we already know CHUTNEY_PATH, don't bother with argument parsing TEST_NETWORK="$CHUTNEY_PATH/tools/test-network.sh" # Call the chutney version of this script, if it exists, and we can find it -if [ -d "$CHUTNEY_PATH" -a -x "$TEST_NETWORK" ]; then +if [ -d "$CHUTNEY_PATH" ] && [ -x "$TEST_NETWORK" ]; then # we can't produce any output, because we might be --quiet # this preserves arguments with spaces correctly exec "$TEST_NETWORK" "$@" @@ -16,11 +16,11 @@ fi # Do we output anything at all? ECHO="${ECHO:-echo}" # Output is prefixed with the name of the script -myname=$(basename $0) +myname=$(basename "$0") # Save the arguments before we destroy them # This might not preserve arguments with spaces in them -ORIGINAL_ARGS="$@" +ORIGINAL_ARGS=( "$@" ) # We need to find CHUTNEY_PATH, so that we can call the version of this script # in chutney/tools with the same arguments. We also need to respect --quiet. @@ -52,12 +52,12 @@ done # - if $PWD looks like a tor build directory, set it to $PWD, or # - unset $TOR_DIR, and let chutney fall back to finding tor binaries in $PATH if [ ! -d "$TOR_DIR" ]; then - if [ -d "$BUILDDIR/src/core/or" -a -d "$BUILDDIR/src/tools" ]; then + if [ -d "$BUILDDIR/src/core/or" ] && [ -d "$BUILDDIR/src/tools" ]; then # Choose the build directory # But only if it looks like one $ECHO "$myname: \$TOR_DIR not set, trying \$BUILDDIR" TOR_DIR="$BUILDDIR" - elif [ -d "$PWD/src/core/or" -a -d "$PWD/src/tools" ]; then + elif [ -d "$PWD/src/core/or" ] && [ -d "$PWD/src/tools" ]; then # Guess the tor directory is the current directory # But only if it looks like one $ECHO "$myname: \$TOR_DIR not set, trying \$PWD" @@ -73,12 +73,12 @@ fi # - if $PWD looks like a chutney directory, set it to $PWD, or # - set it based on $TOR_DIR, expecting chutney to be next to tor, or # - fail and tell the user how to clone the chutney repository -if [ ! -d "$CHUTNEY_PATH" -o ! -x "$CHUTNEY_PATH/chutney" ]; then +if [ ! -d "$CHUTNEY_PATH" ] || [ ! -x "$CHUTNEY_PATH/chutney" ]; then if [ -x "$PWD/chutney" ]; then $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$PWD" CHUTNEY_PATH="$PWD" - elif [ -d "$TOR_DIR" -a -d "$TOR_DIR/../chutney" -a \ - -x "$TOR_DIR/../chutney/chutney" ]; then + elif [ -d "$TOR_DIR" ] && [ -d "$TOR_DIR/../chutney" ] && \ + [ -x "$TOR_DIR/../chutney/chutney" ]; then $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$TOR_DIR/../chutney" CHUTNEY_PATH="$TOR_DIR/../chutney" else @@ -94,12 +94,12 @@ fi TEST_NETWORK="$CHUTNEY_PATH/tools/test-network.sh" # Call the chutney version of this script, if it exists, and we can find it -if [ -d "$CHUTNEY_PATH" -a -x "$TEST_NETWORK" ]; then +if [ -d "$CHUTNEY_PATH" ] && [ -x "$TEST_NETWORK" ]; then $ECHO "$myname: Calling newer chutney script $TEST_NETWORK" # this may fail if some arguments have spaces in them # if so, set CHUTNEY_PATH before calling test-network.sh, and spaces # will be handled correctly - exec "$TEST_NETWORK" $ORIGINAL_ARGS + exec "$TEST_NETWORK" "${ORIGINAL_ARGS[@]}" # $ORIGINAL_ARGS else $ECHO "$myname: Could not find tools/test-network.sh in CHUTNEY_PATH." $ECHO "$myname: Please update your chutney using 'git pull'." diff --git a/src/test/test.c b/src/test/test.c index 25e9da5591..fbc30fb64e 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -857,6 +857,7 @@ struct testgroup_t testgroups[] = { { "consdiff/", consdiff_tests }, { "consdiffmgr/", consdiffmgr_tests }, { "container/", container_tests }, + { "container/namemap/", namemap_tests }, { "control/", controller_tests }, { "control/btrack/", btrack_tests }, { "control/event/", controller_event_tests }, @@ -872,6 +873,7 @@ struct testgroup_t testgroups[] = { { "dir/voting/flags/", voting_flags_tests }, { "dir/voting/schedule/", voting_schedule_tests }, { "dir_handle_get/", dir_handle_get_tests }, + { "dispatch/", dispatch_tests, }, { "dns/", dns_tests }, { "dos/", dos_tests }, { "entryconn/", entryconn_tests }, @@ -909,6 +911,8 @@ struct testgroup_t testgroups[] = { { "proto/misc/", proto_misc_tests }, { "protover/", protover_tests }, { "pt/", pt_tests }, + { "pubsub/build/", pubsub_build_tests }, + { "pubsub/msg/", pubsub_msg_tests }, { "relay/" , relay_tests }, { "relaycell/", relaycell_tests }, { "relaycrypt/", relaycrypt_tests }, diff --git a/src/test/test.h b/src/test/test.h index 2564432985..7d19af9b20 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -210,6 +210,7 @@ extern struct testcase_t crypto_rng_tests[]; extern struct testcase_t crypto_tests[]; extern struct testcase_t dir_handle_get_tests[]; extern struct testcase_t dir_tests[]; +extern struct testcase_t dispatch_tests[]; extern struct testcase_t dns_tests[]; extern struct testcase_t dos_tests[]; extern struct testcase_t entryconn_tests[]; @@ -235,6 +236,7 @@ extern struct testcase_t link_handshake_tests[]; extern struct testcase_t logging_tests[]; extern struct testcase_t mainloop_tests[]; extern struct testcase_t microdesc_tests[]; +extern struct testcase_t namemap_tests[]; extern struct testcase_t netinfo_tests[]; extern struct testcase_t nodelist_tests[]; extern struct testcase_t oom_tests[]; @@ -252,6 +254,8 @@ extern struct testcase_t proto_http_tests[]; extern struct testcase_t proto_misc_tests[]; extern struct testcase_t protover_tests[]; extern struct testcase_t pt_tests[]; +extern struct testcase_t pubsub_build_tests[]; +extern struct testcase_t pubsub_msg_tests[]; extern struct testcase_t relay_tests[]; extern struct testcase_t relaycell_tests[]; extern struct testcase_t relaycrypt_tests[]; @@ -278,6 +282,7 @@ extern struct testcase_t x509_tests[]; extern struct testcase_t slow_crypto_tests[]; extern struct testcase_t slow_process_tests[]; +extern struct testcase_t slow_ptr_tests[]; extern struct testgroup_t testgroups[]; diff --git a/src/test/test_bt.sh b/src/test/test_bt.sh index df8bcb8eda..312905a4e2 100755 --- a/src/test/test_bt.sh +++ b/src/test/test_bt.sh @@ -3,8 +3,6 @@ exitcode=0 -ulimit -c 0 - export ASAN_OPTIONS="handle_segv=0:allow_user_segv_handler=1" "${builddir:-.}/src/test/test-bt-cl" backtraces || exit $? "${builddir:-.}/src/test/test-bt-cl" assert 2>&1 | "${PYTHON:-python}" "${abs_top_srcdir:-.}/src/test/bt_test.py" || exitcode="$?" diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c index 0c15a02ee4..b29c2c6cbc 100644 --- a/src/test/test_bt_cl.c +++ b/src/test/test_bt_cl.c @@ -4,6 +4,9 @@ #include "orconfig.h" #include <stdio.h> #include <stdlib.h> +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif /* To prevent 'assert' from going away. */ #undef TOR_COVERAGE @@ -43,7 +46,7 @@ crash(int x) *(volatile int *)0 = 0; #endif /* defined(__clang_analyzer__) || defined(__COVERITY__) */ } else if (crashtype == 1) { - tor_assert(1 == 0); + tor_assertf(1 == 0, "%d != %d", 1, 0); } else if (crashtype == -1) { ; } @@ -88,6 +91,11 @@ main(int argc, char **argv) return 1; } +#ifdef HAVE_SYS_RESOURCE_H + struct rlimit rlim = { .rlim_cur = 0, .rlim_max = 0 }; + setrlimit(RLIMIT_CORE, &rlim); +#endif + #if !(defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION)) puts("Backtrace reporting is not supported on this platform"); diff --git a/src/test/test_channel.c b/src/test/test_channel.c index e55b9b0750..4c2bbc86b4 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -598,7 +598,6 @@ test_channel_outbound_cell(void *arg) circuit_set_n_circid_chan(TO_CIRCUIT(circ), 42, chan); tt_int_op(channel_num_circuits(chan), OP_EQ, 1); /* Test the cmux state. */ - tt_ptr_op(TO_CIRCUIT(circ)->n_mux, OP_EQ, chan->cmux); tt_int_op(circuitmux_is_circuit_attached(chan->cmux, TO_CIRCUIT(circ)), OP_EQ, 1); diff --git a/src/test/test_circuitpadding.c b/src/test/test_circuitpadding.c index 22fa817cd6..3289c866cf 100644 --- a/src/test/test_circuitpadding.c +++ b/src/test/test_circuitpadding.c @@ -17,6 +17,7 @@ #include "core/or/circuitlist.h" #include "core/or/circuitbuild.h" #include "core/or/circuitpadding.h" +#include "core/mainloop/netstatus.h" #include "core/crypto/relay_crypto.h" #include "core/or/protover.h" #include "feature/nodelist/nodelist.h" @@ -121,7 +122,6 @@ new_fake_orcirc(channel_t *nchan, channel_t *pchan) //circ->n_chan = nchan; circ->n_circ_id = get_unique_circ_id_by_chan(nchan); - circ->n_mux = NULL; /* ?? */ cell_queue_init(&(circ->n_chan_cells)); circ->n_hop = NULL; circ->streams_blocked_on_n_chan = 0; @@ -341,7 +341,7 @@ test_circuitpadding_rtt(void *arg) OP_EQ, relay_side->padding_info[0]->rtt_estimate_usec+ circpad_machine_current_state( - relay_side->padding_info[0])->start_usec); + relay_side->padding_info[0])->histogram_edges[0]); circpad_cell_event_nonpadding_received((circuit_t*)relay_side); circpad_cell_event_nonpadding_received((circuit_t*)relay_side); @@ -357,7 +357,7 @@ test_circuitpadding_rtt(void *arg) OP_EQ, relay_side->padding_info[0]->rtt_estimate_usec+ circpad_machine_current_state( - relay_side->padding_info[0])->start_usec); + relay_side->padding_info[0])->histogram_edges[0]); /* Test 2: Termination of RTT measurement (from the previous test) */ tt_int_op(relay_side->padding_info[0]->stop_rtt_update, OP_EQ, 1); @@ -375,7 +375,7 @@ test_circuitpadding_rtt(void *arg) OP_EQ, relay_side->padding_info[0]->rtt_estimate_usec+ circpad_machine_current_state( - relay_side->padding_info[0])->start_usec); + relay_side->padding_info[0])->histogram_edges[0]); /* Test 3: Make sure client side machine properly ignores RTT */ circpad_cell_event_nonpadding_received((circuit_t*)client_side); @@ -391,7 +391,7 @@ test_circuitpadding_rtt(void *arg) tt_int_op(circpad_histogram_bin_to_usec(client_side->padding_info[0], 0), OP_EQ, circpad_machine_current_state( - client_side->padding_info[0])->start_usec); + client_side->padding_info[0])->histogram_edges[0]); done: free_fake_orcirc(relay_side); circuitmux_detach_all_circuits(dummy_channel.cmux, NULL); @@ -422,19 +422,23 @@ helper_create_basic_machine(void) circ_client_machine.states[CIRCPAD_STATE_BURST]. next_state[CIRCPAD_EVENT_NONPADDING_SENT] = CIRCPAD_STATE_CANCEL; - // FIXME: Is this what we want? circ_client_machine.states[CIRCPAD_STATE_BURST].token_removal = CIRCPAD_TOKEN_REMOVAL_HIGHER; - // FIXME: Tune this histogram circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_len = 5; - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 500; - circ_client_machine.states[CIRCPAD_STATE_BURST].range_usec = 1000000; + + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[0] = 500; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[1] = 2500; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[2] = 5000; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[3] = 10000; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[4] = 20000; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram[0] = 1; circ_client_machine.states[CIRCPAD_STATE_BURST].histogram[1] = 0; circ_client_machine.states[CIRCPAD_STATE_BURST].histogram[2] = 2; circ_client_machine.states[CIRCPAD_STATE_BURST].histogram[3] = 2; circ_client_machine.states[CIRCPAD_STATE_BURST].histogram[4] = 2; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_total_tokens = 7; circ_client_machine.states[CIRCPAD_STATE_BURST].use_rtt_estimate = 1; @@ -466,15 +470,25 @@ helper_create_machine_with_big_histogram(circpad_removal_t removal_strategy) burst_state->token_removal = CIRCPAD_TOKEN_REMOVAL_HIGHER; burst_state->histogram_len = BIG_HISTOGRAM_LEN; - burst_state->start_usec = 0; - burst_state->range_usec = 1000; int n_tokens = 0; - for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + int i; + for (i = 0; i < BIG_HISTOGRAM_LEN ; i++) { burst_state->histogram[i] = tokens_per_bin; n_tokens += tokens_per_bin; } + burst_state->histogram_edges[0] = 0; + burst_state->histogram_edges[1] = 1; + burst_state->histogram_edges[2] = 7; + burst_state->histogram_edges[3] = 15; + burst_state->histogram_edges[4] = 31; + burst_state->histogram_edges[5] = 62; + burst_state->histogram_edges[6] = 125; + burst_state->histogram_edges[7] = 250; + burst_state->histogram_edges[8] = 500; + burst_state->histogram_edges[9] = 1000; + burst_state->histogram_total_tokens = n_tokens; burst_state->length_dist.type = CIRCPAD_DIST_UNIFORM; burst_state->length_dist.param1 = n_tokens; @@ -486,7 +500,7 @@ helper_create_machine_with_big_histogram(circpad_removal_t removal_strategy) } static circpad_decision_t -circpad_machine_schedule_padding_mock(circpad_machine_state_t *mi) +circpad_machine_schedule_padding_mock(circpad_machine_runtime_t *mi) { (void)mi; return 0; @@ -502,7 +516,7 @@ mock_monotime_absolute_usec(void) static void test_circuitpadding_token_removal_higher(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; (void)arg; /* Mock it up */ @@ -535,12 +549,20 @@ test_circuitpadding_token_removal_higher(void *arg) /* Test left boundaries of each histogram bin: */ const circpad_delay_t bin_left_bounds[] = - {0, 1, 7, 15, 31, 62, 125, 250, 500, CIRCPAD_DELAY_INFINITE}; - for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + {0, 1, 7, 15, 31, 62, 125, 250, 500, 1000, CIRCPAD_DELAY_INFINITE}; + for (int i = 0; i <= BIG_HISTOGRAM_LEN ; i++) { tt_uint_op(bin_left_bounds[i], OP_EQ, circpad_histogram_bin_to_usec(mi, i)); } + /* Test right boundaries of each histogram bin: */ + const circpad_delay_t bin_right_bounds[] = + {0, 6, 14, 30, 61, 124, 249, 499, 999, CIRCPAD_DELAY_INFINITE-1}; + for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + tt_uint_op(bin_right_bounds[i], OP_EQ, + histogram_get_bin_upper_bound(mi, i)); + } + /* Check that all bins have two tokens right now */ for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { tt_int_op(mi->histogram[i], OP_EQ, 2); @@ -584,8 +606,8 @@ test_circuitpadding_token_removal_higher(void *arg) /* Test below the lowest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; - mi->padding_scheduled_at_usec = current_time - 1; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[0] = 100; + mi->padding_scheduled_at_usec = current_time; circpad_machine_remove_token(mi); tt_int_op(mi->histogram[0], OP_EQ, 1); @@ -599,7 +621,7 @@ test_circuitpadding_token_removal_higher(void *arg) static void test_circuitpadding_token_removal_lower(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; (void)arg; /* Mock it up */ @@ -632,8 +654,8 @@ test_circuitpadding_token_removal_lower(void *arg) /* Test left boundaries of each histogram bin: */ const circpad_delay_t bin_left_bounds[] = - {0, 1, 7, 15, 31, 62, 125, 250, 500, CIRCPAD_DELAY_INFINITE}; - for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + {0, 1, 7, 15, 31, 62, 125, 250, 500, 1000, CIRCPAD_DELAY_INFINITE}; + for (int i = 0; i <= BIG_HISTOGRAM_LEN ; i++) { tt_uint_op(bin_left_bounds[i], OP_EQ, circpad_histogram_bin_to_usec(mi, i)); } @@ -681,7 +703,8 @@ test_circuitpadding_token_removal_lower(void *arg) /* Test above the highest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST]. + histogram_edges[BIG_HISTOGRAM_LEN-2] = 100; mi->padding_scheduled_at_usec = current_time - 29202; circpad_machine_remove_token(mi); tt_int_op(mi->histogram[BIG_HISTOGRAM_LEN-2], OP_EQ, 1); @@ -696,7 +719,7 @@ test_circuitpadding_token_removal_lower(void *arg) static void test_circuitpadding_closest_token_removal(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; (void)arg; /* Mock it up */ @@ -729,8 +752,8 @@ test_circuitpadding_closest_token_removal(void *arg) /* Test left boundaries of each histogram bin: */ const circpad_delay_t bin_left_bounds[] = - {0, 1, 7, 15, 31, 62, 125, 250, 500, CIRCPAD_DELAY_INFINITE}; - for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + {0, 1, 7, 15, 31, 62, 125, 250, 500, 1000, CIRCPAD_DELAY_INFINITE}; + for (int i = 0; i <= BIG_HISTOGRAM_LEN ; i++) { tt_uint_op(bin_left_bounds[i], OP_EQ, circpad_histogram_bin_to_usec(mi, i)); } @@ -777,7 +800,9 @@ test_circuitpadding_closest_token_removal(void *arg) /* Test below the lowest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[0] = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[1] = 101; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[2] = 120; mi->padding_scheduled_at_usec = current_time - 102; mi->histogram[0] = 0; circpad_machine_remove_token(mi); @@ -786,7 +811,6 @@ test_circuitpadding_closest_token_removal(void *arg) /* Test above the highest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; mi->padding_scheduled_at_usec = current_time - 29202; circpad_machine_remove_token(mi); tt_int_op(mi->histogram[BIG_HISTOGRAM_LEN-2], OP_EQ, 1); @@ -801,7 +825,7 @@ test_circuitpadding_closest_token_removal(void *arg) static void test_circuitpadding_closest_token_removal_usec(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; (void)arg; /* Mock it up */ @@ -834,8 +858,8 @@ test_circuitpadding_closest_token_removal_usec(void *arg) /* Test left boundaries of each histogram bin: */ const circpad_delay_t bin_left_bounds[] = - {0, 1, 7, 15, 31, 62, 125, 250, 500, CIRCPAD_DELAY_INFINITE}; - for (int i = 0; i < BIG_HISTOGRAM_LEN ; i++) { + {0, 1, 7, 15, 31, 62, 125, 250, 500, 1000, CIRCPAD_DELAY_INFINITE}; + for (int i = 0; i <= BIG_HISTOGRAM_LEN ; i++) { tt_uint_op(bin_left_bounds[i], OP_EQ, circpad_histogram_bin_to_usec(mi, i)); } @@ -885,7 +909,9 @@ test_circuitpadding_closest_token_removal_usec(void *arg) /* Test below the lowest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[0] = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[1] = 101; + circ_client_machine.states[CIRCPAD_STATE_BURST].histogram_edges[2] = 120; mi->padding_scheduled_at_usec = current_time - 102; mi->histogram[0] = 0; circpad_machine_remove_token(mi); @@ -894,7 +920,8 @@ test_circuitpadding_closest_token_removal_usec(void *arg) /* Test above the highest bin, for coverage */ tt_int_op(client_side->padding_info[0]->current_state, OP_EQ, CIRCPAD_STATE_BURST); - circ_client_machine.states[CIRCPAD_STATE_BURST].start_usec = 100; + circ_client_machine.states[CIRCPAD_STATE_BURST]. + histogram_edges[BIG_HISTOGRAM_LEN-2] = 100; mi->padding_scheduled_at_usec = current_time - 29202; circpad_machine_remove_token(mi); tt_int_op(mi->histogram[BIG_HISTOGRAM_LEN-2], OP_EQ, 1); @@ -909,7 +936,7 @@ test_circuitpadding_closest_token_removal_usec(void *arg) static void test_circuitpadding_token_removal_exact(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; (void)arg; /* Mock it up */ @@ -970,7 +997,7 @@ void test_circuitpadding_tokens(void *arg) { const circpad_state_t *state; - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; int64_t actual_mocked_monotime_start; (void)arg; @@ -1004,6 +1031,9 @@ test_circuitpadding_tokens(void *arg) monotime_coarse_set_mock_time_nsec(actual_mocked_monotime_start); curr_mocked_time = actual_mocked_monotime_start; + /* This is needed so that we are not considered to be dormant */ + note_user_activity(20); + timers_initialize(); helper_create_basic_machine(); @@ -1052,7 +1082,7 @@ test_circuitpadding_tokens(void *arg) // Test 1: converting usec->bin->usec->bin // Bin 0+1 have different semantics. - for (circpad_delay_t i = 0; i <= state->start_usec+1; i++) { + for (circpad_delay_t i = 0; i <= state->histogram_edges[0]; i++) { int bin = circpad_histogram_usec_to_bin(client_side->padding_info[0], i); circpad_delay_t usec = @@ -1062,8 +1092,9 @@ test_circuitpadding_tokens(void *arg) tt_int_op(bin, OP_EQ, bin2); tt_int_op(i, OP_LE, usec); } - for (circpad_delay_t i = state->start_usec+1; - i <= state->start_usec + state->range_usec; i++) { + for (circpad_delay_t i = state->histogram_edges[0]+1; + i <= state->histogram_edges[0] + + state->histogram_edges[state->histogram_len-2]; i++) { int bin = circpad_histogram_usec_to_bin(client_side->padding_info[0], i); circpad_delay_t usec = @@ -1116,8 +1147,7 @@ test_circuitpadding_tokens(void *arg) { tt_int_op(mi->histogram[0], OP_EQ, 0); mi->histogram[0] = 1; - circpad_machine_remove_higher_token(mi, - state->start_usec/2); + circpad_machine_remove_higher_token(mi, state->histogram_edges[0]/2); tt_int_op(mi->histogram[0], OP_EQ, 0); } @@ -1136,8 +1166,7 @@ test_circuitpadding_tokens(void *arg) /* 3.a. Bin 0 */ { tt_int_op(mi->histogram[0], OP_EQ, 1); - circpad_machine_remove_higher_token(mi, - state->start_usec/2); + circpad_machine_remove_higher_token(mi, state->histogram_edges[0]/2); tt_int_op(mi->histogram[0], OP_EQ, 0); } @@ -1629,15 +1658,20 @@ helper_create_conditional_machine(void) ret->states[CIRCPAD_STATE_BURST]. next_state[CIRCPAD_EVENT_LENGTH_COUNT] = CIRCPAD_STATE_END; + /* Use EXACT removal strategy, otherwise setup_tokens() does not work */ ret->states[CIRCPAD_STATE_BURST].token_removal = - CIRCPAD_TOKEN_REMOVAL_NONE; + CIRCPAD_TOKEN_REMOVAL_EXACT; ret->states[CIRCPAD_STATE_BURST].histogram_len = 3; - ret->states[CIRCPAD_STATE_BURST].start_usec = 0; - ret->states[CIRCPAD_STATE_BURST].range_usec = 1000000; + + ret->states[CIRCPAD_STATE_BURST].histogram_edges[0] = 0; + ret->states[CIRCPAD_STATE_BURST].histogram_edges[1] = 1; + ret->states[CIRCPAD_STATE_BURST].histogram_edges[2] = 1000000; + ret->states[CIRCPAD_STATE_BURST].histogram[0] = 6; ret->states[CIRCPAD_STATE_BURST].histogram[1] = 0; - ret->states[CIRCPAD_STATE_BURST].histogram[1] = 0; + ret->states[CIRCPAD_STATE_BURST].histogram[2] = 0; + ret->states[CIRCPAD_STATE_BURST].histogram_total_tokens = 6; ret->states[CIRCPAD_STATE_BURST].use_rtt_estimate = 0; ret->states[CIRCPAD_STATE_BURST].length_includes_nonpadding = 1; @@ -1668,8 +1702,7 @@ helper_create_conditional_machines(void) add->conditions.state_mask = CIRCPAD_CIRC_BUILDING| CIRCPAD_CIRC_NO_STREAMS|CIRCPAD_CIRC_HAS_RELAY_EARLY; add->conditions.purpose_mask = CIRCPAD_PURPOSE_ALL; - - smartlist_add(origin_padding_machines, add); + register_padding_machine(add, origin_padding_machines); add = helper_create_conditional_machine(); add->machine_num = 3; @@ -1688,15 +1721,15 @@ helper_create_conditional_machines(void) add->conditions.state_mask = CIRCPAD_CIRC_OPENED| CIRCPAD_CIRC_STREAMS|CIRCPAD_CIRC_HAS_NO_RELAY_EARLY; add->conditions.purpose_mask = CIRCPAD_PURPOSE_ALL; - smartlist_add(origin_padding_machines, add); + register_padding_machine(add, origin_padding_machines); add = helper_create_conditional_machine(); add->machine_num = 2; - smartlist_add(relay_padding_machines, add); + register_padding_machine(add, relay_padding_machines); add = helper_create_conditional_machine(); add->machine_num = 3; - smartlist_add(relay_padding_machines, add); + register_padding_machine(add, relay_padding_machines); } void @@ -1736,6 +1769,9 @@ test_circuitpadding_conditions(void *arg) monotime_coarse_set_mock_time_nsec(actual_mocked_monotime_start); curr_mocked_time = actual_mocked_monotime_start; + /* This is needed so that we are not considered to be dormant */ + note_user_activity(20); + timers_initialize(); helper_create_conditional_machines(); @@ -2085,60 +2121,63 @@ helper_circpad_circ_distribution_machine_setup(int min, int max) circpad_state_t *zero_st = &circ_client_machine.states[0]; zero_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 1; zero_st->iat_dist.type = CIRCPAD_DIST_UNIFORM; + /* param2 is upper bound, param1 is lower */ zero_st->iat_dist.param1 = min; zero_st->iat_dist.param2 = max; - zero_st->start_usec = min; - zero_st->range_usec = max; + zero_st->dist_added_shift_usec = min; + zero_st->dist_max_sample_usec = max; circpad_state_t *first_st = &circ_client_machine.states[1]; first_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 2; first_st->iat_dist.type = CIRCPAD_DIST_LOGISTIC; - first_st->iat_dist.param1 = min; - first_st->iat_dist.param2 = max; - first_st->start_usec = min; - first_st->range_usec = max; + /* param1 is Mu, param2 is sigma. */ + first_st->iat_dist.param1 = 9; + first_st->iat_dist.param2 = 3; + first_st->dist_added_shift_usec = min; + first_st->dist_max_sample_usec = max; circpad_state_t *second_st = &circ_client_machine.states[2]; second_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 3; second_st->iat_dist.type = CIRCPAD_DIST_LOG_LOGISTIC; - second_st->iat_dist.param1 = min; - second_st->iat_dist.param2 = max; - second_st->start_usec = min; - second_st->range_usec = max; + /* param1 is Alpha, param2 is 1.0/Beta */ + second_st->iat_dist.param1 = 1; + second_st->iat_dist.param2 = 0.5; + second_st->dist_added_shift_usec = min; + second_st->dist_max_sample_usec = max; circpad_state_t *third_st = &circ_client_machine.states[3]; third_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 4; third_st->iat_dist.type = CIRCPAD_DIST_GEOMETRIC; - third_st->iat_dist.param1 = min; - third_st->iat_dist.param2 = max; - third_st->start_usec = min; - third_st->range_usec = max; + /* param1 is 'p' (success probability) */ + third_st->iat_dist.param1 = 0.2; + third_st->dist_added_shift_usec = min; + third_st->dist_max_sample_usec = max; circpad_state_t *fourth_st = &circ_client_machine.states[4]; fourth_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 5; fourth_st->iat_dist.type = CIRCPAD_DIST_WEIBULL; - fourth_st->iat_dist.param1 = min; - fourth_st->iat_dist.param2 = max; - fourth_st->start_usec = min; - fourth_st->range_usec = max; + /* param1 is k, param2 is Lambda */ + fourth_st->iat_dist.param1 = 1.5; + fourth_st->iat_dist.param2 = 1; + fourth_st->dist_added_shift_usec = min; + fourth_st->dist_max_sample_usec = max; circpad_state_t *fifth_st = &circ_client_machine.states[5]; fifth_st->next_state[CIRCPAD_EVENT_NONPADDING_RECV] = 6; fifth_st->iat_dist.type = CIRCPAD_DIST_PARETO; - fifth_st->iat_dist.param1 = min; - fifth_st->iat_dist.param2 = max; - fifth_st->start_usec = min; - fifth_st->range_usec = max; + /* param1 is sigma, param2 is xi */ + fifth_st->iat_dist.param1 = 1; + fifth_st->iat_dist.param2 = 5; + fifth_st->dist_added_shift_usec = min; + fifth_st->dist_max_sample_usec = max; } /** Simple test that the padding delays sampled from a uniform distribution * actually faill within the uniform distribution range. */ -/* TODO: Upgrade this test so that each state tests a different prob - * distribution */ static void test_circuitpadding_sample_distribution(void *arg) { - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; int n_samples; int n_states; @@ -2148,8 +2187,7 @@ test_circuitpadding_sample_distribution(void *arg) MOCK(circpad_machine_schedule_padding, circpad_machine_schedule_padding_mock); - /* Initialize a machine with multiple probability distributions that should - * return values between 0 and 5 */ + /* Initialize a machine with multiple probability distributions */ circpad_machines_init(); helper_circpad_circ_distribution_machine_setup(0, 10); @@ -2183,7 +2221,7 @@ test_circuitpadding_sample_distribution(void *arg) } static circpad_decision_t -circpad_machine_spec_transition_mock(circpad_machine_state_t *mi, +circpad_machine_spec_transition_mock(circpad_machine_runtime_t *mi, circpad_event_t event) { (void) mi; @@ -2198,7 +2236,7 @@ test_circuitpadding_machine_rate_limiting(void *arg) { (void) arg; bool retval; - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; int i; /* Ignore machine transitions for the purposes of this function, we only @@ -2266,7 +2304,7 @@ test_circuitpadding_global_rate_limiting(void *arg) { (void) arg; bool retval; - circpad_machine_state_t *mi; + circpad_machine_runtime_t *mi; int i; int64_t actual_mocked_monotime_start; diff --git a/src/test/test_connection.h b/src/test/test_connection.h index 47a5599e5f..027e405d89 100644 --- a/src/test/test_connection.h +++ b/src/test/test_connection.h @@ -1,6 +1,9 @@ /* Copyright (c) 2014-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#ifndef TOR_TEST_CONNECTION_H +#define TOR_TEST_CONNECTION_H + /** Some constants used by test_connection and helpers */ #define TEST_CONN_FAMILY (AF_INET) #define TEST_CONN_ADDRESS "127.0.0.1" @@ -11,3 +14,4 @@ void test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr); +#endif diff --git a/src/test/test_containers.c b/src/test/test_containers.c index a0832f868e..7892a08853 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -606,6 +606,66 @@ test_container_smartlist_ints_eq(void *arg) smartlist_free(sl2); } +static void +test_container_smartlist_grow(void *arg) +{ + (void)arg; + smartlist_t *sl = smartlist_new(); + int i; + const char *s[] = { "first", "2nd", "3rd" }; + + /* case 1: starting from empty. */ + smartlist_grow(sl, 10); + tt_int_op(10, OP_EQ, smartlist_len(sl)); + for (i = 0; i < 10; ++i) { + tt_ptr_op(smartlist_get(sl, i), OP_EQ, NULL); + } + + /* case 2: starting with a few elements, probably not reallocating. */ + smartlist_free(sl); + sl = smartlist_new(); + smartlist_add(sl, (char*)s[0]); + smartlist_add(sl, (char*)s[1]); + smartlist_add(sl, (char*)s[2]); + smartlist_grow(sl, 5); + tt_int_op(5, OP_EQ, smartlist_len(sl)); + for (i = 0; i < 3; ++i) { + tt_ptr_op(smartlist_get(sl, i), OP_EQ, s[i]); + } + tt_ptr_op(smartlist_get(sl, 3), OP_EQ, NULL); + tt_ptr_op(smartlist_get(sl, 4), OP_EQ, NULL); + + /* case 3: starting with a few elements, but reallocating. */ + smartlist_free(sl); + sl = smartlist_new(); + smartlist_add(sl, (char*)s[0]); + smartlist_add(sl, (char*)s[1]); + smartlist_add(sl, (char*)s[2]); + smartlist_grow(sl, 100); + tt_int_op(100, OP_EQ, smartlist_len(sl)); + for (i = 0; i < 3; ++i) { + tt_ptr_op(smartlist_get(sl, i), OP_EQ, s[i]); + } + for (i = 3; i < 100; ++i) { + tt_ptr_op(smartlist_get(sl, i), OP_EQ, NULL); + } + + /* case 4: shrinking doesn't happen. */ + smartlist_free(sl); + sl = smartlist_new(); + smartlist_add(sl, (char*)s[0]); + smartlist_add(sl, (char*)s[1]); + smartlist_add(sl, (char*)s[2]); + smartlist_grow(sl, 1); + tt_int_op(3, OP_EQ, smartlist_len(sl)); + for (i = 0; i < 3; ++i) { + tt_ptr_op(smartlist_get(sl, i), OP_EQ, s[i]); + } + + done: + smartlist_free(sl); +} + /** Run unit tests for bitarray code */ static void test_container_bitarray(void *arg) @@ -1312,6 +1372,7 @@ struct testcase_t container_tests[] = { CONTAINER_LEGACY(smartlist_pos), CONTAINER(smartlist_remove, 0), CONTAINER(smartlist_ints_eq, 0), + CONTAINER(smartlist_grow, 0), CONTAINER_LEGACY(bitarray), CONTAINER_LEGACY(digestset), CONTAINER_LEGACY(strmap), diff --git a/src/test/test_controller.c b/src/test/test_controller.c index 5b406e159b..f3af6d2ec0 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -1,11 +1,14 @@ /* Copyright (c) 2015-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define CONTROL_PRIVATE +#define CONTROL_CMD_PRIVATE +#define CONTROL_GETINFO_PRIVATE #include "core/or/or.h" #include "lib/crypt_ops/crypto_ed25519.h" #include "feature/client/bridges.h" #include "feature/control/control.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_getinfo.h" #include "feature/client/entrynodes.h" #include "feature/hs/hs_common.h" #include "feature/nodelist/networkstatus.h" diff --git a/src/test/test_controller_events.c b/src/test/test_controller_events.c index 647eac43c7..910aacace3 100644 --- a/src/test/test_controller_events.c +++ b/src/test/test_controller_events.c @@ -4,6 +4,7 @@ #define CONNECTION_PRIVATE #define TOR_CHANNEL_INTERNAL_ #define CONTROL_PRIVATE +#define CONTROL_EVENTS_PRIVATE #define OCIRC_EVENT_PRIVATE #define ORCONN_EVENT_PRIVATE #include "core/or/or.h" @@ -13,7 +14,7 @@ #include "core/or/ocirc_event.h" #include "core/or/orconn_event.h" #include "core/mainloop/connection.h" -#include "feature/control/control.h" +#include "feature/control/control_events.h" #include "test/test.h" #include "core/or/or_circuit_st.h" diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index 0b57448bcf..a5c17b3e6a 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -1011,13 +1011,19 @@ test_crypto_sha3_xof(void *arg) crypto_xof_free(xof); memset(out, 0, sizeof(out)); + /* Test one-function absorb/squeeze. */ + crypto_xof(out, sizeof(out), msg, sizeof(msg)); + test_memeq_hex(out, squeezed_hex); + memset(out, 0, sizeof(out)); + /* Test incremental absorb/squeeze. */ xof = crypto_xof_new(); tt_assert(xof); for (size_t i = 0; i < sizeof(msg); i++) crypto_xof_add_bytes(xof, msg + i, 1); - for (size_t i = 0; i < sizeof(out); i++) + for (size_t i = 0; i < sizeof(out); i++) { crypto_xof_squeeze_bytes(xof, out + i, 1); + } test_memeq_hex(out, squeezed_hex); done: @@ -1703,13 +1709,13 @@ test_crypto_base32_decode(void *arg) /* Encode and decode a random string. */ base32_encode(encoded, 96 + 1, plain, 60); res = base32_decode(decoded, 60, encoded, 96); - tt_int_op(res,OP_EQ, 0); + tt_int_op(res, OP_EQ, 60); tt_mem_op(plain,OP_EQ, decoded, 60); /* Encode, uppercase, and decode a random string. */ base32_encode(encoded, 96 + 1, plain, 60); tor_strupper(encoded); res = base32_decode(decoded, 60, encoded, 96); - tt_int_op(res,OP_EQ, 0); + tt_int_op(res, OP_EQ, 60); tt_mem_op(plain,OP_EQ, decoded, 60); /* Change encoded string and decode. */ if (encoded[0] == 'A' || encoded[0] == 'a') @@ -1717,12 +1723,12 @@ test_crypto_base32_decode(void *arg) else encoded[0] = 'A'; res = base32_decode(decoded, 60, encoded, 96); - tt_int_op(res,OP_EQ, 0); + tt_int_op(res, OP_EQ, 60); tt_mem_op(plain,OP_NE, decoded, 60); /* Bad encodings. */ encoded[0] = '!'; res = base32_decode(decoded, 60, encoded, 96); - tt_int_op(0, OP_GT, res); + tt_int_op(res, OP_LT, 0); done: ; diff --git a/src/test/test_crypto_rng.c b/src/test/test_crypto_rng.c index 23b0c66514..6b7749a889 100644 --- a/src/test/test_crypto_rng.c +++ b/src/test/test_crypto_rng.c @@ -218,6 +218,14 @@ test_crypto_rng_fast(void *arg) tt_int_op(counts[i], OP_GT, 0); } + /* per-thread rand_fast shouldn't crash or leak. */ + crypto_fast_rng_t *t_rng = get_thread_fast_rng(); + for (int i = 0; i < N; ++i) { + uint64_t u64 = crypto_fast_rng_get_uint64(t_rng, UINT64_C(1)<<40); + tt_u64_op(u64, OP_GE, 0); + tt_u64_op(u64, OP_LT, UINT64_C(1)<<40); + } + done: crypto_fast_rng_free(rng); } diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 07a2641c9f..6518977b6f 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -8,7 +8,7 @@ #define BWAUTH_PRIVATE #define CONFIG_PRIVATE -#define CONTROL_PRIVATE +#define CONTROL_GETINFO_PRIVATE #define DIRCACHE_PRIVATE #define DIRCLIENT_PRIVATE #define DIRSERV_PRIVATE @@ -32,7 +32,7 @@ #include "core/or/versions.h" #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" -#include "feature/control/control.h" +#include "feature/control/control_getinfo.h" #include "feature/dirauth/bwauth.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/dsigs_parse.h" @@ -162,6 +162,269 @@ test_dir_nicknames(void *arg) ; } +/* Allocate and return a new routerinfo, with the fields set from the + * arguments to this function. + * + * Also sets: + * - random RSA identity and onion keys, + * - the platform field using get_platform_str(), and + * - supports_tunnelled_dir_requests to 1. + * + * If rsa_onion_keypair_out is not NULL, it is set to the onion keypair. + * The caller must free this keypair. + */ +static routerinfo_t * +basic_routerinfo_new(const char *nickname, uint32_t ipv4_addr, + uint16_t or_port, uint16_t dir_port, + uint32_t bandwidthrate, uint32_t bandwidthburst, + uint32_t bandwidthcapacity, + time_t published_on, + crypto_pk_t **rsa_onion_keypair_out) +{ + char platform[256]; + + tor_assert(nickname); + + crypto_pk_t *pk1 = NULL, *pk2 = NULL; + /* These keys are random: idx is ignored. */ + pk1 = pk_generate(0); + pk2 = pk_generate(1); + + tor_assert(pk1); + tor_assert(pk2); + + get_platform_str(platform, sizeof(platform)); + + routerinfo_t *r1 = tor_malloc_zero(sizeof(routerinfo_t)); + + r1->nickname = tor_strdup(nickname); + r1->platform = tor_strdup(platform); + + r1->addr = ipv4_addr; + r1->or_port = or_port; + r1->dir_port = dir_port; + r1->supports_tunnelled_dir_requests = 1; + + router_set_rsa_onion_pkey(pk1, &r1->onion_pkey, &r1->onion_pkey_len); + r1->identity_pkey = pk2; + + r1->bandwidthrate = bandwidthrate; + r1->bandwidthburst = bandwidthburst; + r1->bandwidthcapacity = bandwidthcapacity; + + r1->cache_info.published_on = published_on; + + if (rsa_onion_keypair_out) { + *rsa_onion_keypair_out = pk1; + } else { + crypto_pk_free(pk1); + } + + return r1; +} + +/* Allocate and return a new string containing a "router" line for r1. */ +static char * +get_new_router_line(const routerinfo_t *r1) +{ + char *line = NULL; + + tor_assert(r1); + + tor_asprintf(&line, + "router %s %s %d 0 %d\n", + r1->nickname, fmt_addr32(r1->addr), + r1->or_port, r1->dir_port); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing a "platform" line for the + * current Tor version and OS. */ +static char * +get_new_platform_line(void) +{ + char *line = NULL; + + tor_asprintf(&line, + "platform Tor %s on %s\n", + VERSION, get_uname()); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing a "published" line for r1. + * r1->cache_info.published_on must be between 0 and 59 seconds. */ +static char * +get_new_published_line(const routerinfo_t *r1) +{ + char *line = NULL; + + tor_assert(r1); + + tor_assert(r1->cache_info.published_on >= 0); + tor_assert(r1->cache_info.published_on <= 59); + + tor_asprintf(&line, + "published 1970-01-01 00:00:%02u\n", + (unsigned)r1->cache_info.published_on); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing a "fingerprint" line for r1. */ +static char * +get_new_fingerprint_line(const routerinfo_t *r1) +{ + char *line = NULL; + char fingerprint[FINGERPRINT_LEN+1]; + + tor_assert(r1); + + tor_assert(!crypto_pk_get_fingerprint(r1->identity_pkey, fingerprint, 1)); + tor_assert(strlen(fingerprint) > 0); + + tor_asprintf(&line, + "fingerprint %s\n", + fingerprint); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing an "uptime" line with uptime t. + * + * You should pass a hard-coded value to this function, because even if we made + * it reflect uptime, that still wouldn't make it right, because the two + * descriptors might be made on different seconds. + */ +static char * +get_new_uptime_line(time_t t) +{ + char *line = NULL; + + tor_asprintf(&line, + "uptime %u\n", + (unsigned)t); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing an "bandwidth" line for r1. + */ +static char * +get_new_bandwidth_line(const routerinfo_t *r1) +{ + char *line = NULL; + + tor_assert(r1); + + tor_asprintf(&line, + "bandwidth %u %u %u\n", + r1->bandwidthrate, + r1->bandwidthburst, + r1->bandwidthcapacity); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing a key_name block for the + * RSA key pk1. + */ +static char * +get_new_rsa_key_block(const char *key_name, crypto_pk_t *pk1) +{ + char *block = NULL; + char *pk1_str = NULL; + size_t pk1_str_len = 0; + + tor_assert(key_name); + tor_assert(pk1); + + tor_assert(!crypto_pk_write_public_key_to_string(pk1, &pk1_str, + &pk1_str_len)); + tor_assert(pk1_str); + tor_assert(pk1_str_len); + + tor_asprintf(&block, + "%s\n%s", + key_name, + pk1_str); + tor_free(pk1_str); + + tor_assert(block); + return block; +} + +/* Allocate and return a new string containing an "onion-key" block for the + * router r1. + */ +static char * +get_new_onion_key_block(const routerinfo_t *r1) +{ + char *block = NULL; + tor_assert(r1); + crypto_pk_t *pk_tmp = router_get_rsa_onion_pkey(r1->onion_pkey, + r1->onion_pkey_len); + block = get_new_rsa_key_block("onion-key", pk_tmp); + crypto_pk_free(pk_tmp); + return block; +} + +/* Allocate and return a new string containing an "signing-key" block for the + * router r1. + */ +static char * +get_new_signing_key_block(const routerinfo_t *r1) +{ + tor_assert(r1); + return get_new_rsa_key_block("signing-key", r1->identity_pkey); +} + +/* Allocate and return a new string containing an "ntor-onion-key" line for + * the curve25519 public key ntor_onion_pubkey. + */ +static char * +get_new_ntor_onion_key_line(const curve25519_public_key_t *ntor_onion_pubkey) +{ + char *line = NULL; + char cert_buf[256]; + int rv = 0; + + tor_assert(ntor_onion_pubkey); + + rv = base64_encode(cert_buf, sizeof(cert_buf), + (const char*)ntor_onion_pubkey->public_key, 32, + BASE64_ENCODE_MULTILINE); + tor_assert(rv > 0); + tor_assert(strlen(cert_buf) > 0); + + tor_asprintf(&line, + "ntor-onion-key %s", + cert_buf); + tor_assert(line); + + return line; +} + +/* Allocate and return a new string containing a "bridge-distribution-request" + * line for options. + */ +static char * +get_new_bridge_distribution_request_line(const or_options_t *options) +{ + if (options->BridgeRelay) { + return tor_strdup("bridge-distribution-request any\n"); + } else { + return tor_strdup(""); + } +} + static smartlist_t *mocked_configured_ports = NULL; /** Returns mocked_configured_ports */ @@ -171,71 +434,510 @@ mock_get_configured_ports(void) return mocked_configured_ports; } -/** Run unit tests for router descriptor generation logic. */ +static tor_cert_t * +mock_tor_cert_dup_null(const tor_cert_t *cert) +{ + (void)cert; + return NULL; +} + +static crypto_pk_t *mocked_server_identitykey = NULL; + +/* Returns mocked_server_identitykey with no checks. */ +static crypto_pk_t * +mock_get_server_identity_key(void) +{ + return mocked_server_identitykey; +} + +static crypto_pk_t *mocked_onionkey = NULL; + +/* Returns mocked_onionkey with no checks. */ +static crypto_pk_t * +mock_get_onion_key(void) +{ + return mocked_onionkey; +} + +static routerinfo_t *mocked_routerinfo = NULL; + +/* Returns 0 and sets ri_out to mocked_routerinfo. + * ri_out must not be NULL. There are no other checks. */ +static int +mock_router_build_fresh_unsigned_routerinfo(routerinfo_t **ri_out) +{ + tor_assert(ri_out); + *ri_out = mocked_routerinfo; + return 0; +} + +static ed25519_keypair_t *mocked_master_signing_key = NULL; + +/* Returns mocked_master_signing_key with no checks. */ +static const ed25519_keypair_t * +mock_get_master_signing_keypair(void) +{ + return mocked_master_signing_key; +} + +static struct tor_cert_st *mocked_signing_key_cert = NULL; + +/* Returns mocked_signing_key_cert with no checks. */ +static const struct tor_cert_st * +mock_get_master_signing_key_cert(void) +{ + return mocked_signing_key_cert; +} + +static curve25519_keypair_t *mocked_curve25519_onion_key = NULL; + +/* Returns mocked_curve25519_onion_key with no checks. */ +static const curve25519_keypair_t * +mock_get_current_curve25519_keypair(void) +{ + return mocked_curve25519_onion_key; +} + +/* Unmock get_configured_ports() and free mocked_configured_ports. */ +static void +cleanup_mock_configured_ports(void) +{ + UNMOCK(get_configured_ports); + + if (mocked_configured_ports) { + SMARTLIST_FOREACH(mocked_configured_ports, port_cfg_t *, p, tor_free(p)); + smartlist_free(mocked_configured_ports); + } +} + +/* Mock get_configured_ports() with a list containing or_port and dir_port. + * If a port is 0, don't set it. + * Only sets the minimal data required for the tests to pass. */ +static void +setup_mock_configured_ports(uint16_t or_port, uint16_t dir_port) +{ + cleanup_mock_configured_ports(); + + /* Fake just enough of an ORPort and DirPort to get by */ + MOCK(get_configured_ports, mock_get_configured_ports); + mocked_configured_ports = smartlist_new(); + + if (or_port) { + port_cfg_t *or_port_cfg = tor_malloc_zero(sizeof(*or_port_cfg)); + or_port_cfg->type = CONN_TYPE_OR_LISTENER; + or_port_cfg->addr.family = AF_INET; + or_port_cfg->port = or_port; + smartlist_add(mocked_configured_ports, or_port_cfg); + } + + if (dir_port) { + port_cfg_t *dir_port_cfg = tor_malloc_zero(sizeof(*dir_port_cfg)); + dir_port_cfg->type = CONN_TYPE_DIR_LISTENER; + dir_port_cfg->addr.family = AF_INET; + dir_port_cfg->port = dir_port; + smartlist_add(mocked_configured_ports, dir_port_cfg); + } +} + +/* Clean up the data structures and unmock the functions needed for generating + * a fresh descriptor. */ +static void +cleanup_mocks_for_fresh_descriptor(void) +{ + tor_free(get_options_mutable()->Nickname); + + mocked_server_identitykey = NULL; + UNMOCK(get_server_identity_key); + + crypto_pk_free(mocked_onionkey); + UNMOCK(get_onion_key); +} + +/* Mock the data structures and functions needed for generating a fresh + * descriptor. + * + * Sets options->Nickname from r1->nickname. + * Mocks get_server_identity_key() with r1->identity_pkey. + * + * If rsa_onion_keypair is not NULL, it is used to mock get_onion_key(). + * Otherwise, the public key in r1->onion_pkey is used to mock get_onion_key(). + */ static void -test_dir_formats(void *arg) +setup_mocks_for_fresh_descriptor(const routerinfo_t *r1, + crypto_pk_t *rsa_onion_keypair) +{ + cleanup_mocks_for_fresh_descriptor(); + + tor_assert(r1); + + /* router_build_fresh_signed_extrainfo() requires options->Nickname */ + get_options_mutable()->Nickname = tor_strdup(r1->nickname); + + /* router_build_fresh_signed_extrainfo() requires get_server_identity_key(). + * Use the same one as the call to router_dump_router_to_string() above. + */ + mocked_server_identitykey = r1->identity_pkey; + MOCK(get_server_identity_key, mock_get_server_identity_key); + + /* router_dump_and_sign_routerinfo_descriptor_body() requires + * get_onion_key(). Use the same one as r1. + */ + if (rsa_onion_keypair) { + mocked_onionkey = crypto_pk_dup_key(rsa_onion_keypair); + } else { + mocked_onionkey = router_get_rsa_onion_pkey(r1->onion_pkey, + r1->onion_pkey_len); + } + MOCK(get_onion_key, mock_get_onion_key); +} + +/* Set options based on arg. + * + * b: BridgeRelay 1 + * e: ExtraInfoStatistics 1 + * s: sets all the individual statistics options to 1 + * + * Always sets AssumeReachable to 1. + * + * Does not set ServerTransportPlugin, because it's parsed before use. + * + * Does not set BridgeRecordUsageByCountry, because the tests don't have access + * to a GeoIPFile or GeoIPv6File. */ +static void +setup_dir_formats_options(const char *arg, or_options_t *options) +{ + /* Skip reachability checks for DirPort, ORPort, and tunnelled-dir-server */ + options->AssumeReachable = 1; + + if (strchr(arg, 'b')) { + options->BridgeRelay = 1; + } + + if (strchr(arg, 'e')) { + options->ExtraInfoStatistics = 1; + } + + if (strchr(arg, 's')) { + options->DirReqStatistics = 1; + options->HiddenServiceStatistics = 1; + options->EntryStatistics = 1; + options->CellStatistics = 1; + options->ExitPortStatistics = 1; + options->ConnDirectionStatistics = 1; + options->PaddingStatistics = 1; + } +} + +/* Check that routerinfos r1 and rp1 are consistent. + * Only performs some basic checks. + */ +#define CHECK_ROUTERINFO_CONSISTENCY(r1, rp1) \ +STMT_BEGIN \ + tt_assert(r1); \ + tt_assert(rp1); \ +\ + tt_int_op(rp1->addr,OP_EQ, r1->addr); \ + tt_int_op(rp1->or_port,OP_EQ, r1->or_port); \ + tt_int_op(rp1->dir_port,OP_EQ, r1->dir_port); \ + tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate); \ + tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst); \ + tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity); \ + crypto_pk_t *rp1_onion_pkey = router_get_rsa_onion_pkey(rp1->onion_pkey, \ + rp1->onion_pkey_len); \ + crypto_pk_t *r1_onion_pkey = router_get_rsa_onion_pkey(r1->onion_pkey, \ + r1->onion_pkey_len); \ + tt_int_op(crypto_pk_cmp_keys(rp1_onion_pkey, r1_onion_pkey), OP_EQ, 0); \ + crypto_pk_free(rp1_onion_pkey); \ + crypto_pk_free(r1_onion_pkey); \ + tt_int_op(crypto_pk_cmp_keys(rp1->identity_pkey, r1->identity_pkey), \ + OP_EQ, 0); \ + tt_int_op(rp1->supports_tunnelled_dir_requests, OP_EQ, \ + r1->supports_tunnelled_dir_requests); \ +STMT_END + +/* Check that routerinfo r1 and extrainfo e1 are consistent. + * Only performs some basic checks. + */ +#define CHECK_EXTRAINFO_CONSISTENCY(r1, e1) \ +STMT_BEGIN \ + tt_assert(r1); \ + tt_assert(e1); \ +\ + tt_str_op(e1->nickname, OP_EQ, r1->nickname); \ +STMT_END + +/** Run unit tests for router descriptor generation logic for a RSA-only + * router. Tor versions without ed25519 (0.2.6 and earlier) are no longer + * officially supported, but the authorities still accept their descriptors. + */ +static void +test_dir_formats_rsa(void *arg) { char *buf = NULL; - char buf2[8192]; - char platform[256]; - char fingerprint[FINGERPRINT_LEN+1]; - char *pk1_str = NULL, *pk2_str = NULL, *cp; - size_t pk1_str_len, pk2_str_len; - routerinfo_t *r1=NULL, *r2=NULL; - crypto_pk_t *pk1 = NULL, *pk2 = NULL; - routerinfo_t *rp1 = NULL, *rp2 = NULL; - addr_policy_t *ex1, *ex2; - routerlist_t *dir1 = NULL, *dir2 = NULL; + char *buf2 = NULL; + char *cp = NULL; + uint8_t *rsa_cc = NULL; - or_options_t *options = get_options_mutable(); - const addr_policy_t *p; - time_t now = time(NULL); - port_cfg_t orport, dirport; - char cert_buf[256]; - (void)arg; - pk1 = pk_generate(0); - pk2 = pk_generate(1); + routerinfo_t *r1 = NULL; + extrainfo_t *e1 = NULL; + routerinfo_t *rp1 = NULL; + extrainfo_t *ep1 = NULL; - tt_assert(pk1 && pk2); + smartlist_t *chunks = NULL; + const char *msg = NULL; + int rv = -1; + + or_options_t *options = get_options_mutable(); + setup_dir_formats_options((const char *)arg, options); hibernate_set_state_for_testing_(HIBERNATE_STATE_LIVE); - get_platform_str(platform, sizeof(platform)); - r1 = tor_malloc_zero(sizeof(routerinfo_t)); - r1->addr = 0xc0a80001u; /* 192.168.0.1 */ - r1->cache_info.published_on = 0; - r1->or_port = 9000; - r1->dir_port = 9003; - r1->supports_tunnelled_dir_requests = 1; - tor_addr_parse(&r1->ipv6_addr, "1:2:3:4::"); - r1->ipv6_orport = 9999; - router_set_rsa_onion_pkey(pk1, &r1->onion_pkey, &r1->onion_pkey_len); - /* Fake just enough of an ntor key to get by */ + /* r1 is a minimal, RSA-only descriptor, with DirPort and IPv6 */ + r1 = basic_routerinfo_new("Magri", 0xc0a80001u /* 192.168.0.1 */, + 9000, 9003, + 1000, 5000, 10000, + 0, + NULL); + + /* Fake just enough of an ntor key to get by */ curve25519_keypair_t r1_onion_keypair; curve25519_keypair_generate(&r1_onion_keypair, 0); r1->onion_curve25519_pkey = tor_memdup(&r1_onion_keypair.pubkey, sizeof(curve25519_public_key_t)); - r1->identity_pkey = crypto_pk_dup_key(pk2); - r1->bandwidthrate = 1000; - r1->bandwidthburst = 5000; - r1->bandwidthcapacity = 10000; + + /* Now add IPv6 */ + tor_addr_parse(&r1->ipv6_addr, "1:2:3:4::"); + r1->ipv6_orport = 9999; + r1->exit_policy = NULL; - r1->nickname = tor_strdup("Magri"); - r1->platform = tor_strdup(platform); - ex1 = tor_malloc_zero(sizeof(addr_policy_t)); - ex2 = tor_malloc_zero(sizeof(addr_policy_t)); - ex1->policy_type = ADDR_POLICY_ACCEPT; - tor_addr_from_ipv4h(&ex1->addr, 0); - ex1->maskbits = 0; - ex1->prt_min = ex1->prt_max = 80; - ex2->policy_type = ADDR_POLICY_REJECT; - tor_addr_from_ipv4h(&ex2->addr, 18<<24); - ex2->maskbits = 8; - ex2->prt_min = ex2->prt_max = 24; - r2 = tor_malloc_zero(sizeof(routerinfo_t)); - r2->addr = 0x0a030201u; /* 10.3.2.1 */ + /* XXXX+++ router_dump_to_string should really take this from ri. */ + options->ContactInfo = tor_strdup("Magri White " + "<magri@elsewhere.example.com>"); + + setup_mock_configured_ports(r1->or_port, r1->dir_port); + + buf = router_dump_router_to_string(r1, r1->identity_pkey, NULL, NULL, NULL); + tt_assert(buf); + + tor_free(options->ContactInfo); + cleanup_mock_configured_ports(); + + /* Synthesise a router descriptor, without the signature */ + chunks = smartlist_new(); + + smartlist_add(chunks, get_new_router_line(r1)); + smartlist_add_strdup(chunks, "or-address [1:2:3:4::]:9999\n"); + + smartlist_add(chunks, get_new_platform_line()); + smartlist_add(chunks, get_new_published_line(r1)); + smartlist_add(chunks, get_new_fingerprint_line(r1)); + + smartlist_add(chunks, get_new_uptime_line(0)); + smartlist_add(chunks, get_new_bandwidth_line(r1)); + + smartlist_add(chunks, get_new_onion_key_block(r1)); + smartlist_add(chunks, get_new_signing_key_block(r1)); + + smartlist_add_strdup(chunks, "hidden-service-dir\n"); + + smartlist_add_strdup(chunks, "contact Magri White " + "<magri@elsewhere.example.com>\n"); + + smartlist_add(chunks, get_new_bridge_distribution_request_line(options)); + smartlist_add(chunks, get_new_ntor_onion_key_line(&r1_onion_keypair.pubkey)); + smartlist_add_strdup(chunks, "reject *:*\n"); + smartlist_add_strdup(chunks, "tunnelled-dir-server\n"); + + smartlist_add_strdup(chunks, "router-signature\n"); + + size_t len_out = 0; + buf2 = smartlist_join_strings(chunks, "", 0, &len_out); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + + tt_assert(len_out > 0); + + buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same + * twice */ + + tt_str_op(buf,OP_EQ, buf2); + tor_free(buf); + + setup_mock_configured_ports(r1->or_port, r1->dir_port); + + buf = router_dump_router_to_string(r1, r1->identity_pkey, NULL, NULL, NULL); + tt_assert(buf); + + cleanup_mock_configured_ports(); + + /* Now, try to parse buf */ + cp = buf; + rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); + + CHECK_ROUTERINFO_CONSISTENCY(r1, rp1); + + tt_assert(rp1->policy_is_reject_star); + + tor_free(buf); + routerinfo_free(rp1); + + /* Test extrainfo creation. + * We avoid calling router_build_fresh_unsigned_routerinfo(), because it's + * too complex. Instead, we re-use the manually-created routerinfos. + */ + + /* Set up standard mocks and data */ + setup_mocks_for_fresh_descriptor(r1, NULL); + + /* router_build_fresh_signed_extrainfo() passes the result of + * get_master_signing_key_cert() directly to tor_cert_dup(), which fails on + * NULL. But we want a NULL ei->cache_info.signing_key_cert to test the + * non-ed key path. + */ + MOCK(tor_cert_dup, mock_tor_cert_dup_null); + + /* Fake just enough of an ORPort and DirPort to get by */ + setup_mock_configured_ports(r1->or_port, r1->dir_port); + + /* Test some of the low-level static functions. */ + e1 = router_build_fresh_signed_extrainfo(r1); + tt_assert(e1); + router_update_routerinfo_from_extrainfo(r1, e1); + rv = router_dump_and_sign_routerinfo_descriptor_body(r1); + tt_assert(rv == 0); + msg = ""; + rv = routerinfo_incompatible_with_extrainfo(r1->identity_pkey, e1, + &r1->cache_info, &msg); + /* If they are incompatible, fail and show the msg string */ + tt_str_op(msg, OP_EQ, ""); + tt_assert(rv == 0); + + /* Now cleanup */ + cleanup_mocks_for_fresh_descriptor(); + + UNMOCK(tor_cert_dup); + + cleanup_mock_configured_ports(); + + CHECK_EXTRAINFO_CONSISTENCY(r1, e1); + + /* Test that the signed ri is parseable */ + tt_assert(r1->cache_info.signed_descriptor_body); + cp = r1->cache_info.signed_descriptor_body; + rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); + + CHECK_ROUTERINFO_CONSISTENCY(r1, rp1); + + tt_assert(rp1->policy_is_reject_star); + + routerinfo_free(rp1); + + /* Test that the signed ei is parseable */ + tt_assert(e1->cache_info.signed_descriptor_body); + cp = e1->cache_info.signed_descriptor_body; + ep1 = extrainfo_parse_entry_from_string((const char*)cp,NULL,1,NULL,NULL); + + CHECK_EXTRAINFO_CONSISTENCY(r1, ep1); + + /* In future tests, we could check the actual extrainfo statistics. */ + + extrainfo_free(ep1); + + done: + dirserv_free_fingerprint_list(); + + tor_free(options->ContactInfo); + tor_free(options->Nickname); + + cleanup_mock_configured_ports(); + cleanup_mocks_for_fresh_descriptor(); + + if (chunks) { + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + } + + routerinfo_free(r1); + routerinfo_free(rp1); + + extrainfo_free(e1); + extrainfo_free(ep1); + + tor_free(rsa_cc); + + tor_free(buf); + tor_free(buf2); +} + +/* Check that the exit policy in rp2 is as expected. */ +#define CHECK_PARSED_EXIT_POLICY(rp2) \ +STMT_BEGIN \ + tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2); \ + \ + p = smartlist_get(rp2->exit_policy, 0); \ + tt_int_op(p->policy_type,OP_EQ, ADDR_POLICY_ACCEPT); \ + tt_assert(tor_addr_is_null(&p->addr)); \ + tt_int_op(p->maskbits,OP_EQ, 0); \ + tt_int_op(p->prt_min,OP_EQ, 80); \ + tt_int_op(p->prt_max,OP_EQ, 80); \ + \ + p = smartlist_get(rp2->exit_policy, 1); \ + tt_int_op(p->policy_type,OP_EQ, ADDR_POLICY_REJECT); \ + tt_assert(tor_addr_eq(&p->addr, &ex2->addr)); \ + tt_int_op(p->maskbits,OP_EQ, 8); \ + tt_int_op(p->prt_min,OP_EQ, 24); \ + tt_int_op(p->prt_max,OP_EQ, 24); \ +STMT_END + +/** Run unit tests for router descriptor generation logic for a RSA + ed25519 + * router. + */ +static void +test_dir_formats_rsa_ed25519(void *arg) +{ + char *buf = NULL; + char *buf2 = NULL; + char *cp = NULL; + + crypto_pk_t *r2_onion_pkey = NULL; + char cert_buf[256]; + uint8_t *rsa_cc = NULL; + time_t now = time(NULL); + + routerinfo_t *r2 = NULL; + extrainfo_t *e2 = NULL; + routerinfo_t *r2_out = NULL; + routerinfo_t *rp2 = NULL; + extrainfo_t *ep2 = NULL; + addr_policy_t *ex1, *ex2; + const addr_policy_t *p; + + smartlist_t *chunks = NULL; + int rv = -1; + + or_options_t *options = get_options_mutable(); + setup_dir_formats_options((const char *)arg, options); + + hibernate_set_state_for_testing_(HIBERNATE_STATE_LIVE); + + /* r2 is a RSA + ed25519 descriptor, with an exit policy, but no DirPort or + * IPv6 */ + r2 = basic_routerinfo_new("Fred", 0x0a030201u /* 10.3.2.1 */, + 9005, 0, + 3000, 3000, 3000, + 5, + &r2_onion_pkey); + + /* Fake just enough of an ntor key to get by */ + curve25519_keypair_t r2_onion_keypair; + curve25519_keypair_generate(&r2_onion_keypair, 0); + r2->onion_curve25519_pkey = tor_memdup(&r2_onion_keypair.pubkey, + sizeof(curve25519_public_key_t)); + + /* Now add relay ed25519 keys + * We can't use init_mock_ed_keys() here, because the keys are seeded */ ed25519_keypair_t kp1, kp2; ed25519_secret_key_from_seed(&kp1.seckey, (const uint8_t*)"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"); @@ -248,157 +950,80 @@ test_dir_formats(void *arg) &kp2.pubkey, now, 86400, CERT_FLAG_INCLUDE_SIGNING_KEY); - r2->platform = tor_strdup(platform); - r2->cache_info.published_on = 5; - r2->or_port = 9005; - r2->dir_port = 0; - r2->supports_tunnelled_dir_requests = 1; - router_set_rsa_onion_pkey(pk2, &r2->onion_pkey, &r2->onion_pkey_len); - curve25519_keypair_t r2_onion_keypair; - curve25519_keypair_generate(&r2_onion_keypair, 0); - r2->onion_curve25519_pkey = tor_memdup(&r2_onion_keypair.pubkey, - sizeof(curve25519_public_key_t)); - r2->identity_pkey = crypto_pk_dup_key(pk1); - r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000; + + /* Now add an exit policy */ + ex1 = tor_malloc_zero(sizeof(addr_policy_t)); + ex2 = tor_malloc_zero(sizeof(addr_policy_t)); + ex1->policy_type = ADDR_POLICY_ACCEPT; + tor_addr_from_ipv4h(&ex1->addr, 0); + ex1->maskbits = 0; + ex1->prt_min = ex1->prt_max = 80; + ex2->policy_type = ADDR_POLICY_REJECT; + tor_addr_from_ipv4h(&ex2->addr, 18<<24); + ex2->maskbits = 8; + ex2->prt_min = ex2->prt_max = 24; + r2->exit_policy = smartlist_new(); smartlist_add(r2->exit_policy, ex1); smartlist_add(r2->exit_policy, ex2); - r2->nickname = tor_strdup("Fred"); - - tt_assert(!crypto_pk_write_public_key_to_string(pk1, &pk1_str, - &pk1_str_len)); - tt_assert(!crypto_pk_write_public_key_to_string(pk2 , &pk2_str, - &pk2_str_len)); - - /* XXXX+++ router_dump_to_string should really take this from ri.*/ - options->ContactInfo = tor_strdup("Magri White " - "<magri@elsewhere.example.com>"); - /* Skip reachability checks for DirPort and tunnelled-dir-server */ - options->AssumeReachable = 1; - - /* Fake just enough of an ORPort and DirPort to get by */ - MOCK(get_configured_ports, mock_get_configured_ports); - mocked_configured_ports = smartlist_new(); - - memset(&orport, 0, sizeof(orport)); - orport.type = CONN_TYPE_OR_LISTENER; - orport.addr.family = AF_INET; - orport.port = 9000; - smartlist_add(mocked_configured_ports, &orport); - - memset(&dirport, 0, sizeof(dirport)); - dirport.type = CONN_TYPE_DIR_LISTENER; - dirport.addr.family = AF_INET; - dirport.port = 9003; - smartlist_add(mocked_configured_ports, &dirport); - buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL); - - UNMOCK(get_configured_ports); - smartlist_free(mocked_configured_ports); - mocked_configured_ports = NULL; + /* Fake just enough of an ORPort to get by */ + setup_mock_configured_ports(r2->or_port, 0); - tor_free(options->ContactInfo); + buf = router_dump_router_to_string(r2, + r2->identity_pkey, r2_onion_pkey, + &r2_onion_keypair, &kp2); tt_assert(buf); - strlcpy(buf2, "router Magri 192.168.0.1 9000 0 9003\n" - "or-address [1:2:3:4::]:9999\n" - "platform Tor "VERSION" on ", sizeof(buf2)); - strlcat(buf2, get_uname(), sizeof(buf2)); - strlcat(buf2, "\n" - "published 1970-01-01 00:00:00\n" - "fingerprint ", sizeof(buf2)); - tt_assert(!crypto_pk_get_fingerprint(pk2, fingerprint, 1)); - strlcat(buf2, fingerprint, sizeof(buf2)); - strlcat(buf2, "\nuptime 0\n" - /* XXX the "0" above is hard-coded, but even if we made it reflect - * uptime, that still wouldn't make it right, because the two - * descriptors might be made on different seconds... hm. */ - "bandwidth 1000 5000 10000\n" - "onion-key\n", sizeof(buf2)); - strlcat(buf2, pk1_str, sizeof(buf2)); - strlcat(buf2, "signing-key\n", sizeof(buf2)); - strlcat(buf2, pk2_str, sizeof(buf2)); - strlcat(buf2, "hidden-service-dir\n", sizeof(buf2)); - strlcat(buf2, "contact Magri White <magri@elsewhere.example.com>\n", - sizeof(buf2)); - strlcat(buf2, "ntor-onion-key ", sizeof(buf2)); - base64_encode(cert_buf, sizeof(cert_buf), - (const char*)r1_onion_keypair.pubkey.public_key, 32, - BASE64_ENCODE_MULTILINE); - strlcat(buf2, cert_buf, sizeof(buf2)); - strlcat(buf2, "reject *:*\n", sizeof(buf2)); - strlcat(buf2, "tunnelled-dir-server\nrouter-signature\n", sizeof(buf2)); - buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same - * twice */ + cleanup_mock_configured_ports(); - tt_str_op(buf,OP_EQ, buf2); - tor_free(buf); + chunks = smartlist_new(); - buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL); - tt_assert(buf); - cp = buf; - rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); - tt_assert(rp1); - tt_int_op(rp1->addr,OP_EQ, r1->addr); - tt_int_op(rp1->or_port,OP_EQ, r1->or_port); - tt_int_op(rp1->dir_port,OP_EQ, r1->dir_port); - tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate); - tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst); - tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity); - crypto_pk_t *onion_pkey = router_get_rsa_onion_pkey(rp1->onion_pkey, - rp1->onion_pkey_len); - tt_int_op(crypto_pk_cmp_keys(onion_pkey, pk1), OP_EQ, 0); - crypto_pk_free(onion_pkey); - tt_int_op(crypto_pk_cmp_keys(rp1->identity_pkey, pk2), OP_EQ, 0); - tt_assert(rp1->supports_tunnelled_dir_requests); - //tt_assert(rp1->exit_policy == NULL); - tor_free(buf); + /* Synthesise a router descriptor, without the signatures */ + smartlist_add(chunks, get_new_router_line(r2)); - strlcpy(buf2, - "router Fred 10.3.2.1 9005 0 0\n" - "identity-ed25519\n" - "-----BEGIN ED25519 CERT-----\n", sizeof(buf2)); + smartlist_add_strdup(chunks, + "identity-ed25519\n" + "-----BEGIN ED25519 CERT-----\n"); base64_encode(cert_buf, sizeof(cert_buf), (const char*)r2->cache_info.signing_key_cert->encoded, r2->cache_info.signing_key_cert->encoded_len, BASE64_ENCODE_MULTILINE); - strlcat(buf2, cert_buf, sizeof(buf2)); - strlcat(buf2, "-----END ED25519 CERT-----\n", sizeof(buf2)); - strlcat(buf2, "master-key-ed25519 ", sizeof(buf2)); + smartlist_add_strdup(chunks, cert_buf); + smartlist_add_strdup(chunks, "-----END ED25519 CERT-----\n"); + + smartlist_add_strdup(chunks, "master-key-ed25519 "); { char k[ED25519_BASE64_LEN+1]; tt_int_op(ed25519_public_to_base64(k, &r2->cache_info.signing_key_cert->signing_key), OP_GE, 0); - strlcat(buf2, k, sizeof(buf2)); - strlcat(buf2, "\n", sizeof(buf2)); + smartlist_add_strdup(chunks, k); + smartlist_add_strdup(chunks, "\n"); } - strlcat(buf2, "platform Tor "VERSION" on ", sizeof(buf2)); - strlcat(buf2, get_uname(), sizeof(buf2)); - strlcat(buf2, "\n" - "published 1970-01-01 00:00:05\n" - "fingerprint ", sizeof(buf2)); - tt_assert(!crypto_pk_get_fingerprint(pk1, fingerprint, 1)); - strlcat(buf2, fingerprint, sizeof(buf2)); - strlcat(buf2, "\nuptime 0\n" - "bandwidth 3000 3000 3000\n", sizeof(buf2)); - strlcat(buf2, "onion-key\n", sizeof(buf2)); - strlcat(buf2, pk2_str, sizeof(buf2)); - strlcat(buf2, "signing-key\n", sizeof(buf2)); - strlcat(buf2, pk1_str, sizeof(buf2)); + + smartlist_add(chunks, get_new_platform_line()); + smartlist_add(chunks, get_new_published_line(r2)); + smartlist_add(chunks, get_new_fingerprint_line(r2)); + + smartlist_add(chunks, get_new_uptime_line(0)); + smartlist_add(chunks, get_new_bandwidth_line(r2)); + + smartlist_add(chunks, get_new_onion_key_block(r2)); + smartlist_add(chunks, get_new_signing_key_block(r2)); + int rsa_cc_len; - rsa_cc = make_tap_onion_key_crosscert(pk2, + rsa_cc = make_tap_onion_key_crosscert(r2_onion_pkey, &kp1.pubkey, - pk1, + r2->identity_pkey, &rsa_cc_len); tt_assert(rsa_cc); base64_encode(cert_buf, sizeof(cert_buf), (char*)rsa_cc, rsa_cc_len, BASE64_ENCODE_MULTILINE); - strlcat(buf2, "onion-key-crosscert\n" - "-----BEGIN CROSSCERT-----\n", sizeof(buf2)); - strlcat(buf2, cert_buf, sizeof(buf2)); - strlcat(buf2, "-----END CROSSCERT-----\n", sizeof(buf2)); + smartlist_add_strdup(chunks, "onion-key-crosscert\n" + "-----BEGIN CROSSCERT-----\n"); + smartlist_add_strdup(chunks, cert_buf); + smartlist_add_strdup(chunks, "-----END CROSSCERT-----\n"); int ntor_cc_sign; { tor_cert_t *ntor_cc = NULL; @@ -413,112 +1038,165 @@ test_dir_formats(void *arg) BASE64_ENCODE_MULTILINE); tor_cert_free(ntor_cc); } - tor_snprintf(buf2+strlen(buf2), sizeof(buf2)-strlen(buf2), + smartlist_add_asprintf(chunks, "ntor-onion-key-crosscert %d\n" "-----BEGIN ED25519 CERT-----\n" "%s" "-----END ED25519 CERT-----\n", ntor_cc_sign, cert_buf); - strlcat(buf2, "hidden-service-dir\n", sizeof(buf2)); - strlcat(buf2, "ntor-onion-key ", sizeof(buf2)); - base64_encode(cert_buf, sizeof(cert_buf), - (const char*)r2_onion_keypair.pubkey.public_key, 32, - BASE64_ENCODE_MULTILINE); - strlcat(buf2, cert_buf, sizeof(buf2)); - strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2)); - strlcat(buf2, "tunnelled-dir-server\n", sizeof(buf2)); - strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2)); + smartlist_add_strdup(chunks, "hidden-service-dir\n"); - /* Fake just enough of an ORPort to get by */ - MOCK(get_configured_ports, mock_get_configured_ports); - mocked_configured_ports = smartlist_new(); + smartlist_add(chunks, get_new_bridge_distribution_request_line(options)); + smartlist_add(chunks, get_new_ntor_onion_key_line(&r2_onion_keypair.pubkey)); + smartlist_add_strdup(chunks, "accept *:80\nreject 18.0.0.0/8:24\n"); + smartlist_add_strdup(chunks, "tunnelled-dir-server\n"); - memset(&orport, 0, sizeof(orport)); - orport.type = CONN_TYPE_OR_LISTENER; - orport.addr.family = AF_INET; - orport.port = 9005; - smartlist_add(mocked_configured_ports, &orport); + smartlist_add_strdup(chunks, "router-sig-ed25519 "); - buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2); - tt_assert(buf); - buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same + size_t len_out = 0; + buf2 = smartlist_join_strings(chunks, "", 0, &len_out); + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + + tt_assert(len_out > 0); + + buf[strlen(buf2)] = '\0'; /* Don't compare either sig; they're never the same * twice */ tt_str_op(buf, OP_EQ, buf2); tor_free(buf); - buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL); + setup_mock_configured_ports(r2->or_port, 0); - UNMOCK(get_configured_ports); - smartlist_free(mocked_configured_ports); - mocked_configured_ports = NULL; + buf = router_dump_router_to_string(r2, r2->identity_pkey, NULL, NULL, NULL); + tt_assert(buf); - /* Reset for later */ + cleanup_mock_configured_ports(); + + /* Now, try to parse buf */ cp = buf; rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); - tt_assert(rp2); - tt_int_op(rp2->addr,OP_EQ, r2->addr); - tt_int_op(rp2->or_port,OP_EQ, r2->or_port); - tt_int_op(rp2->dir_port,OP_EQ, r2->dir_port); - tt_int_op(rp2->bandwidthrate,OP_EQ, r2->bandwidthrate); - tt_int_op(rp2->bandwidthburst,OP_EQ, r2->bandwidthburst); - tt_int_op(rp2->bandwidthcapacity,OP_EQ, r2->bandwidthcapacity); + + CHECK_ROUTERINFO_CONSISTENCY(r2, rp2); + tt_mem_op(rp2->onion_curve25519_pkey->public_key,OP_EQ, r2->onion_curve25519_pkey->public_key, CURVE25519_PUBKEY_LEN); - onion_pkey = router_get_rsa_onion_pkey(rp2->onion_pkey, - rp2->onion_pkey_len); - tt_int_op(crypto_pk_cmp_keys(onion_pkey, pk2), OP_EQ, 0); - crypto_pk_free(onion_pkey); - tt_int_op(crypto_pk_cmp_keys(rp2->identity_pkey, pk1), OP_EQ, 0); - tt_assert(rp2->supports_tunnelled_dir_requests); - - tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2); - - p = smartlist_get(rp2->exit_policy, 0); - tt_int_op(p->policy_type,OP_EQ, ADDR_POLICY_ACCEPT); - tt_assert(tor_addr_is_null(&p->addr)); - tt_int_op(p->maskbits,OP_EQ, 0); - tt_int_op(p->prt_min,OP_EQ, 80); - tt_int_op(p->prt_max,OP_EQ, 80); - - p = smartlist_get(rp2->exit_policy, 1); - tt_int_op(p->policy_type,OP_EQ, ADDR_POLICY_REJECT); - tt_assert(tor_addr_eq(&p->addr, &ex2->addr)); - tt_int_op(p->maskbits,OP_EQ, 8); - tt_int_op(p->prt_min,OP_EQ, 24); - tt_int_op(p->prt_max,OP_EQ, 24); - -#if 0 - /* Okay, now for the directories. */ - { - fingerprint_list = smartlist_new(); - crypto_pk_get_fingerprint(pk2, buf, 1); - add_fingerprint_to_dir(buf, fingerprint_list, 0); - crypto_pk_get_fingerprint(pk1, buf, 1); - add_fingerprint_to_dir(buf, fingerprint_list, 0); + + CHECK_PARSED_EXIT_POLICY(rp2); + + tor_free(buf); + routerinfo_free(rp2); + + /* Test extrainfo creation. */ + + /* Set up standard mocks and data */ + setup_mocks_for_fresh_descriptor(r2, r2_onion_pkey); + + /* router_build_fresh_descriptor() requires + * router_build_fresh_unsigned_routerinfo(), but the implementation is + * too complex. Instead, we re-use r2. + */ + mocked_routerinfo = r2; + MOCK(router_build_fresh_unsigned_routerinfo, + mock_router_build_fresh_unsigned_routerinfo); + + /* r2 uses ed25519, so we need to mock the ed key functions */ + mocked_master_signing_key = &kp2; + MOCK(get_master_signing_keypair, mock_get_master_signing_keypair); + + mocked_signing_key_cert = r2->cache_info.signing_key_cert; + MOCK(get_master_signing_key_cert, mock_get_master_signing_key_cert); + + mocked_curve25519_onion_key = &r2_onion_keypair; + MOCK(get_current_curve25519_keypair, mock_get_current_curve25519_keypair); + + /* Fake just enough of an ORPort to get by */ + setup_mock_configured_ports(r2->or_port, 0); + + /* Test the high-level interface. */ + rv = router_build_fresh_descriptor(&r2_out, &e2); + if (rv < 0) { + /* router_build_fresh_descriptor() frees r2 on failure. */ + r2 = NULL; + /* Get rid of an alias to rp2 */ + r2_out = NULL; } + tt_assert(rv == 0); + tt_assert(r2_out); + tt_assert(e2); + /* Guaranteed by mock_router_build_fresh_unsigned_routerinfo() */ + tt_ptr_op(r2_out, OP_EQ, r2); + /* Get rid of an alias to r2 */ + r2_out = NULL; + + /* Now cleanup */ + cleanup_mocks_for_fresh_descriptor(); + + mocked_routerinfo = NULL; + UNMOCK(router_build_fresh_unsigned_routerinfo); + mocked_master_signing_key = NULL; + UNMOCK(get_master_signing_keypair); + mocked_signing_key_cert = NULL; + UNMOCK(get_master_signing_key_cert); + mocked_curve25519_onion_key = NULL; + UNMOCK(get_current_curve25519_keypair); + + cleanup_mock_configured_ports(); + + CHECK_EXTRAINFO_CONSISTENCY(r2, e2); + + /* Test that the signed ri is parseable */ + tt_assert(r2->cache_info.signed_descriptor_body); + cp = r2->cache_info.signed_descriptor_body; + rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); -#endif /* 0 */ - dirserv_free_fingerprint_list(); + CHECK_ROUTERINFO_CONSISTENCY(r2, rp2); + + tt_mem_op(rp2->onion_curve25519_pkey->public_key,OP_EQ, + r2->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN); + + CHECK_PARSED_EXIT_POLICY(rp2); + + routerinfo_free(rp2); + + /* Test that the signed ei is parseable */ + tt_assert(e2->cache_info.signed_descriptor_body); + cp = e2->cache_info.signed_descriptor_body; + ep2 = extrainfo_parse_entry_from_string((const char*)cp,NULL,1,NULL,NULL); + + CHECK_EXTRAINFO_CONSISTENCY(r2, ep2); + + /* In future tests, we could check the actual extrainfo statistics. */ + + extrainfo_free(ep2); done: - if (r1) - routerinfo_free(r1); - if (r2) - routerinfo_free(r2); - if (rp2) - routerinfo_free(rp2); + dirserv_free_fingerprint_list(); + + tor_free(options->Nickname); + + cleanup_mock_configured_ports(); + cleanup_mocks_for_fresh_descriptor(); + + if (chunks) { + SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); + smartlist_free(chunks); + } + + routerinfo_free(r2); + routerinfo_free(r2_out); + routerinfo_free(rp2); + + extrainfo_free(e2); + extrainfo_free(ep2); tor_free(rsa_cc); + crypto_pk_free(r2_onion_pkey); + tor_free(buf); - tor_free(pk1_str); - tor_free(pk2_str); - if (pk1) crypto_pk_free(pk1); - if (pk2) crypto_pk_free(pk2); - if (rp1) routerinfo_free(rp1); - tor_free(dir1); /* XXXX And more !*/ - tor_free(dir2); /* And more !*/ + tor_free(buf2); } #include "failing_routerdescs.inc" @@ -6546,7 +7224,22 @@ test_dir_format_versions_list(void *arg) struct testcase_t dir_tests[] = { DIR_LEGACY(nicknames), - DIR_LEGACY(formats), + /* extrainfo without any stats */ + DIR_ARG(formats_rsa, TT_FORK, ""), + DIR_ARG(formats_rsa_ed25519, TT_FORK, ""), + /* on a bridge */ + DIR_ARG(formats_rsa, TT_FORK, "b"), + DIR_ARG(formats_rsa_ed25519, TT_FORK, "b"), + /* extrainfo with basic stats */ + DIR_ARG(formats_rsa, TT_FORK, "e"), + DIR_ARG(formats_rsa_ed25519, TT_FORK, "e"), + DIR_ARG(formats_rsa, TT_FORK, "be"), + DIR_ARG(formats_rsa_ed25519, TT_FORK, "be"), + /* extrainfo with all stats */ + DIR_ARG(formats_rsa, TT_FORK, "es"), + DIR_ARG(formats_rsa_ed25519, TT_FORK, "es"), + DIR_ARG(formats_rsa, TT_FORK, "bes"), + DIR_ARG(formats_rsa_ed25519, TT_FORK, "bes"), DIR(routerinfo_parsing, 0), DIR(extrainfo_parsing, 0), DIR(parse_router_list, TT_FORK), diff --git a/src/test/test_dir_common.h b/src/test/test_dir_common.h index d6c5241b14..ab99ed36f4 100644 --- a/src/test/test_dir_common.h +++ b/src/test/test_dir_common.h @@ -3,6 +3,9 @@ * Copyright (c) 2007-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#ifndef TOR_TEST_DIR_COMMON_H +#define TOR_TEST_DIR_COMMON_H + #include "core/or/or.h" #include "feature/nodelist/networkstatus.h" @@ -49,3 +52,4 @@ int dir_common_construct_vote_3(networkstatus_t **vote, networkstatus_t **vote_out, int *n_vrs, time_t now, int clear_rl); +#endif diff --git a/src/test/test_dispatch.c b/src/test/test_dispatch.c new file mode 100644 index 0000000000..d6fe7e781a --- /dev/null +++ b/src/test/test_dispatch.c @@ -0,0 +1,249 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define DISPATCH_PRIVATE + +#include "test/test.h" + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_cfg.h" +#include "lib/dispatch/dispatch_st.h" +#include "lib/dispatch/msgtypes.h" + +#include "lib/log/escape.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include <stdio.h> +#include <string.h> + +static dispatch_t *dispatcher_in_use=NULL; + +/* Construct an empty dispatch_t. */ +static void +test_dispatch_empty(void *arg) +{ + (void)arg; + + dispatch_t *d=NULL; + dispatch_cfg_t *cfg=NULL; + + cfg = dcfg_new(); + d = dispatch_new(cfg); + tt_assert(d); + + done: + dispatch_free(d); + dcfg_free(cfg); +} + +static int total_recv1_simple = 0; +static int total_recv2_simple = 0; + +static void +simple_recv1(const msg_t *m) +{ + total_recv1_simple += m->aux_data__.u64; +} + +static char *recv2_received = NULL; + +static void +simple_recv2(const msg_t *m) +{ + tor_free(recv2_received); + recv2_received = dispatch_fmt_msg_data(dispatcher_in_use, m); + + total_recv2_simple += m->aux_data__.u64*10; +} + +/* Construct a dispatch_t with two messages, make sure that they both get + * delivered. */ +static void +test_dispatch_simple(void *arg) +{ + (void)arg; + + dispatch_t *d=NULL; + dispatch_cfg_t *cfg=NULL; + int r; + + cfg = dcfg_new(); + r = dcfg_msg_set_type(cfg,0,0); + r += dcfg_msg_set_chan(cfg,0,0); + r += dcfg_add_recv(cfg,0,1,simple_recv1); + r += dcfg_msg_set_type(cfg,1,0); + r += dcfg_msg_set_chan(cfg,1,0); + r += dcfg_add_recv(cfg,1,1,simple_recv2); + r += dcfg_add_recv(cfg,1,1,simple_recv2); /* second copy */ + tt_int_op(r, OP_EQ, 0); + + d = dispatch_new(cfg); + tt_assert(d); + dispatcher_in_use = d; + + msg_aux_data_t data = {.u64 = 7}; + r = dispatch_send(d, 99, 0, 0, 0, data); + tt_int_op(r, OP_EQ, 0); + tt_int_op(total_recv1_simple, OP_EQ, 0); + + r = dispatch_flush(d, 0, INT_MAX); + tt_int_op(r, OP_EQ, 0); + tt_int_op(total_recv1_simple, OP_EQ, 7); + tt_int_op(total_recv2_simple, OP_EQ, 0); + + total_recv1_simple = 0; + r = dispatch_send(d, 99, 0, 1, 0, data); + tt_int_op(r, OP_EQ, 0); + r = dispatch_flush(d, 0, INT_MAX); + tt_int_op(total_recv1_simple, OP_EQ, 0); + tt_int_op(total_recv2_simple, OP_EQ, 140); + + tt_str_op(recv2_received, OP_EQ, "<>"); // no format function was set. + + done: + dispatch_free(d); + dcfg_free(cfg); + tor_free(recv2_received); +} + +/* Construct a dispatch_t with a message and no reciever; make sure that it + * gets dropped properly. */ +static void +test_dispatch_no_recipient(void *arg) +{ + (void)arg; + + dispatch_t *d=NULL; + dispatch_cfg_t *cfg=NULL; + int r; + + cfg = dcfg_new(); + r = dcfg_msg_set_type(cfg,0,0); + r += dcfg_msg_set_chan(cfg,0,0); + tt_int_op(r, OP_EQ, 0); + + d = dispatch_new(cfg); + tt_assert(d); + dispatcher_in_use = d; + + msg_aux_data_t data = { .u64 = 7}; + r = dispatch_send(d, 99, 0, 0, 0, data); + tt_int_op(r, OP_EQ, 0); + + r = dispatch_flush(d, 0, INT_MAX); + tt_int_op(r, OP_EQ, 0); + + done: + dispatch_free(d); + dcfg_free(cfg); +} + +struct coord { int x; int y; }; +static void +free_coord(msg_aux_data_t d) +{ + tor_free(d.ptr); +} +static char * +fmt_coord(msg_aux_data_t d) +{ + char *v; + struct coord *c = d.ptr; + tor_asprintf(&v, "[%d, %d]", c->x, c->y); + return v; +} +static dispatch_typefns_t coord_fns = { + .fmt_fn = fmt_coord, + .free_fn = free_coord, +}; +static void +alert_run_immediate(dispatch_t *d, channel_id_t ch, void *arg) +{ + (void)arg; + dispatch_flush(d, ch, INT_MAX); +} + +static char *received_data=NULL; + +static void +recv_typed_data(const msg_t *m) +{ + tor_free(received_data); + received_data = dispatch_fmt_msg_data(dispatcher_in_use, m); +} + +static void +test_dispatch_with_types(void *arg) +{ + (void)arg; + + dispatch_t *d=NULL; + dispatch_cfg_t *cfg=NULL; + int r; + + cfg = dcfg_new(); + r = dcfg_msg_set_type(cfg,5,3); + r += dcfg_msg_set_chan(cfg,5,2); + r += dcfg_add_recv(cfg,5,0,recv_typed_data); + r += dcfg_type_set_fns(cfg,3,&coord_fns); + tt_int_op(r, OP_EQ, 0); + + d = dispatch_new(cfg); + tt_assert(d); + dispatcher_in_use = d; + + /* Make this message get run immediately. */ + r = dispatch_set_alert_fn(d, 2, alert_run_immediate, NULL); + tt_int_op(r, OP_EQ, 0); + + struct coord *xy = tor_malloc(sizeof(*xy)); + xy->x = 13; + xy->y = 37; + msg_aux_data_t data = {.ptr = xy}; + r = dispatch_send(d, 99/*sender*/, 2/*channel*/, 5/*msg*/, 3/*type*/, data); + tt_int_op(r, OP_EQ, 0); + tt_str_op(received_data, OP_EQ, "[13, 37]"); + + done: + dispatch_free(d); + dcfg_free(cfg); + tor_free(received_data); + dispatcher_in_use = NULL; +} + +static void +test_dispatch_bad_type_setup(void *arg) +{ + (void)arg; + static dispatch_typefns_t fns; + dispatch_cfg_t *cfg = dcfg_new(); + + tt_int_op(0, OP_EQ, dcfg_type_set_fns(cfg, 7, &coord_fns)); + + fns = coord_fns; + fns.fmt_fn = NULL; + tt_int_op(-1, OP_EQ, dcfg_type_set_fns(cfg, 7, &fns)); + + fns = coord_fns; + fns.free_fn = NULL; + tt_int_op(-1, OP_EQ, dcfg_type_set_fns(cfg, 7, &fns)); + + fns = coord_fns; + tt_int_op(0, OP_EQ, dcfg_type_set_fns(cfg, 7, &fns)); + + done: + dcfg_free(cfg); +} + +#define T(name) \ + { #name, test_dispatch_ ## name, TT_FORK, NULL, NULL } + +struct testcase_t dispatch_tests[] = { + T(empty), + T(simple), + T(no_recipient), + T(with_types), + T(bad_type_setup), + END_OF_TESTCASES +}; diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c index aeb71ec583..f5d16af921 100644 --- a/src/test/test_extorport.c +++ b/src/test/test_extorport.c @@ -9,7 +9,7 @@ #include "core/mainloop/connection.h" #include "core/or/connection_or.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 "feature/relay/ext_orport.h" #include "core/mainloop/mainloop.h" diff --git a/src/test/test_hs.c b/src/test/test_hs.c index a611b46ca6..aeb3387471 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -6,7 +6,7 @@ * \brief Unit tests for hidden service. **/ -#define CONTROL_PRIVATE +#define CONTROL_EVENTS_PRIVATE #define CIRCUITBUILD_PRIVATE #define RENDCOMMON_PRIVATE #define RENDSERVICE_PRIVATE @@ -15,6 +15,8 @@ #include "core/or/or.h" #include "test/test.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" +#include "feature/control/control_fmt.h" #include "app/config/config.h" #include "feature/hs/hs_common.h" #include "feature/rend/rendcommon.h" diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c index 0c93f593ce..6e00e8807e 100644 --- a/src/test/test_hs_cell.c +++ b/src/test/test_hs_cell.c @@ -39,7 +39,7 @@ test_gen_establish_intro_cell(void *arg) attempt to parse it. */ { /* We only need the auth key pair here. */ - hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0, 0); + hs_service_intro_point_t *ip = service_intro_point_new(NULL); /* Auth key pair is generated in the constructor so we are all set for * using this IP object. */ ret = hs_cell_build_establish_intro(circ_nonce, ip, buf); @@ -107,7 +107,7 @@ test_gen_establish_intro_cell_bad(void *arg) ed25519_sign_prefixed() function and make it fail. */ cell = trn_cell_establish_intro_new(); tt_assert(cell); - ip = service_intro_point_new(NULL, 0, 0); + ip = service_intro_point_new(NULL); cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL); service_intro_point_free(ip); expect_log_msg_containing("Unable to make signature for " diff --git a/src/test/test_hs_client.c b/src/test/test_hs_client.c index 2f2bb45581..8362b6cbda 100644 --- a/src/test/test_hs_client.c +++ b/src/test/test_hs_client.c @@ -403,6 +403,9 @@ test_client_pick_intro(void *arg) /* 2) Mark all intro points except _the chosen one_ as failed. Then query the * desc and get a random intro: check that we got _the chosen one_. */ { + /* Tell hs_get_extend_info_from_lspecs() to skip the private address check. + */ + get_options_mutable()->ExtendAllowPrivateAddresses = 1; /* Pick the chosen intro point and get its ei */ hs_desc_intro_point_t *chosen_intro_point = smartlist_get(desc->encrypted_data.intro_points, 0); @@ -476,6 +479,7 @@ test_client_pick_intro(void *arg) SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, hs_desc_intro_point_t *, ip) { extend_info_t *intro_ei = desc_intro_point_to_extend_info(ip); + tt_assert(intro_ei); if (intro_ei) { const char *ptr; char ip_addr[TOR_ADDR_BUF_LEN]; diff --git a/src/test/test_hs_control.c b/src/test/test_hs_control.c index ba67712f1b..481ef1eb39 100644 --- a/src/test/test_hs_control.c +++ b/src/test/test_hs_control.c @@ -6,11 +6,13 @@ * \brief Unit tests for hidden service control port event and command. **/ -#define CONTROL_PRIVATE +#define CONTROL_EVENTS_PRIVATE #include "core/or/or.h" #include "test/test.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" +#include "feature/control/control_fmt.h" #include "app/config/config.h" #include "feature/hs/hs_common.h" #include "feature/hs/hs_control.h" diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index de584ed47a..09c6c3e700 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -179,115 +179,6 @@ test_descriptor_padding(void *arg) } static void -test_link_specifier(void *arg) -{ - ssize_t ret; - hs_desc_link_specifier_t spec; - smartlist_t *link_specifiers = smartlist_new(); - char buf[256]; - char *b64 = NULL; - link_specifier_t *ls = NULL; - - (void) arg; - - /* Always this port. */ - spec.u.ap.port = 42; - smartlist_add(link_specifiers, &spec); - - /* Test IPv4 for starter. */ - { - uint32_t ipv4; - - spec.type = LS_IPV4; - ret = tor_addr_parse(&spec.u.ap.addr, "1.2.3.4"); - tt_int_op(ret, OP_EQ, AF_INET); - b64 = encode_link_specifiers(link_specifiers); - tt_assert(b64); - - /* Decode it and validate the format. */ - ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, OP_GT, 0); - /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), OP_EQ, 1); - ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); - tt_int_op(ret, OP_EQ, 8); - /* Should be 2 bytes for port and 4 bytes for IPv4. */ - tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, 6); - ipv4 = link_specifier_get_un_ipv4_addr(ls); - tt_int_op(tor_addr_to_ipv4h(&spec.u.ap.addr), OP_EQ, ipv4); - tt_int_op(link_specifier_get_un_ipv4_port(ls), OP_EQ, spec.u.ap.port); - - link_specifier_free(ls); - ls = NULL; - tor_free(b64); - } - - /* Test IPv6. */ - { - uint8_t ipv6[16]; - - spec.type = LS_IPV6; - ret = tor_addr_parse(&spec.u.ap.addr, "[1:2:3:4::]"); - tt_int_op(ret, OP_EQ, AF_INET6); - b64 = encode_link_specifiers(link_specifiers); - tt_assert(b64); - - /* Decode it and validate the format. */ - ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, OP_GT, 0); - /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), OP_EQ, 1); - ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); - tt_int_op(ret, OP_EQ, 20); - /* Should be 2 bytes for port and 16 bytes for IPv6. */ - tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, 18); - for (unsigned int i = 0; i < sizeof(ipv6); i++) { - ipv6[i] = link_specifier_get_un_ipv6_addr(ls, i); - } - tt_mem_op(tor_addr_to_in6_addr8(&spec.u.ap.addr), OP_EQ, ipv6, - sizeof(ipv6)); - tt_int_op(link_specifier_get_un_ipv6_port(ls), OP_EQ, spec.u.ap.port); - - link_specifier_free(ls); - ls = NULL; - tor_free(b64); - } - - /* Test legacy. */ - { - uint8_t *id; - - spec.type = LS_LEGACY_ID; - memset(spec.u.legacy_id, 'Y', sizeof(spec.u.legacy_id)); - b64 = encode_link_specifiers(link_specifiers); - tt_assert(b64); - - /* Decode it and validate the format. */ - ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, OP_GT, 0); - /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), OP_EQ, 1); - ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); - /* 20 bytes digest + 1 byte type + 1 byte len. */ - tt_int_op(ret, OP_EQ, 22); - tt_int_op(link_specifier_getlen_un_legacy_id(ls), OP_EQ, DIGEST_LEN); - /* Digest length is 20 bytes. */ - tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, DIGEST_LEN); - id = link_specifier_getarray_un_legacy_id(ls); - tt_mem_op(spec.u.legacy_id, OP_EQ, id, DIGEST_LEN); - - link_specifier_free(ls); - ls = NULL; - tor_free(b64); - } - - done: - link_specifier_free(ls); - tor_free(b64); - smartlist_free(link_specifiers); -} - -static void test_encode_descriptor(void *arg) { int ret; @@ -932,8 +823,6 @@ struct testcase_t hs_descriptor[] = { /* Encoding tests. */ { "cert_encoding", test_cert_encoding, TT_FORK, NULL, NULL }, - { "link_specifier", test_link_specifier, TT_FORK, - NULL, NULL }, { "encode_descriptor", test_encode_descriptor, TT_FORK, NULL, NULL }, { "descriptor_padding", test_descriptor_padding, TT_FORK, diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index 660f21ffd8..b7163c5c13 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -50,7 +50,7 @@ new_establish_intro_cell(const char *circ_nonce, /* Auth key pair is generated in the constructor so we are all set for * using this IP object. */ - ip = service_intro_point_new(NULL, 0, 0); + ip = service_intro_point_new(NULL); tt_assert(ip); cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf); tt_i64_op(cell_len, OP_GT, 0); @@ -76,7 +76,7 @@ new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out) /* Auth key pair is generated in the constructor so we are all set for * using this IP object. */ - ip = service_intro_point_new(NULL, 0, 0); + ip = service_intro_point_new(NULL); tt_assert(ip); cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out); tt_i64_op(cell_len, OP_GT, 0); diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 43bf894383..57132e6197 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -328,17 +328,18 @@ helper_create_service_with_clients(int num_clients) static hs_service_intro_point_t * helper_create_service_ip(void) { - hs_desc_link_specifier_t *ls; - hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0, 0); + link_specifier_t *ls; + hs_service_intro_point_t *ip = service_intro_point_new(NULL); tor_assert(ip); /* Add a first unused link specifier. */ - ls = tor_malloc_zero(sizeof(*ls)); - ls->type = LS_IPV4; + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); smartlist_add(ip->base.link_specifiers, ls); /* Add a second link specifier used by a test. */ - ls = tor_malloc_zero(sizeof(*ls)); - ls->type = LS_LEGACY_ID; - memset(ls->u.legacy_id, 'A', sizeof(ls->u.legacy_id)); + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memset(link_specifier_getarray_un_legacy_id(ls), 'A', + link_specifier_getlen_un_legacy_id(ls)); smartlist_add(ip->base.link_specifiers, ls); return ip; @@ -811,10 +812,11 @@ test_helper_functions(void *arg) const node_t *node = get_node_from_intro_point(ip); tt_ptr_op(node, OP_EQ, &mock_node); SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, - hs_desc_link_specifier_t *, ls) { - if (ls->type == LS_LEGACY_ID) { + link_specifier_t *, ls) { + if (link_specifier_get_ls_type(ls) == LS_LEGACY_ID) { /* Change legacy id in link specifier which is not the mock node. */ - memset(ls->u.legacy_id, 'B', sizeof(ls->u.legacy_id)); + memset(link_specifier_getarray_un_legacy_id(ls), 'B', + link_specifier_getlen_un_legacy_id(ls)); } } SMARTLIST_FOREACH_END(ls); node = get_node_from_intro_point(ip); diff --git a/src/test/test_keygen.sh b/src/test/test_keygen.sh index 455f9e7d42..9fbf7dd578 100755 --- a/src/test/test_keygen.sh +++ b/src/test/test_keygen.sh @@ -6,14 +6,14 @@ umask 077 set -e -if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then +if [ $# -eq 0 ] || [ ! -f "${1}" ] || [ ! -x "${1}" ]; then if [ "$TESTING_TOR_BINARY" = "" ] ; then echo "Usage: ${0} PATH_TO_TOR [case-number]" exit 1 fi fi -UNAME_OS=`uname -s | cut -d_ -f1` +UNAME_OS=$(uname -s | cut -d_ -f1) if test "$UNAME_OS" = 'CYGWIN' || \ test "$UNAME_OS" = 'MSYS' || \ test "$UNAME_OS" = 'MINGW'; then @@ -64,11 +64,11 @@ dump() { xxd -p "$1" | tr -d '\n '; } die() { echo "$1" >&2 ; exit 5; } check_dir() { [ -d "$1" ] || die "$1 did not exist"; } check_file() { [ -e "$1" ] || die "$1 did not exist"; } -check_no_file() { [ -e "$1" ] && die "$1 was not supposed to exist" || true; } -check_files_eq() { cmp "$1" "$2" || die "$1 and $2 did not match: `dump $1` vs `dump $2`"; } +check_no_file() { if [ -e "$1" ]; then die "$1 was not supposed to exist"; fi } +check_files_eq() { cmp "$1" "$2" || die "$1 and $2 did not match: $(dump "$1") vs $(dump "$2")"; } check_keys_eq() { check_files_eq "${SRC}/keys/${1}" "${ME}/keys/${1}"; } -DATA_DIR=`mktemp -d -t tor_keygen_tests.XXXXXX` +DATA_DIR=$(mktemp -d -t tor_keygen_tests.XXXXXX) if [ -z "$DATA_DIR" ]; then echo "Failure: mktemp invocation returned empty string" >&2 exit 3 @@ -77,10 +77,10 @@ if [ ! -d "$DATA_DIR" ]; then echo "Failure: mktemp invocation result doesn't point to directory" >&2 exit 3 fi -trap "rm -rf '$DATA_DIR'" 0 +trap 'rm -rf "$DATA_DIR"' 0 # Use an absolute path for this or Tor will complain -DATA_DIR=`cd "${DATA_DIR}" && pwd` +DATA_DIR=$(cd "${DATA_DIR}" && pwd) touch "${DATA_DIR}/empty_torrc" @@ -143,7 +143,9 @@ ME="${DATA_DIR}/case2a" SRC="${DATA_DIR}/orig" mkdir -p "${ME}/keys" cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" -${TOR} --DataDirectory "${ME}" --list-fingerprint > "${ME}/stdout" && die "Somehow succeeded when missing secret key, certs: `cat ${ME}/stdout`" || true +if ${TOR} --DataDirectory "${ME}" --list-fingerprint > "${ME}/stdout"; then + die "Somehow succeeded when missing secret key, certs: $(cat "${ME}/stdout")" +fi check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" grep "We needed to load a secret key.*but couldn't find it" "${ME}/stdout" >/dev/null || die "Tor didn't declare that it was missing a secret key" @@ -280,7 +282,9 @@ SRC="${DATA_DIR}/encrypted" mkdir -p "${ME}/keys" cp "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/" cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" -${TOR} --DataDirectory "${ME}" --list-fingerprint > "${ME}/stdout" && die "Tor started with encrypted secret key and no certs" || true +if ${TOR} --DataDirectory "${ME}" --list-fingerprint > "${ME}/stdout"; then + die "Tor started with encrypted secret key and no certs" +fi check_no_file "${ME}/keys/ed25519_signing_cert" check_no_file "${ME}/keys/ed25519_signing_secret_key" @@ -369,7 +373,9 @@ mkdir -p "${ME}/keys" cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" cp "${OTHER}/keys/ed25519_master_id_secret_key" "${ME}/keys/" -${TOR} --DataDirectory "${ME}" --list-fingerprint >"${ME}/stdout" && die "Successfully started with mismatched keys!?" || true +if ${TOR} --DataDirectory "${ME}" --list-fingerprint >"${ME}/stdout"; then + die "Successfully started with mismatched keys!?" +fi grep "public_key does not match.*secret_key" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a key mismatch" @@ -385,7 +391,9 @@ ME="${DATA_DIR}/case11a" mkdir -p "${ME}/keys" -${TOR} --DataDirectory "${ME}" --passphrase-fd 1 > "${ME}/stdout" && die "Successfully started with passphrase-fd but no keygen?" || true +if ${TOR} --DataDirectory "${ME}" --passphrase-fd 1 > "${ME}/stdout"; then + die "Successfully started with passphrase-fd but no keygen?" +fi grep "passphrase-fd specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." @@ -401,7 +409,9 @@ ME="${DATA_DIR}/case11b" mkdir -p "${ME}/keys" -${TOR} --DataDirectory "${ME}" --no-passphrase > "${ME}/stdout" && die "Successfully started with no-passphrase but no keygen?" || true +if ${TOR} --DataDirectory "${ME}" --no-passphrase > "${ME}/stdout"; then + die "Successfully started with no-passphrase but no keygen?" +fi grep "no-passphrase specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." @@ -417,7 +427,9 @@ ME="${DATA_DIR}/case11C" mkdir -p "${ME}/keys" -${TOR} --DataDirectory "${ME}" --newpass > "${ME}/stdout" && die "Successfully started with newpass but no keygen?" || true +if ${TOR} --DataDirectory "${ME}" --newpass > "${ME}/stdout"; then + die "Successfully started with newpass but no keygen?" +fi grep "newpass specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." @@ -455,7 +467,9 @@ ME="${DATA_DIR}/case11E" mkdir -p "${ME}/keys" -${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd ewigeblumenkraft > "${ME}/stdout" && die "Successfully started with bogus passphrase-fd?" || true +if ${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd ewigeblumenkraft > "${ME}/stdout"; then + die "Successfully started with bogus passphrase-fd?" +fi grep "Invalid --passphrase-fd value" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." @@ -472,7 +486,9 @@ ME="${DATA_DIR}/case11F" mkdir -p "${ME}/keys" -${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd 1 --no-passphrase > "${ME}/stdout" && die "Successfully started with bogus passphrase-fd combination?" || true +if ${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd 1 --no-passphrase > "${ME}/stdout"; then + die "Successfully started with bogus passphrase-fd combination?" +fi grep "no-passphrase specified with --passphrase-fd" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." diff --git a/src/test/test_namemap.c b/src/test/test_namemap.c new file mode 100644 index 0000000000..df77d4e2de --- /dev/null +++ b/src/test/test_namemap.c @@ -0,0 +1,174 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "test/test.h" + +#include "lib/cc/torint.h" +#include "lib/container/namemap.h" +#include "lib/container/namemap_st.h" +#include "lib/malloc/malloc.h" + +#include <stdio.h> +#include <string.h> + +static void +test_namemap_empty(void *arg) +{ + (void)arg; + + namemap_t m; + namemap_init(&m); + namemap_t m2 = NAMEMAP_INIT(); + + tt_uint_op(0, OP_EQ, namemap_get_size(&m)); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, "hello")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, "hello")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, "hello128")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, "")); + tt_uint_op(0, OP_EQ, namemap_get_size(&m)); + + tt_uint_op(0, OP_EQ, namemap_get_size(&m2)); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m2, "hello")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m2, "hello")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m2, "hello128")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m2, "")); + tt_uint_op(0, OP_EQ, namemap_get_size(&m)); + + done: + namemap_clear(&m); + namemap_clear(&m2); +} + +static void +test_namemap_toolong(void *arg) +{ + (void)arg; + namemap_t m; + char *ok = NULL; + char *toolong = NULL; + namemap_init(&m); + + ok = tor_malloc_zero(MAX_NAMEMAP_NAME_LEN+1); + memset(ok, 'x', MAX_NAMEMAP_NAME_LEN); + + toolong = tor_malloc_zero(MAX_NAMEMAP_NAME_LEN+2); + memset(toolong, 'x', MAX_NAMEMAP_NAME_LEN+1); + + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, ok)); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, toolong)); + unsigned u1 = namemap_get_or_create_id(&m, toolong); + unsigned u2 = namemap_get_or_create_id(&m, ok); + tt_uint_op(u1, OP_EQ, NAMEMAP_ERR); + tt_uint_op(u2, OP_NE, NAMEMAP_ERR); + tt_uint_op(u2, OP_EQ, namemap_get_id(&m, ok)); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m, toolong)); + + tt_str_op(ok, OP_EQ, namemap_get_name(&m, u2)); + tt_ptr_op(NULL, OP_EQ, namemap_get_name(&m, u1)); + + done: + tor_free(ok); + tor_free(toolong); + namemap_clear(&m); +} + +static void +test_namemap_blackbox(void *arg) +{ + (void)arg; + + namemap_t m1, m2; + namemap_init(&m1); + namemap_init(&m2); + + unsigned u1 = namemap_get_or_create_id(&m1, "hello"); + unsigned u2 = namemap_get_or_create_id(&m1, "world"); + tt_uint_op(u1, OP_NE, NAMEMAP_ERR); + tt_uint_op(u2, OP_NE, NAMEMAP_ERR); + tt_uint_op(u1, OP_NE, u2); + + tt_uint_op(u1, OP_EQ, namemap_get_id(&m1, "hello")); + tt_uint_op(u1, OP_EQ, namemap_get_or_create_id(&m1, "hello")); + tt_uint_op(u2, OP_EQ, namemap_get_id(&m1, "world")); + tt_uint_op(u2, OP_EQ, namemap_get_or_create_id(&m1, "world")); + + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m1, "HELLO")); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m2, "hello")); + + unsigned u3 = namemap_get_or_create_id(&m2, "hola"); + tt_uint_op(u3, OP_NE, NAMEMAP_ERR); + tt_uint_op(NAMEMAP_ERR, OP_EQ, namemap_get_id(&m1, "hola")); + tt_uint_op(u3, OP_EQ, namemap_get_or_create_id(&m2, "hola")); + tt_uint_op(u3, OP_EQ, namemap_get_id(&m2, "hola")); + + unsigned int u4 = namemap_get_or_create_id(&m1, "hola"); + tt_uint_op(u4, OP_NE, NAMEMAP_ERR); + tt_uint_op(u4, OP_EQ, namemap_get_id(&m1, "hola")); + tt_uint_op(u3, OP_EQ, namemap_get_id(&m2, "hola")); + + tt_str_op("hello", OP_EQ, namemap_get_name(&m1, u1)); + tt_str_op("world", OP_EQ, namemap_get_name(&m1, u2)); + tt_str_op("hola", OP_EQ, namemap_get_name(&m2, u3)); + tt_str_op("hola", OP_EQ, namemap_get_name(&m1, u4)); + + tt_ptr_op(NULL, OP_EQ, namemap_get_name(&m2, u3 + 10)); + + done: + namemap_clear(&m1); + namemap_clear(&m2); +} + +static void +test_namemap_internals(void *arg) +{ + (void)arg; + // This test actually assumes know something about the identity layout. + namemap_t m; + namemap_init(&m); + + tt_uint_op(0, OP_EQ, namemap_get_or_create_id(&m, "that")); + tt_uint_op(0, OP_EQ, namemap_get_or_create_id(&m, "that")); + tt_uint_op(1, OP_EQ, namemap_get_or_create_id(&m, "is")); + tt_uint_op(1, OP_EQ, namemap_get_or_create_id(&m, "is")); + + tt_uint_op(0, OP_EQ, namemap_get_id(&m, "that")); + tt_uint_op(0, OP_EQ, namemap_get_id(&m, "that")); + tt_uint_op(1, OP_EQ, namemap_get_id(&m, "is")); + tt_uint_op(2, OP_EQ, namemap_get_or_create_id(&m, "not")); + tt_uint_op(1, OP_EQ, namemap_get_or_create_id(&m, "is")); + tt_uint_op(2, OP_EQ, namemap_get_or_create_id(&m, "not")); + + done: + namemap_clear(&m); +} + +static void +test_namemap_fmt(void *arg) +{ + (void)arg; + namemap_t m = NAMEMAP_INIT(); + + unsigned a = namemap_get_or_create_id(&m, "greetings"); + unsigned b = namemap_get_or_create_id(&m, "earthlings"); + + tt_str_op(namemap_fmt_name(&m, a), OP_EQ, "greetings"); + tt_str_op(namemap_fmt_name(&m, b), OP_EQ, "earthlings"); + tt_int_op(a, OP_NE, 100); + tt_int_op(b, OP_NE, 100); + tt_str_op(namemap_fmt_name(&m, 100), OP_EQ, "{100}"); + + done: + namemap_clear(&m); +} + +#define T(name) \ + { #name, test_namemap_ ## name , 0, NULL, NULL } + +struct testcase_t namemap_tests[] = { + T(empty), + T(toolong), + T(blackbox), + T(internals), + T(fmt), + END_OF_TESTCASES +}; diff --git a/src/test/test_pt.c b/src/test/test_pt.c index d2996f4cc3..87e3ba356c 100644 --- a/src/test/test_pt.c +++ b/src/test/test_pt.c @@ -7,12 +7,13 @@ #define PT_PRIVATE #define UTIL_PRIVATE #define STATEFILE_PRIVATE -#define CONTROL_PRIVATE +#define CONTROL_EVENTS_PRIVATE #define PROCESS_PRIVATE #include "core/or/or.h" #include "app/config/config.h" #include "app/config/confparse.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" #include "feature/client/transports.h" #include "core/or/circuitbuild.h" #include "app/config/statefile.h" diff --git a/src/test/test_ptr_slow.c b/src/test/test_ptr_slow.c new file mode 100644 index 0000000000..76bdbf1891 --- /dev/null +++ b/src/test/test_ptr_slow.c @@ -0,0 +1,106 @@ +/* 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 */ + +#include "orconfig.h" +#include "core/or/or.h" +#include "test/test.h" +#include "test/ptr_helpers.h" + +#include <stdint.h> +#include <limits.h> + +/** Assert that <b>a</b> can be cast to void * and back. */ +static void +assert_int_voidptr_roundtrip(int a) +{ + intptr_t ap = (intptr_t)a; + void *b = cast_intptr_to_voidstar(ap); + intptr_t c = cast_voidstar_to_intptr(b); + void *d = cast_intptr_to_voidstar(c); + + tt_assert(ap == c); + tt_assert(b == d); + + done: + return; +} + +/** Test for possibility of casting `int` to `void *` and back. */ +static void +test_int_voidstar_interop(void *arg) +{ + int a; + (void)arg; + + for (a = -1024; a <= 1024; a++) { + assert_int_voidptr_roundtrip(a); + } + + for (a = INT_MIN; a <= INT_MIN+1024; a++) { + assert_int_voidptr_roundtrip(a); + } + + for (a = INT_MAX-1024; a < INT_MAX; a++) { + assert_int_voidptr_roundtrip(a); + } + + a = 1; + for (unsigned long i = 0; i < sizeof(int) * 8; i++) { + assert_int_voidptr_roundtrip(a); + a = (a << 1); + } +} + +/** Assert that <b>a</b> can be cast to void * and back. */ +static void +assert_uint_voidptr_roundtrip(unsigned int a) +{ + uintptr_t ap = (uintptr_t)a; + void *b = cast_uintptr_to_voidstar(ap); + uintptr_t c = cast_voidstar_to_uintptr(b); + void *d = cast_uintptr_to_voidstar(c); + + tt_assert(ap == c); + tt_assert(b == d); + + done: + return; +} + +/** Test for possibility of casting `int` to `void *` and back. */ +static void +test_uint_voidstar_interop(void *arg) +{ + unsigned int a; + (void)arg; + + for (a = 0; a <= 1024; a++) { + assert_uint_voidptr_roundtrip(a); + } + + for (a = UINT_MAX-1024; a < UINT_MAX; a++) { + assert_uint_voidptr_roundtrip(a); + } + + a = 1; + for (unsigned long i = 0; i < sizeof(int) * 8; i++) { + assert_uint_voidptr_roundtrip(a); + a = (a << 1); + } +} + +struct testcase_t slow_ptr_tests[] = { + { .name = "int_voidstar_interop", + .fn = test_int_voidstar_interop, + .flags = 0, + .setup = NULL, + .setup_data = NULL }, + { .name = "uint_voidstar_interop", + .fn = test_uint_voidstar_interop, + .flags = 0, + .setup = NULL, + .setup_data = NULL }, + END_OF_TESTCASES +}; diff --git a/src/test/test_pubsub_build.c b/src/test/test_pubsub_build.c new file mode 100644 index 0000000000..ce5bf60080 --- /dev/null +++ b/src/test/test_pubsub_build.c @@ -0,0 +1,621 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define DISPATCH_PRIVATE +#define PUBSUB_PRIVATE + +#include "test/test.h" + +#include "lib/cc/torint.h" +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/dispatch_st.h" +#include "lib/dispatch/msgtypes.h" +#include "lib/pubsub/pubsub_macros.h" +#include "lib/pubsub/pubsub_build.h" +#include "lib/pubsub/pubsub_builder_st.h" + +#include "lib/log/escape.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include "test/log_test_helpers.h" + +#include <stdio.h> +#include <string.h> + +static char * +ex_int_fmt(msg_aux_data_t aux) +{ + int val = (int) aux.u64; + char *r=NULL; + tor_asprintf(&r, "%d", val); + return r; +} + +static char * +ex_str_fmt(msg_aux_data_t aux) +{ + return esc_for_log(aux.ptr); +} + +static void +ex_str_free(msg_aux_data_t aux) +{ + tor_free_(aux.ptr); +} + +static dispatch_typefns_t intfns = { + .fmt_fn = ex_int_fmt +}; + +static dispatch_typefns_t stringfns = { + .free_fn = ex_str_free, + .fmt_fn = ex_str_fmt +}; + +DECLARE_MESSAGE_INT(bunch_of_coconuts, int, int); +DECLARE_PUBLISH(bunch_of_coconuts); +DECLARE_SUBSCRIBE(bunch_of_coconuts, coconut_recipient_cb); + +DECLARE_MESSAGE(yes_we_have_no, string, char *); +DECLARE_PUBLISH(yes_we_have_no); +DECLARE_SUBSCRIBE(yes_we_have_no, absent_item_cb); + +static void +coconut_recipient_cb(const msg_t *m, int n_coconuts) +{ + (void)m; + (void)n_coconuts; +} + +static void +absent_item_cb(const msg_t *m, const char *fruitname) +{ + (void)m; + (void)fruitname; +} + +#define FLAG_SKIP 99999 + +static void +seed_dispatch_builder(pubsub_builder_t *b, + unsigned fl1, unsigned fl2, unsigned fl3, unsigned fl4) +{ + pubsub_connector_t *c = NULL; + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1")); + DISPATCH_REGISTER_TYPE(c, int, &intfns); + if (fl1 != FLAG_SKIP) + DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, fl1); + if (fl2 != FLAG_SKIP) + DISPATCH_ADD_SUB_(c, main, yes_we_have_no, fl2); + pubsub_connector_free(c); + } + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2")); + DISPATCH_REGISTER_TYPE(c, string, &stringfns); + if (fl3 != FLAG_SKIP) + DISPATCH_ADD_PUB_(c, main, yes_we_have_no, fl3); + if (fl4 != FLAG_SKIP) + DISPATCH_ADD_SUB_(c, main, bunch_of_coconuts, fl4); + pubsub_connector_free(c); + } +} + +static void +seed_pubsub_builder_basic(pubsub_builder_t *b) +{ + seed_dispatch_builder(b, 0, 0, 0, 0); +} + +/* Regular builder with valid types and messages. + */ +static void +test_pubsub_build_types_ok(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + pubsub_items_t *items = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + + dispatcher = pubsub_builder_finalize(b, &items); + b = NULL; + tt_assert(dispatcher); + tt_assert(items); + tt_int_op(smartlist_len(items->items), OP_EQ, 4); + + // Make sure that the bindings got build correctly. + SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) { + if (item->is_publish) { + tt_assert(item->pub_binding); + tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, dispatcher); + } + } SMARTLIST_FOREACH_END(item); + + tt_int_op(dispatcher->n_types, OP_GE, 2); + tt_assert(dispatcher->typefns); + + tt_assert(dispatcher->typefns[get_msg_type_id("int")].fmt_fn == ex_int_fmt); + tt_assert(dispatcher->typefns[get_msg_type_id("string")].fmt_fn == + ex_str_fmt); + + // Now clear the bindings, like we would do before freeing the + // the dispatcher. + pubsub_items_clear_bindings(items); + SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) { + if (item->is_publish) { + tt_assert(item->pub_binding); + tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, NULL); + } + } SMARTLIST_FOREACH_END(item); + + done: + pubsub_connector_free(c); + pubsub_builder_free(b); + dispatch_free(dispatcher); + pubsub_items_free(items); +} + +/* We fail if the same type is defined in two places with different functions. + */ +static void +test_pubsub_build_types_decls_conflict(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); + // Extra declaration of int: we don't allow this. + DISPATCH_REGISTER_TYPE(c, int, &stringfns); + pubsub_connector_free(c); + } + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + // expect_log_msg_containing("(int) declared twice"); // XXXX + + done: + pubsub_connector_free(c); + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* If a message ID exists but nobody is publishing or subscribing to it, + * that's okay. */ +static void +test_pubsub_build_unused_message(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + + // This message isn't actually generated by anyone, but that will be fine: + // we just log it at info. + get_message_id("unused"); + setup_capture_of_logs(LOG_INFO); + + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher); + expect_log_msg_containing( + "Nobody is publishing or subscribing to message"); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* Publishing or subscribing to a message with no subscribers / publishers + * should fail and warn. */ +static void +test_pubsub_build_missing_pubsub(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + + b = pubsub_builder_new(); + seed_dispatch_builder(b, 0, 0, FLAG_SKIP, FLAG_SKIP); + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing( + "Message \"bunch_of_coconuts\" has publishers, but no subscribers."); + expect_log_msg_containing( + "Message \"yes_we_have_no\" has subscribers, but no publishers."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* Make sure that a stub publisher or subscriber prevents an error from + * happening even if there are no other publishers/subscribers for a message + */ +static void +test_pubsub_build_stub_pubsub(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + + b = pubsub_builder_new(); + seed_dispatch_builder(b, 0, 0, DISP_FLAG_STUB, DISP_FLAG_STUB); + + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher); + + // 1 subscriber. + tt_int_op(1, OP_EQ, + dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); + // no subscribers + tt_ptr_op(NULL, OP_EQ, + dispatcher->table[get_message_id("bunch_of_coconuts")]); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); +} + +/* Only one channel per msg id. */ +static void +test_pubsub_build_channels_conflict(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + pub_binding_t btmp; + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("problems")); + /* Usually the DISPATCH_ADD_PUB macro would keep us from using + * the wrong channel */ + pubsub_add_pub_(c, &btmp, get_channel_id("hithere"), + get_message_id("bunch_of_coconuts"), + get_msg_type_id("int"), + 0 /* flags */, + "somewhere.c", 22); + pubsub_connector_free(c); + }; + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated " + "with multiple inconsistent channels."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* Only one type per msg id. */ +static void +test_pubsub_build_types_conflict(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + pub_binding_t btmp; + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("problems")); + /* Usually the DISPATCH_ADD_PUB macro would keep us from using + * the wrong channel */ + pubsub_add_pub_(c, &btmp, get_channel_id("hithere"), + get_message_id("bunch_of_coconuts"), + get_msg_type_id("string"), + 0 /* flags */, + "somewhere.c", 22); + pubsub_connector_free(c); + }; + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated " + "with multiple inconsistent message types."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* The same module can't publish and subscribe the same message */ +static void +test_pubsub_build_pubsub_same(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1")); + // already publishing this. + DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); + pubsub_connector_free(c); + }; + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing("Message \"bunch_of_coconuts\" is published " + "and subscribed by the same subsystem \"sys1\"."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* More than one subsystem may publish or subscribe, and that's okay. */ +static void +test_pubsub_build_pubsub_multi(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + pub_binding_t btmp; + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); + DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); + pubsub_add_pub_(c, &btmp, get_channel_id("main"), + get_message_id("yes_we_have_no"), + get_msg_type_id("string"), + 0 /* flags */, + "somewhere.c", 22); + pubsub_connector_free(c); + }; + + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher); + + // 1 subscribers + tt_int_op(1, OP_EQ, + dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); + // 2 subscribers. + dtbl_entry_t *ent = + dispatcher->table[get_message_id("bunch_of_coconuts")]; + tt_int_op(2, OP_EQ, ent->n_enabled); + tt_int_op(2, OP_EQ, ent->n_fns); + tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts); + tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); +} + +static void +some_other_coconut_hook(const msg_t *m) +{ + (void)m; +} + +/* Subscribe hooks should be build correctly when there are a bunch of + * them. */ +static void +test_pubsub_build_sub_many(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + char *sysname = NULL; + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + + int i; + for (i = 1; i < 100; ++i) { + tor_asprintf(&sysname, "system%d",i); + c = pubsub_connector_for_subsystem(b, get_subsys_id(sysname)); + if (i % 7) { + DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); + } else { + pubsub_add_sub_(c, some_other_coconut_hook, + get_channel_id("main"), + get_message_id("bunch_of_coconuts"), + get_msg_type_id("int"), + 0 /* flags */, + "somewhere.c", 22); + } + pubsub_connector_free(c); + tor_free(sysname); + }; + + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher); + + dtbl_entry_t *ent = + dispatcher->table[get_message_id("bunch_of_coconuts")]; + tt_int_op(100, OP_EQ, ent->n_enabled); + tt_int_op(100, OP_EQ, ent->n_fns); + tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts); + tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts); + tt_ptr_op(ent->rcv[76].fn, OP_EQ, recv_fn__bunch_of_coconuts); + tt_ptr_op(ent->rcv[77].fn, OP_EQ, some_other_coconut_hook); + tt_ptr_op(ent->rcv[78].fn, OP_EQ, recv_fn__bunch_of_coconuts); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + tor_free(sysname); +} + +/* The same subsystem can only declare one publish or subscribe. */ +static void +test_pubsub_build_pubsub_redundant(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_pubsub_builder_basic(b); + pub_binding_t btmp; + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2")); + DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); + pubsub_add_pub_(c, &btmp, get_channel_id("main"), + get_message_id("yes_we_have_no"), + get_msg_type_id("string"), + 0 /* flags */, + "somewhere.c", 22); + pubsub_connector_free(c); + }; + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing( + "Message \"yes_we_have_no\" is configured to be published by " + "subsystem \"sys2\" more than once."); + expect_log_msg_containing( + "Message \"bunch_of_coconuts\" is configured to be subscribed by " + "subsystem \"sys2\" more than once."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +/* It's fine to declare the excl flag. */ +static void +test_pubsub_build_excl_ok(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + + b = pubsub_builder_new(); + // Try one excl/excl pair and one excl/non pair. + seed_dispatch_builder(b, DISP_FLAG_EXCL, 0, + DISP_FLAG_EXCL, DISP_FLAG_EXCL); + + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher); + + // 1 subscribers + tt_int_op(1, OP_EQ, + dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); + // 1 subscriber. + tt_int_op(1, OP_EQ, + dispatcher->table[get_message_id("bunch_of_coconuts")]->n_enabled); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); +} + +/* but if you declare the excl flag, you need to mean it. */ +static void +test_pubsub_build_excl_bad(void *arg) +{ + (void)arg; + pubsub_builder_t *b = NULL; + dispatch_t *dispatcher = NULL; + pubsub_connector_t *c = NULL; + + b = pubsub_builder_new(); + seed_dispatch_builder(b, DISP_FLAG_EXCL, DISP_FLAG_EXCL, + 0, 0); + + { + c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); + DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, 0); + DISPATCH_ADD_SUB_(c, main, yes_we_have_no, 0); + pubsub_connector_free(c); + }; + + setup_full_capture_of_logs(LOG_WARN); + dispatcher = pubsub_builder_finalize(b, NULL); + b = NULL; + tt_assert(dispatcher == NULL); + + expect_log_msg_containing("has multiple publishers, but at least one is " + "marked as exclusive."); + expect_log_msg_containing("has multiple subscribers, but at least one is " + "marked as exclusive."); + + done: + pubsub_builder_free(b); + dispatch_free(dispatcher); + teardown_capture_of_logs(); +} + +#define T(name, flags) \ + { #name, test_pubsub_build_ ## name , (flags), NULL, NULL } + +struct testcase_t pubsub_build_tests[] = { + T(types_ok, TT_FORK), + T(types_decls_conflict, TT_FORK), + T(unused_message, TT_FORK), + T(missing_pubsub, TT_FORK), + T(stub_pubsub, TT_FORK), + T(channels_conflict, TT_FORK), + T(types_conflict, TT_FORK), + T(pubsub_same, TT_FORK), + T(pubsub_multi, TT_FORK), + T(sub_many, TT_FORK), + T(pubsub_redundant, TT_FORK), + T(excl_ok, TT_FORK), + T(excl_bad, TT_FORK), + END_OF_TESTCASES +}; diff --git a/src/test/test_pubsub_msg.c b/src/test/test_pubsub_msg.c new file mode 100644 index 0000000000..73c7c9f540 --- /dev/null +++ b/src/test/test_pubsub_msg.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define DISPATCH_PRIVATE + +#include "test/test.h" + +#include "lib/dispatch/dispatch.h" +#include "lib/dispatch/dispatch_naming.h" +#include "lib/dispatch/dispatch_st.h" +#include "lib/dispatch/msgtypes.h" +#include "lib/pubsub/pubsub_flags.h" +#include "lib/pubsub/pub_binding_st.h" +#include "lib/pubsub/pubsub_build.h" +#include "lib/pubsub/pubsub_builder_st.h" +#include "lib/pubsub/pubsub_connect.h" +#include "lib/pubsub/pubsub_publish.h" + +#include "lib/log/escape.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include <stdio.h> +#include <string.h> + +static char * +ex_str_fmt(msg_aux_data_t aux) +{ + return esc_for_log(aux.ptr); +} +static void +ex_str_free(msg_aux_data_t aux) +{ + tor_free_(aux.ptr); +} +static dispatch_typefns_t stringfns = { + .free_fn = ex_str_free, + .fmt_fn = ex_str_fmt +}; + +// We're using the lowest-level publish/subscribe logic here, to avoid the +// pubsub_macros.h macros and just test the dispatch core. We'll use a string +// type for everything. + +#define DECLARE_MESSAGE(suffix) \ + static pub_binding_t pub_binding_##suffix; \ + static int msg_received_##suffix = 0; \ + static void recv_msg_##suffix(const msg_t *m) { \ + (void)m; \ + ++msg_received_##suffix; \ + } \ + EAT_SEMICOLON + +#define ADD_PUBLISH(binding_suffix, subsys, channel, msg, flags) \ + STMT_BEGIN { \ + con = pubsub_connector_for_subsystem(builder, \ + get_subsys_id(#subsys)); \ + pubsub_add_pub_(con, &pub_binding_##binding_suffix, \ + get_channel_id(#channel), \ + get_message_id(#msg), get_msg_type_id("string"), \ + (flags), __FILE__, __LINE__); \ + pubsub_connector_free(con); \ + } STMT_END + +#define ADD_SUBSCRIBE(hook_suffix, subsys, channel, msg, flags) \ + STMT_BEGIN { \ + con = pubsub_connector_for_subsystem(builder, \ + get_subsys_id(#subsys)); \ + pubsub_add_sub_(con, recv_msg_##hook_suffix, \ + get_channel_id(#channel), \ + get_message_id(#msg), get_msg_type_id("string"), \ + (flags), __FILE__, __LINE__); \ + pubsub_connector_free(con); \ + } STMT_END + +#define SEND(binding_suffix, val) \ + STMT_BEGIN { \ + msg_aux_data_t data_; \ + data_.ptr = tor_strdup(val); \ + pubsub_pub_(&pub_binding_##binding_suffix, data_); \ + } STMT_END + +DECLARE_MESSAGE(msg1); +DECLARE_MESSAGE(msg2); +DECLARE_MESSAGE(msg3); +DECLARE_MESSAGE(msg4); +DECLARE_MESSAGE(msg5); + +static smartlist_t *strings_received = NULL; +static void +recv_msg_copy_string(const msg_t *m) +{ + const char *s = m->aux_data__.ptr; + smartlist_add(strings_received, tor_strdup(s)); +} + +static void * +setup_dispatcher(const struct testcase_t *testcase) +{ + (void)testcase; + pubsub_builder_t *builder = pubsub_builder_new(); + pubsub_connector_t *con; + + { + con = pubsub_connector_for_subsystem(builder, get_subsys_id("types")); + pubsub_connector_register_type_(con, + get_msg_type_id("string"), + &stringfns, + "nowhere.c", 99); + pubsub_connector_free(con); + } + // message1 has one publisher and one subscriber. + ADD_PUBLISH(msg1, sys1, main, message1, 0); + ADD_SUBSCRIBE(msg1, sys2, main, message1, 0); + + // message2 has a publisher and a stub subscriber. + ADD_PUBLISH(msg2, sys1, main, message2, 0); + ADD_SUBSCRIBE(msg2, sys2, main, message2, DISP_FLAG_STUB); + + // message3 has a publisher and three subscribers. + ADD_PUBLISH(msg3, sys1, main, message3, 0); + ADD_SUBSCRIBE(msg3, sys2, main, message3, 0); + ADD_SUBSCRIBE(msg3, sys3, main, message3, 0); + ADD_SUBSCRIBE(msg3, sys4, main, message3, 0); + + // message4 has one publisher and two subscribers, but it's on another + // channel. + ADD_PUBLISH(msg4, sys2, other, message4, 0); + ADD_SUBSCRIBE(msg4, sys1, other, message4, 0); + ADD_SUBSCRIBE(msg4, sys3, other, message4, 0); + + // message5 has a huge number of recipients. + ADD_PUBLISH(msg5, sys3, main, message5, 0); + ADD_SUBSCRIBE(msg5, sys4, main, message5, 0); + ADD_SUBSCRIBE(msg5, sys5, main, message5, 0); + ADD_SUBSCRIBE(msg5, sys6, main, message5, 0); + ADD_SUBSCRIBE(msg5, sys7, main, message5, 0); + ADD_SUBSCRIBE(msg5, sys8, main, message5, 0); + for (int i = 0; i < 1000-5; ++i) { + char *sys; + tor_asprintf(&sys, "xsys-%d", i); + con = pubsub_connector_for_subsystem(builder, get_subsys_id(sys)); + pubsub_add_sub_(con, recv_msg_copy_string, + get_channel_id("main"), + get_message_id("message5"), + get_msg_type_id("string"), 0, "here", 100); + pubsub_connector_free(con); + tor_free(sys); + } + + return pubsub_builder_finalize(builder, NULL); +} + +static int +cleanup_dispatcher(const struct testcase_t *testcase, void *dispatcher_) +{ + (void)testcase; + dispatch_t *dispatcher = dispatcher_; + dispatch_free(dispatcher); + return 1; +} + +static const struct testcase_setup_t dispatcher_setup = { + setup_dispatcher, cleanup_dispatcher +}; + +static void +test_pubsub_msg_minimal(void *arg) +{ + dispatch_t *d = arg; + + tt_int_op(0, OP_EQ, msg_received_msg1); + SEND(msg1, "hello world"); + tt_int_op(0, OP_EQ, msg_received_msg1); // hasn't actually arrived yet. + + tt_int_op(0, OP_EQ, dispatch_flush(d, get_channel_id("main"), 1000)); + tt_int_op(1, OP_EQ, msg_received_msg1); // we got the message! + + done: + ; +} + +static void +test_pubsub_msg_send_to_stub(void *arg) +{ + dispatch_t *d = arg; + + tt_int_op(0, OP_EQ, msg_received_msg2); + SEND(msg2, "hello silence"); + tt_int_op(0, OP_EQ, msg_received_msg2); // hasn't actually arrived yet. + + tt_int_op(0, OP_EQ, dispatch_flush(d, get_channel_id("main"), 1000)); + tt_int_op(0, OP_EQ, msg_received_msg2); // doesn't arrive -- stub hook. + + done: + ; +} + +static void +test_pubsub_msg_cancel_msgs(void *arg) +{ + dispatch_t *d = arg; + + tt_int_op(0, OP_EQ, msg_received_msg1); + for (int i = 0; i < 100; ++i) { + SEND(msg1, "hello world"); + } + tt_int_op(0, OP_EQ, msg_received_msg1); // hasn't actually arrived yet. + + tt_int_op(0, OP_EQ, dispatch_flush(d, get_channel_id("main"), 10)); + tt_int_op(10, OP_EQ, msg_received_msg1); // we got the message 10 times. + + // At this point, the dispatcher will be freed with queued, undelivered + // messages. + done: + ; +} + +struct alertfn_target { + dispatch_t *d; + channel_id_t ch; + int count; +}; +static void +alertfn_generic(dispatch_t *d, channel_id_t ch, void *arg) +{ + struct alertfn_target *t = arg; + tt_ptr_op(d, OP_EQ, t->d); + tt_int_op(ch, OP_EQ, t->ch); + ++t->count; + done: + ; +} + +static void +test_pubsub_msg_alertfns(void *arg) +{ + dispatch_t *d = arg; + struct alertfn_target ch1_a = { d, get_channel_id("main"), 0 }; + struct alertfn_target ch2_a = { d, get_channel_id("other"), 0 }; + + tt_int_op(0, OP_EQ, + dispatch_set_alert_fn(d, get_channel_id("main"), + alertfn_generic, &ch1_a)); + tt_int_op(0, OP_EQ, + dispatch_set_alert_fn(d, get_channel_id("other"), + alertfn_generic, &ch2_a)); + + SEND(msg3, "hello"); + tt_int_op(ch1_a.count, OP_EQ, 1); + SEND(msg3, "world"); + tt_int_op(ch1_a.count, OP_EQ, 1); // only the first message sends an alert + tt_int_op(ch2_a.count, OP_EQ, 0); // no alert for 'other' + + SEND(msg4, "worse things happen in C"); + tt_int_op(ch2_a.count, OP_EQ, 1); + + // flush the first (main) channel... + tt_int_op(0, OP_EQ, dispatch_flush(d, get_channel_id("main"), 1000)); + tt_int_op(6, OP_EQ, msg_received_msg3); // 3 subscribers, 2 instances. + + // now that the main channel is flushed, sending another message on it + // starts another alert. + tt_int_op(ch1_a.count, OP_EQ, 1); + SEND(msg1, "plover"); + tt_int_op(ch1_a.count, OP_EQ, 2); + tt_int_op(ch2_a.count, OP_EQ, 1); + + done: + ; +} + +/* try more than N_FAST_FNS hooks on msg5 */ +static void +test_pubsub_msg_many_hooks(void *arg) +{ + dispatch_t *d = arg; + strings_received = smartlist_new(); + + tt_int_op(0, OP_EQ, msg_received_msg5); + SEND(msg5, "hello world"); + tt_int_op(0, OP_EQ, msg_received_msg5); + tt_int_op(0, OP_EQ, smartlist_len(strings_received)); + + tt_int_op(0, OP_EQ, dispatch_flush(d, get_channel_id("main"), 100000)); + tt_int_op(5, OP_EQ, msg_received_msg5); + tt_int_op(995, OP_EQ, smartlist_len(strings_received)); + + done: + SMARTLIST_FOREACH(strings_received, char *, s, tor_free(s)); + smartlist_free(strings_received); +} + +#define T(name) \ + { #name, test_pubsub_msg_ ## name , TT_FORK, \ + &dispatcher_setup, NULL } + +struct testcase_t pubsub_msg_tests[] = { + T(minimal), + T(send_to_stub), + T(cancel_msgs), + T(alertfns), + T(many_hooks), + END_OF_TESTCASES +}; diff --git a/src/test/test_rebind.sh b/src/test/test_rebind.sh index 498072de35..a8f07c7c1e 100755 --- a/src/test/test_rebind.sh +++ b/src/test/test_rebind.sh @@ -15,10 +15,15 @@ fi exitcode=0 tmpdir= -clean () { test -n "$tmpdir" && test -d "$tmpdir" && rm -rf "$tmpdir" || :; } +clean () { + if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then + rm -rf "$tmpdir" + fi +} + trap clean EXIT HUP INT TERM -tmpdir="`mktemp -d -t tor_rebind_test.XXXXXX`" +tmpdir="$(mktemp -d -t tor_rebind_test.XXXXXX)" if [ -z "$tmpdir" ]; then echo >&2 mktemp failed exit 2 diff --git a/src/test/test_router.c b/src/test/test_router.c index ea0ee3e84c..5477ab51e9 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -100,6 +100,9 @@ test_router_dump_router_to_string_no_bridge_distribution_method(void *arg) router = (routerinfo_t*)router_get_my_routerinfo(); tt_ptr_op(router, !=, NULL); + /* The real router_get_my_routerinfo() looks up onion_curve25519_pkey using + * get_current_curve25519_keypair(), but we don't initialise static data in + * this test. */ router->onion_curve25519_pkey = &ntor_keypair.pubkey; /* Generate our server descriptor and ensure that the substring diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index 727fa5660f..102d9334a1 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -455,11 +455,11 @@ test_routerkeys_ed_keys_init_all(void *arg) options->TestingLinkKeySlop = 2*3600; #ifdef _WIN32 - mkdir(dir); - mkdir(keydir); + tt_int_op(0, OP_EQ, mkdir(dir)); + tt_int_op(0, OP_EQ, mkdir(keydir)); #else - mkdir(dir, 0700); - mkdir(keydir, 0700); + tt_int_op(0, OP_EQ, mkdir(dir, 0700)); + tt_int_op(0, OP_EQ, mkdir(keydir, 0700)); #endif /* defined(_WIN32) */ options->DataDirectory = dir; diff --git a/src/test/test_rust.sh b/src/test/test_rust.sh index 00b3e88d37..804d2ada36 100755 --- a/src/test/test_rust.sh +++ b/src/test/test_rust.sh @@ -14,11 +14,12 @@ rustc_host=$(rustc -vV | grep host | sed 's/host: //') for cargo_toml_dir in "${abs_top_srcdir:-../../..}"/src/rust/*; do if [ -e "${cargo_toml_dir}/Cargo.toml" ]; then + # shellcheck disable=SC2086 cd "${abs_top_builddir:-../../..}/src/rust" && \ CARGO_TARGET_DIR="${abs_top_builddir:-../../..}/src/rust/target" \ - "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} \ + "${CARGO:-cargo}" test "${CARGO_ONLINE-'--frozen'}" \ --features "test_linking_hack" \ - --target $rustc_host \ + --target "$rustc_host" \ ${EXTRA_CARGO_OPTIONS} \ --manifest-path "${cargo_toml_dir}/Cargo.toml" || exitcode=1 fi diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 5fa7e80d07..480799383b 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -1081,70 +1081,85 @@ test_sr_get_majority_srv_from_votes(void *arg) smartlist_free(votes); } -/* Test utils that don't depend on authority state */ +/* Testing sr_srv_dup(). */ static void -test_utils_general(void *arg) +test_sr_svr_dup(void *arg) { - (void) arg; + (void)arg; - /* Testing sr_srv_dup(). */ - { - sr_srv_t *srv = NULL, *dup_srv = NULL; - const char *srv_value = - "1BDB7C3E973936E4D13A49F37C859B3DC69C429334CF9412E3FEF6399C52D47A"; - srv = tor_malloc_zero(sizeof(*srv)); - srv->num_reveals = 42; - memcpy(srv->value, srv_value, sizeof(srv->value)); - dup_srv = sr_srv_dup(srv); - tt_assert(dup_srv); - tt_u64_op(dup_srv->num_reveals, OP_EQ, srv->num_reveals); - tt_mem_op(dup_srv->value, OP_EQ, srv->value, sizeof(srv->value)); - tor_free(srv); - tor_free(dup_srv); - } + sr_srv_t *srv = NULL, *dup_srv = NULL; + const char *srv_value = + "1BDB7C3E973936E4D13A49F37C859B3DC69C429334CF9412E3FEF6399C52D47A"; + srv = tor_malloc_zero(sizeof(*srv)); + srv->num_reveals = 42; + memcpy(srv->value, srv_value, sizeof(srv->value)); + dup_srv = sr_srv_dup(srv); + tt_assert(dup_srv); + tt_u64_op(dup_srv->num_reveals, OP_EQ, srv->num_reveals); + tt_mem_op(dup_srv->value, OP_EQ, srv->value, sizeof(srv->value)); - /* Testing commitments_are_the_same(). Currently, the check is to test the - * value of the encoded commit so let's make sure that actually works. */ - { - /* Payload of 57 bytes that is the length of sr_commit_t->encoded_commit. - * 56 bytes of payload and a NUL terminated byte at the end ('\x00') - * which comes down to SR_COMMIT_BASE64_LEN + 1. */ - const char *payload = - "\x5d\xb9\x60\xb6\xcc\x51\x68\x52\x31\xd9\x88\x88\x71\x71\xe0\x30" - "\x59\x55\x7f\xcd\x61\xc0\x4b\x05\xb8\xcd\xc1\x48\xe9\xcd\x16\x1f" - "\x70\x15\x0c\xfc\xd3\x1a\x75\xd0\x93\x6c\xc4\xe0\x5c\xbe\xe2\x18" - "\xc7\xaf\x72\xb6\x7c\x9b\x52\x00"; - sr_commit_t commit1, commit2; - memcpy(commit1.encoded_commit, payload, sizeof(commit1.encoded_commit)); - memcpy(commit2.encoded_commit, payload, sizeof(commit2.encoded_commit)); - tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 1); - /* Let's corrupt one of them. */ - memset(commit1.encoded_commit, 'A', sizeof(commit1.encoded_commit)); - tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 0); - } + done: + tor_free(srv); + tor_free(dup_srv); +} - /* Testing commit_is_authoritative(). */ - { - crypto_pk_t *k = crypto_pk_new(); - char digest[DIGEST_LEN]; - sr_commit_t commit; +/* Testing commitments_are_the_same(). Currently, the check is to test the + * value of the encoded commit so let's make sure that actually works. */ +static void +test_commitments_are_the_same(void *arg) +{ + (void)arg; - tt_assert(!crypto_pk_generate_key(k)); + /* Payload of 57 bytes that is the length of sr_commit_t->encoded_commit. + * 56 bytes of payload and a NUL terminated byte at the end ('\x00') + * which comes down to SR_COMMIT_BASE64_LEN + 1. */ + const char *payload = + "\x5d\xb9\x60\xb6\xcc\x51\x68\x52\x31\xd9\x88\x88\x71\x71\xe0\x30" + "\x59\x55\x7f\xcd\x61\xc0\x4b\x05\xb8\xcd\xc1\x48\xe9\xcd\x16\x1f" + "\x70\x15\x0c\xfc\xd3\x1a\x75\xd0\x93\x6c\xc4\xe0\x5c\xbe\xe2\x18" + "\xc7\xaf\x72\xb6\x7c\x9b\x52\x00"; + sr_commit_t commit1, commit2; + memcpy(commit1.encoded_commit, payload, sizeof(commit1.encoded_commit)); + memcpy(commit2.encoded_commit, payload, sizeof(commit2.encoded_commit)); + tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 1); + /* Let's corrupt one of them. */ + memset(commit1.encoded_commit, 'A', sizeof(commit1.encoded_commit)); + tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 0); - tt_int_op(0, OP_EQ, crypto_pk_get_digest(k, digest)); - memcpy(commit.rsa_identity, digest, sizeof(commit.rsa_identity)); - tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 1); - /* Change the pubkey. */ - memset(commit.rsa_identity, 0, sizeof(commit.rsa_identity)); - tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 0); - crypto_pk_free(k); - } + done: + return; +} - /* Testing get_phase_str(). */ - { - tt_str_op(get_phase_str(SR_PHASE_REVEAL), OP_EQ, "reveal"); - tt_str_op(get_phase_str(SR_PHASE_COMMIT), OP_EQ, "commit"); - } +/* Testing commit_is_authoritative(). */ +static void +test_commit_is_authoritative(void *arg) +{ + (void)arg; + + crypto_pk_t *k = crypto_pk_new(); + char digest[DIGEST_LEN]; + sr_commit_t commit; + + tt_assert(!crypto_pk_generate_key(k)); + + tt_int_op(0, OP_EQ, crypto_pk_get_digest(k, digest)); + memcpy(commit.rsa_identity, digest, sizeof(commit.rsa_identity)); + tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 1); + /* Change the pubkey. */ + memset(commit.rsa_identity, 0, sizeof(commit.rsa_identity)); + tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 0); + + done: + crypto_pk_free(k); +} + +static void +test_get_phase_str(void *arg) +{ + (void)arg; + + tt_str_op(get_phase_str(SR_PHASE_REVEAL), OP_EQ, "reveal"); + tt_str_op(get_phase_str(SR_PHASE_COMMIT), OP_EQ, "commit"); done: return; @@ -1649,7 +1664,12 @@ struct testcase_t sr_tests[] = { { "sr_compute_srv", test_sr_compute_srv, TT_FORK, NULL, NULL }, { "sr_get_majority_srv_from_votes", test_sr_get_majority_srv_from_votes, TT_FORK, NULL, NULL }, - { "utils_general", test_utils_general, TT_FORK, NULL, NULL }, + { "sr_svr_dup", test_sr_svr_dup, TT_FORK, NULL, NULL }, + { "commitments_are_the_same", test_commitments_are_the_same, TT_FORK, NULL, + NULL }, + { "commit_is_authoritative", test_commit_is_authoritative, TT_FORK, NULL, + NULL }, + { "get_phase_str", test_get_phase_str, TT_FORK, NULL, NULL }, { "utils_auth", test_utils_auth, TT_FORK, NULL, NULL }, { "state_transition", test_state_transition, TT_FORK, NULL, NULL }, { "state_update", test_state_update, TT_FORK, diff --git a/src/test/test_slow.c b/src/test/test_slow.c index c3e7edd408..d4d5b755a5 100644 --- a/src/test/test_slow.c +++ b/src/test/test_slow.c @@ -22,6 +22,7 @@ struct testgroup_t testgroups[] = { { "slow/crypto/", slow_crypto_tests }, { "slow/process/", slow_process_tests }, { "slow/prob_distr/", slow_stochastic_prob_distr_tests }, + { "slow/ptr/", slow_ptr_tests }, END_OF_GROUPS }; diff --git a/src/test/test_switch_id.sh b/src/test/test_switch_id.sh index 79c44f2eb1..b13bf7602f 100755 --- a/src/test/test_switch_id.sh +++ b/src/test/test_switch_id.sh @@ -1,11 +1,11 @@ #!/bin/sh -if test "`id -u`" != '0'; then +if test "$(id -u)" != '0'; then echo "This test only works when run as root. Skipping." >&2 exit 77 fi -if test "`id -u nobody`" = ""; then +if test "$(id -u nobody)" = ""; then echo "This test requires that your system have a 'nobody' user. Sorry." >&2 exit 1 fi diff --git a/src/test/test_util.c b/src/test/test_util.c index 7a2708c541..b4e702e1d7 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -6,7 +6,6 @@ #include "orconfig.h" #define COMPAT_PRIVATE #define COMPAT_TIME_PRIVATE -#define CONTROL_PRIVATE #define UTIL_PRIVATE #define UTIL_MALLOC_PRIVATE #define SOCKET_PRIVATE @@ -16,6 +15,7 @@ #include "lib/buf/buffers.h" #include "app/config/config.h" #include "feature/control/control.h" +#include "feature/control/control_fmt.h" #include "feature/client/transports.h" #include "lib/crypt_ops/crypto_format.h" #include "lib/crypt_ops/crypto_rand.h" @@ -6165,8 +6165,8 @@ static void test_util_map_anon_nofork(void *arg) { (void)arg; -#if !defined(HAVE_MADVISE) && !defined(HAVE_MINHERIT) - /* The operating system doesn't support this. */ +#ifdef _WIN32 + /* The operating system doesn't support forking. */ tt_skip(); done: ; @@ -6182,6 +6182,7 @@ test_util_map_anon_nofork(void *arg) tor_munmap_anonymous(ptr, sz); ptr = tor_mmap_anonymous(sz, ANONMAP_NOINHERIT); + int outcome = get_last_anon_map_noinherit(); tt_ptr_op(ptr, OP_NE, 0); memset(ptr, 0xd0, sz); @@ -6202,15 +6203,30 @@ test_util_map_anon_nofork(void *arg) pipefd[1] = -1; char buf[1]; ssize_t r = read(pipefd[0], buf, 1); -#if defined(INHERIT_ZERO) || defined(MADV_WIPEONFORK) - tt_int_op((int)r, OP_EQ, 1); // child should send us a byte. - tt_int_op(buf[0], OP_EQ, 0); -#else - tt_int_op(r, OP_LE, 0); // child said nothing; it should have crashed. -#endif + + if (outcome == 2) { + // We should be seeing clear-on-fork behavior. + tt_int_op((int)r, OP_EQ, 1); // child should send us a byte. + tt_int_op(buf[0], OP_EQ, 0); // that byte should be zero. + } else if (outcome == 1) { + // We should be seeing noinherit behavior. + tt_int_op(r, OP_LE, 0); // child said nothing; it should have crashed. + } else { + // noinherit isn't implemented. + tt_int_op(outcome, OP_EQ, 0); + tt_int_op((int)r, OP_EQ, 1); // child should send us a byte. + tt_int_op(buf[0], OP_EQ, 0xd0); // that byte should what we set it to. + } + int ws; waitpid(child, &ws, 0); + if (outcome == 0) { + /* Call this test "skipped", not "passed", since noinherit wasn't + * implemented. */ + tt_skip(); + } + done: tor_munmap_anonymous(ptr, sz); if (pipefd[0] >= 0) { @@ -6360,6 +6376,6 @@ struct testcase_t util_tests[] = { UTIL_TEST(get_unquoted_path, 0), UTIL_TEST(log_mallinfo, 0), UTIL_TEST(map_anon, 0), - UTIL_TEST(map_anon_nofork, TT_SKIP /* See bug #29535 */), + UTIL_TEST(map_anon_nofork, 0), END_OF_TESTCASES }; diff --git a/src/test/test_util_format.c b/src/test/test_util_format.c index 3a0b41faa5..c8945a707c 100644 --- a/src/test/test_util_format.c +++ b/src/test/test_util_format.c @@ -346,7 +346,7 @@ test_util_format_base32_decode(void *arg) const char *src = "mjwgc2dcnrswqmjs"; ret = base32_decode(dst, strlen(expected), src, strlen(src)); - tt_int_op(ret, OP_EQ, 0); + tt_int_op(ret, OP_EQ, 10); tt_str_op(expected, OP_EQ, dst); } @@ -357,7 +357,7 @@ test_util_format_base32_decode(void *arg) const char *src = "mjwgc2dcnrswq"; ret = base32_decode(dst, strlen(expected), src, strlen(src)); - tt_int_op(ret, OP_EQ, 0); + tt_int_op(ret, OP_EQ, 8); tt_mem_op(expected, OP_EQ, dst, strlen(expected)); } diff --git a/src/test/test_workqueue_cancel.sh b/src/test/test_workqueue_cancel.sh index f7c663171e..e50b884f26 100755 --- a/src/test/test_workqueue_cancel.sh +++ b/src/test/test_workqueue_cancel.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue -C 1 +"${builddir:-.}/src/test/test_workqueue" -C 1 diff --git a/src/test/test_workqueue_efd.sh b/src/test/test_workqueue_efd.sh index 4d89396819..592841fc91 100755 --- a/src/test/test_workqueue_efd.sh +++ b/src/test/test_workqueue_efd.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue \ +"${builddir:-.}/src/test/test_workqueue" \ --no-eventfd2 --no-pipe2 --no-pipe --no-socketpair diff --git a/src/test/test_workqueue_efd2.sh b/src/test/test_workqueue_efd2.sh index 7cfff45ff3..4cf1b76cbe 100755 --- a/src/test/test_workqueue_efd2.sh +++ b/src/test/test_workqueue_efd2.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue \ +"${builddir:-.}/src/test/test_workqueue" \ --no-eventfd --no-pipe2 --no-pipe --no-socketpair diff --git a/src/test/test_workqueue_pipe.sh b/src/test/test_workqueue_pipe.sh index afcef87853..fc3ef34c6c 100755 --- a/src/test/test_workqueue_pipe.sh +++ b/src/test/test_workqueue_pipe.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue \ +"${builddir:-.}/src/test/test_workqueue" \ --no-eventfd2 --no-eventfd --no-pipe2 --no-socketpair diff --git a/src/test/test_workqueue_pipe2.sh b/src/test/test_workqueue_pipe2.sh index a20a1427e0..7f19ea880d 100755 --- a/src/test/test_workqueue_pipe2.sh +++ b/src/test/test_workqueue_pipe2.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue \ +"${builddir:-.}/src/test/test_workqueue" \ --no-eventfd2 --no-eventfd --no-pipe --no-socketpair diff --git a/src/test/test_workqueue_socketpair.sh b/src/test/test_workqueue_socketpair.sh index 76af79746d..1ee1776447 100755 --- a/src/test/test_workqueue_socketpair.sh +++ b/src/test/test_workqueue_socketpair.sh @@ -1,4 +1,4 @@ #!/bin/sh -${builddir:-.}/src/test/test_workqueue \ +"${builddir:-.}/src/test/test_workqueue" \ --no-eventfd2 --no-eventfd --no-pipe2 --no-pipe diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 8fc8ef7830..1c2a2e8960 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -12,6 +12,7 @@ #include "orconfig.h" #include "core/or/or.h" #include "feature/control/control.h" +#include "feature/control/control_events.h" #include "app/config/config.h" #include "lib/crypt_ops/crypto_dh.h" #include "lib/crypt_ops/crypto_ed25519.h" diff --git a/src/test/testing_rsakeys.c b/src/test/testing_rsakeys.c index 0f22d4e01b..8ba6bf9fe4 100644 --- a/src/test/testing_rsakeys.c +++ b/src/test/testing_rsakeys.c @@ -448,7 +448,8 @@ static int next_key_idx_2048; static crypto_pk_t * pk_generate_internal(int bits) { - tor_assert(bits == 2048 || bits == 1024); + tor_assertf(bits == 2048 || bits == 1024, + "Wrong key size: %d", bits); #ifdef USE_PREGENERATED_RSA_KEYS int *idxp; diff --git a/src/test/zero_length_keys.sh b/src/test/zero_length_keys.sh index 3c61f8d465..4069148e0b 100755 --- a/src/test/zero_length_keys.sh +++ b/src/test/zero_length_keys.sh @@ -19,7 +19,7 @@ # 3: a command failed - the test could not be completed # -if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then +if [ $# -eq 0 ] || [ ! -f "${1}" ] || [ ! -x "${1}" ]; then echo "Usage: ${0} PATH_TO_TOR [-z|-d|-e]" exit 1 elif [ $# -eq 1 ]; then @@ -31,7 +31,7 @@ else #[$# -gt 1 ]; then shift fi -DATA_DIR=`mktemp -d -t tor_zero_length_keys.XXXXXX` +DATA_DIR=$(mktemp -d -t tor_zero_length_keys.XXXXXX) if [ -z "$DATA_DIR" ]; then echo "Failure: mktemp invocation returned empty string" >&2 exit 3 @@ -40,7 +40,7 @@ if [ ! -d "$DATA_DIR" ]; then echo "Failure: mktemp invocation result doesn't point to directory" >&2 exit 3 fi -trap "rm -rf '$DATA_DIR'" 0 +trap 'rm -rf "$DATA_DIR"' 0 touch "$DATA_DIR"/empty_torrc diff --git a/src/trunnel/ed25519_cert.trunnel b/src/trunnel/ed25519_cert.trunnel index 8d6483d558..e424ce5464 100644 --- a/src/trunnel/ed25519_cert.trunnel +++ b/src/trunnel/ed25519_cert.trunnel @@ -28,12 +28,6 @@ const LS_IPV6 = 0x01; const LS_LEGACY_ID = 0x02; const LS_ED25519_ID = 0x03; -// XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by -// taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE, or -// if we ever make link_specifier contain other types, we will -// need to refactor that function to do the copy by encoding and decoding the -// object. - // amended from tor.trunnel struct link_specifier { u8 ls_type; diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index d3441610b6..892531ceb4 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -218,7 +218,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.4.0.3-alpha-dev" +#define VERSION "0.4.1.0-alpha-dev" |