diff options
Diffstat (limited to 'src/feature/client/transports.c')
-rw-r--r-- | src/feature/client/transports.c | 1738 |
1 files changed, 1738 insertions, 0 deletions
diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c new file mode 100644 index 0000000000..1d3cb7b951 --- /dev/null +++ b/src/feature/client/transports.c @@ -0,0 +1,1738 @@ +/* Copyright (c) 2011-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file transports.c + * \brief Pluggable Transports related code. + * + * \details + * Each managed proxy is represented by a <b>managed_proxy_t</b>. + * Each managed proxy can support multiple transports. + * Each managed proxy gets configured through a multistep process. + * + * ::managed_proxy_list contains all the managed proxies this tor + * instance is supporting. + * In the ::managed_proxy_list there are ::unconfigured_proxies_n + * managed proxies that are still unconfigured. + * + * In every run_scheduled_event() tick, we attempt to launch and then + * configure the unconfiged managed proxies, using the configuration + * protocol defined in the 180_pluggable_transport.txt proposal. A + * managed proxy might need several ticks to get fully configured. + * + * When a managed proxy is fully configured, we register all its + * transports to the circuitbuild.c subsystem. At that point the + * transports are owned by the circuitbuild.c subsystem. + * + * When a managed proxy fails to follow the 180 configuration + * protocol, it gets marked as broken and gets destroyed. + * + * <b>In a little more detail:</b> + * + * While we are serially parsing torrc, we store all the transports + * that a proxy should spawn in its <em>transports_to_launch</em> + * element. + * + * When we finish reading the torrc, we spawn the managed proxy and + * expect {S,C}METHOD lines from its output. We add transports + * described by METHOD lines to its <em>transports</em> element, as + * transport_t structs. + * + * When the managed proxy stops spitting METHOD lines (signified by a + * '{S,C}METHODS DONE' message) we pass copies of its transports to + * the bridge subsystem. We keep copies of the 'transport_t's on the + * managed proxy to be able to associate the proxy with its + * transports, and we pass copies to the bridge subsystem so that + * transports can be associated with bridges. + * [ XXX We should try see whether the two copies are really needed + * and maybe cut it into a single copy of the 'transport_t' shared + * between the managed proxy and the bridge subsystem. Preliminary + * analysis shows that both copies are needed with the current code + * logic, because of race conditions that can cause dangling + * pointers. ] + * + * <b>In even more detail, this is what happens when a config read + * (like a SIGHUP or a SETCONF) occurs:</b> + * + * We immediately destroy all unconfigured proxies (We shouldn't have + * unconfigured proxies in the first place, except when the config + * read happens immediately after tor is launched.). + * + * We mark all managed proxies and transports to signify that they + * must be removed if they don't contribute by the new torrc + * (we mark using the <b>marked_for_removal</b> element). + * We also mark all managed proxies to signify that they might need to + * be restarted so that they end up supporting all the transports the + * new torrc wants them to support + * (we mark using the <b>was_around_before_config_read</b> element). + * We also clear their <b>transports_to_launch</b> list so that we can + * put there the transports we need to launch according to the new + * torrc. + * + * We then start parsing torrc again. + * + * Everytime we encounter a transport line using a managed proxy that + * was around before the config read, we cleanse that proxy from the + * removal mark. We also toggle the <b>check_if_restarts_needed</b> + * flag, so that on the next <b>pt_configure_remaining_proxies</b> + * tick, we investigate whether we need to restart the proxy so that + * it also spawns the new transports. If the post-config-read + * <b>transports_to_launch</b> list is identical to the pre-config-read + * one, it means that no changes were introduced to this proxy during + * the config read and no restart has to take place. + * + * During the post-config-read torrc parsing, we unmark all transports + * spawned by managed proxies that we find in our torrc. + * We do that so that if we don't need to restart a managed proxy, we + * can continue using its old transports normally. + * If we end up restarting the proxy, we destroy and unregister all + * old transports from the circuitbuild.c subsystem. + **/ + +#define PT_PRIVATE +#include "or/or.h" +#include "or/bridges.h" +#include "or/config.h" +#include "or/connection.h" +#include "or/circuitbuild.h" +#include "or/transports.h" +#include "or/router.h" +#include "or/statefile.h" +#include "or/connection_or.h" +#include "or/ext_orport.h" +#include "or/control.h" + +#include "lib/process/env.h" +#include "lib/process/subprocess.h" + +static process_environment_t * +create_managed_proxy_environment(const managed_proxy_t *mp); + +static inline int proxy_configuration_finished(const managed_proxy_t *mp); + +static void handle_finished_proxy(managed_proxy_t *mp); +static void parse_method_error(const char *line, int is_server_method); +#define parse_server_method_error(l) parse_method_error(l, 1) +#define parse_client_method_error(l) parse_method_error(l, 0) + +/** Managed proxy protocol strings */ +#define PROTO_ENV_ERROR "ENV-ERROR" +#define PROTO_NEG_SUCCESS "VERSION" +#define PROTO_NEG_FAIL "VERSION-ERROR no-version" +#define PROTO_CMETHOD "CMETHOD" +#define PROTO_SMETHOD "SMETHOD" +#define PROTO_CMETHOD_ERROR "CMETHOD-ERROR" +#define PROTO_SMETHOD_ERROR "SMETHOD-ERROR" +#define PROTO_CMETHODS_DONE "CMETHODS DONE" +#define PROTO_SMETHODS_DONE "SMETHODS DONE" +#define PROTO_PROXY_DONE "PROXY DONE" +#define PROTO_PROXY_ERROR "PROXY-ERROR" + +/** The first and only supported - at the moment - configuration + protocol version. */ +#define PROTO_VERSION_ONE 1 + +/** A list of pluggable transports found in torrc. */ +static smartlist_t *transport_list = NULL; + +/** Returns a transport_t struct for a transport proxy supporting the + protocol <b>name</b> listening at <b>addr</b>:<b>port</b> using + SOCKS version <b>socks_ver</b>. */ +STATIC transport_t * +transport_new(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver, + const char *extra_info_args) +{ + transport_t *t = tor_malloc_zero(sizeof(transport_t)); + + tor_addr_copy(&t->addr, addr); + t->port = port; + t->name = tor_strdup(name); + t->socks_version = socks_ver; + if (extra_info_args) + t->extra_info_args = tor_strdup(extra_info_args); + + return t; +} + +/** Free the pluggable transport struct <b>transport</b>. */ +void +transport_free_(transport_t *transport) +{ + if (!transport) + return; + + tor_free(transport->name); + tor_free(transport->extra_info_args); + tor_free(transport); +} + +/** Mark every entry of the transport list to be removed on our next call to + * sweep_transport_list unless it has first been un-marked. */ +void +mark_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH(transport_list, transport_t *, t, + t->marked_for_removal = 1); +} + +/** Remove every entry of the transport list that was marked with + * mark_transport_list if it has not subsequently been un-marked. */ +void +sweep_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, t) { + if (t->marked_for_removal) { + SMARTLIST_DEL_CURRENT(transport_list, t); + transport_free(t); + } + } SMARTLIST_FOREACH_END(t); +} + +/** Initialize the pluggable transports list to empty, creating it if + * needed. */ +static void +clear_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH(transport_list, transport_t *, t, transport_free(t)); + smartlist_clear(transport_list); +} + +/** Return a deep copy of <b>transport</b>. */ +static transport_t * +transport_copy(const transport_t *transport) +{ + transport_t *new_transport = NULL; + + tor_assert(transport); + + new_transport = tor_malloc_zero(sizeof(transport_t)); + + new_transport->socks_version = transport->socks_version; + new_transport->name = tor_strdup(transport->name); + tor_addr_copy(&new_transport->addr, &transport->addr); + new_transport->port = transport->port; + new_transport->marked_for_removal = transport->marked_for_removal; + + return new_transport; +} + +/** Returns the transport in our transport list that has the name <b>name</b>. + * Else returns NULL. */ +MOCK_IMPL(transport_t *, +transport_get_by_name,(const char *name)) +{ + tor_assert(name); + + if (!transport_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, transport) { + if (!strcmp(transport->name, name)) + return transport; + } SMARTLIST_FOREACH_END(transport); + + return NULL; +} + +/** Resolve any conflicts that the insertion of transport <b>t</b> + * might cause. + * Return 0 if <b>t</b> is OK and should be registered, 1 if there is + * a transport identical to <b>t</b> already registered and -1 if + * <b>t</b> cannot be added due to conflicts. */ +static int +transport_resolve_conflicts(const transport_t *t) +{ + /* This is how we resolve transport conflicts: + + If there is already a transport with the same name and addrport, + we either have duplicate torrc lines OR we are here post-HUP and + this transport was here pre-HUP as well. In any case, mark the + old transport so that it doesn't get removed and ignore the new + one. Our caller has to free the new transport so we return '1' to + signify this. + + If there is already a transport with the same name but different + addrport: + * if it's marked for removal, it means that it either has a lower + priority than 't' in torrc (otherwise the mark would have been + cleared by the paragraph above), or it doesn't exist at all in + the post-HUP torrc. We destroy the old transport and register 't'. + * if it's *not* marked for removal, it means that it was newly + added in the post-HUP torrc or that it's of higher priority, in + this case we ignore 't'. */ + transport_t *t_tmp = transport_get_by_name(t->name); + if (t_tmp) { /* same name */ + if (tor_addr_eq(&t->addr, &t_tmp->addr) && (t->port == t_tmp->port)) { + /* same name *and* addrport */ + t_tmp->marked_for_removal = 0; + return 1; + } else { /* same name but different addrport */ + char *new_transport_addrport = + tor_strdup(fmt_addrport(&t->addr, t->port)); + if (t_tmp->marked_for_removal) { /* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s' " + "but there was already a transport marked for deletion at " + "'%s'. We deleted the old transport and registered the " + "new one.", t->name, new_transport_addrport, + fmt_addrport(&t_tmp->addr, t_tmp->port)); + smartlist_remove(transport_list, t_tmp); + transport_free(t_tmp); + tor_free(new_transport_addrport); + } else { /* *not* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s' " + "but the same transport already exists at '%s'. " + "Skipping.", t->name, new_transport_addrport, + fmt_addrport(&t_tmp->addr, t_tmp->port)); + tor_free(new_transport_addrport); + return -1; + } + tor_free(new_transport_addrport); + } + } + + return 0; +} + +/** Add transport <b>t</b> to the internal list of pluggable + * transports. + * Returns 0 if the transport was added correctly, 1 if the same + * transport was already registered (in this case the caller must + * free the transport) and -1 if there was an error. */ +static int +transport_add(transport_t *t) +{ + int r; + tor_assert(t); + + r = transport_resolve_conflicts(t); + + switch (r) { + case 0: /* should register transport */ + if (!transport_list) + transport_list = smartlist_new(); + smartlist_add(transport_list, t); + return 0; + default: /* let our caller know the return code */ + return r; + } +} + +/** Remember a new pluggable transport proxy at <b>addr</b>:<b>port</b>. + * <b>name</b> is set to the name of the protocol this proxy uses. + * <b>socks_ver</b> is set to the SOCKS version of the proxy. */ +MOCK_IMPL(int, +transport_add_from_config, (const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver)) +{ + transport_t *t = transport_new(addr, port, name, socks_ver, NULL); + + int r = transport_add(t); + + switch (r) { + case -1: + default: + log_notice(LD_GENERAL, "Could not add transport %s at %s. Skipping.", + t->name, fmt_addrport(&t->addr, t->port)); + transport_free(t); + return -1; + case 1: + log_info(LD_GENERAL, "Successfully registered transport %s at %s.", + t->name, fmt_addrport(&t->addr, t->port)); + transport_free(t); /* falling */ + return 0; + case 0: + log_info(LD_GENERAL, "Successfully registered transport %s at %s.", + t->name, fmt_addrport(&t->addr, t->port)); + return 0; + } +} + +/** List of unconfigured managed proxies. */ +static smartlist_t *managed_proxy_list = NULL; +/** Number of still unconfigured proxies. */ +static int unconfigured_proxies_n = 0; +/** Boolean: True iff we might need to restart some proxies. */ +static int check_if_restarts_needed = 0; + +/** Return true if there are still unconfigured managed proxies, or proxies + * that need restarting. */ +int +pt_proxies_configuration_pending(void) +{ + return unconfigured_proxies_n || check_if_restarts_needed; +} + +/** Assert that the unconfigured_proxies_n value correctly matches the number + * of proxies in a state other than PT_PROTO_COMPLETE. */ +static void +assert_unconfigured_count_ok(void) +{ + int n_completed = 0; + if (!managed_proxy_list) { + tor_assert(unconfigured_proxies_n == 0); + return; + } + + SMARTLIST_FOREACH(managed_proxy_list, managed_proxy_t *, mp, { + if (mp->conf_state == PT_PROTO_COMPLETED) + ++n_completed; + }); + + tor_assert(n_completed + unconfigured_proxies_n == + smartlist_len(managed_proxy_list)); +} + +/** Return true if <b>mp</b> has the same argv as <b>proxy_argv</b> */ +static int +managed_proxy_has_argv(const managed_proxy_t *mp, char **proxy_argv) +{ + char **tmp1=proxy_argv; + char **tmp2=mp->argv; + + tor_assert(tmp1); + tor_assert(tmp2); + + while (*tmp1 && *tmp2) { + if (strcmp(*tmp1++, *tmp2++)) + return 0; + } + + if (!*tmp1 && !*tmp2) + return 1; + + return 0; +} + +/** Return a managed proxy with the same argv as <b>proxy_argv</b>. + * If no such managed proxy exists, return NULL. */ +static managed_proxy_t * +get_managed_proxy_by_argv_and_type(char **proxy_argv, int is_server) +{ + if (!managed_proxy_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (managed_proxy_has_argv(mp, proxy_argv) && + mp->is_server == is_server) + return mp; + } SMARTLIST_FOREACH_END(mp); + + return NULL; +} + +/** Add <b>transport</b> to managed proxy <b>mp</b>. */ +static void +add_transport_to_proxy(const char *transport, managed_proxy_t *mp) +{ + tor_assert(mp->transports_to_launch); + if (!smartlist_contains_string(mp->transports_to_launch, transport)) + smartlist_add_strdup(mp->transports_to_launch, transport); +} + +/** Called when a SIGHUP occurs. Returns true if managed proxy + * <b>mp</b> needs to be restarted after the SIGHUP, based on the new + * torrc. */ +static int +proxy_needs_restart(const managed_proxy_t *mp) +{ + int ret = 1; + char* proxy_uri; + + /* If the PT proxy config has changed, then all existing pluggable transports + * should be restarted. + */ + + proxy_uri = get_pt_proxy_uri(); + if (strcmp_opt(proxy_uri, mp->proxy_uri) != 0) + goto needs_restart; + + /* mp->transport_to_launch is populated with the names of the + transports that must be launched *after* the SIGHUP. + mp->transports is populated with the transports that were + launched *before* the SIGHUP. + + Check if all the transports that need to be launched are already + launched: */ + + tor_assert(smartlist_len(mp->transports_to_launch) > 0); + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + if (smartlist_len(mp->transports_to_launch) != smartlist_len(mp->transports)) + goto needs_restart; + + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + if (!smartlist_contains_string(mp->transports_to_launch, t->name)) + goto needs_restart; + + } SMARTLIST_FOREACH_END(t); + + ret = 0; + needs_restart: + tor_free(proxy_uri); + return ret; +} + +/** Managed proxy <b>mp</b> must be restarted. Do all the necessary + * preparations and then flag its state so that it will be relaunched + * in the next tick. */ +static void +proxy_prepare_for_restart(managed_proxy_t *mp) +{ + transport_t *t_tmp = NULL; + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + /* destroy the process handle and terminate the process. */ + tor_process_handle_destroy(mp->process_handle, 1); + mp->process_handle = NULL; + + /* destroy all its registered transports, since we will no longer + use them. */ + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + t_tmp = transport_get_by_name(t->name); + if (t_tmp) + t_tmp->marked_for_removal = 1; + } SMARTLIST_FOREACH_END(t); + sweep_transport_list(); + + /* free the transport in mp->transports */ + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); + smartlist_clear(mp->transports); + + /* Reset the proxy's HTTPS/SOCKS proxy */ + tor_free(mp->proxy_uri); + mp->proxy_uri = get_pt_proxy_uri(); + mp->proxy_supported = 0; + + /* flag it as an infant proxy so that it gets launched on next tick */ + mp->conf_state = PT_PROTO_INFANT; + unconfigured_proxies_n++; +} + +/** Launch managed proxy <b>mp</b>. */ +static int +launch_managed_proxy(managed_proxy_t *mp) +{ + int retval; + + process_environment_t *env = create_managed_proxy_environment(mp); + +#ifdef _WIN32 + /* Passing NULL as lpApplicationName makes Windows search for the .exe */ + retval = tor_spawn_background(NULL, + (const char **)mp->argv, + env, + &mp->process_handle); +#else /* !(defined(_WIN32)) */ + retval = tor_spawn_background(mp->argv[0], + (const char **)mp->argv, + env, + &mp->process_handle); +#endif /* defined(_WIN32) */ + + process_environment_free(env); + + if (retval == PROCESS_STATUS_ERROR) { + log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.", + mp->argv[0]); + return -1; + } + + log_info(LD_CONFIG, "Managed proxy at '%s' has spawned with PID '%d'.", + mp->argv[0], tor_process_get_pid(mp->process_handle)); + + mp->conf_state = PT_PROTO_LAUNCHED; + + return 0; +} + +/** Check if any of the managed proxies we are currently trying to + * configure has anything new to say. */ +void +pt_configure_remaining_proxies(void) +{ + int at_least_a_proxy_config_finished = 0; + smartlist_t *tmp = smartlist_new(); + + log_debug(LD_CONFIG, "Configuring remaining managed proxies (%d)!", + unconfigured_proxies_n); + + /* Iterate over tmp, not managed_proxy_list, since configure_proxy can + * remove elements from managed_proxy_list. */ + smartlist_add_all(tmp, managed_proxy_list); + + assert_unconfigured_count_ok(); + + SMARTLIST_FOREACH_BEGIN(tmp, managed_proxy_t *, mp) { + tor_assert(mp->conf_state != PT_PROTO_BROKEN && + mp->conf_state != PT_PROTO_FAILED_LAUNCH); + + if (mp->was_around_before_config_read) { + /* This proxy is marked by a config read. Check whether we need + to restart it. */ + + mp->was_around_before_config_read = 0; + + if (proxy_needs_restart(mp)) { + log_info(LD_GENERAL, "Preparing managed proxy '%s' for restart.", + mp->argv[0]); + proxy_prepare_for_restart(mp); + } else { /* it doesn't need to be restarted. */ + log_info(LD_GENERAL, "Nothing changed for managed proxy '%s' after " + "HUP: not restarting.", mp->argv[0]); + } + + continue; + } + + /* If the proxy is not fully configured, try to configure it + further. */ + if (!proxy_configuration_finished(mp)) + if (configure_proxy(mp) == 1) + at_least_a_proxy_config_finished = 1; + + } SMARTLIST_FOREACH_END(mp); + + smartlist_free(tmp); + check_if_restarts_needed = 0; + assert_unconfigured_count_ok(); + + if (at_least_a_proxy_config_finished) + mark_my_descriptor_dirty("configured managed proxies"); +} + +/** Attempt to continue configuring managed proxy <b>mp</b>. + * Return 1 if the transport configuration finished, and return 0 + * otherwise (if we still have more configuring to do for this + * proxy). */ +STATIC int +configure_proxy(managed_proxy_t *mp) +{ + int configuration_finished = 0; + smartlist_t *proxy_output = NULL; + enum stream_status stream_status = 0; + + /* if we haven't launched the proxy yet, do it now */ + if (mp->conf_state == PT_PROTO_INFANT) { + if (launch_managed_proxy(mp) < 0) { /* launch fail */ + mp->conf_state = PT_PROTO_FAILED_LAUNCH; + handle_finished_proxy(mp); + } + return 0; + } + + tor_assert(mp->conf_state != PT_PROTO_INFANT); + tor_assert(mp->process_handle); + + proxy_output = + tor_get_lines_from_handle(tor_process_get_stdout_pipe(mp->process_handle), + &stream_status); + if (!proxy_output) { /* failed to get input from proxy */ + if (stream_status != IO_STREAM_EAGAIN) { /* bad stream status! */ + mp->conf_state = PT_PROTO_BROKEN; + log_warn(LD_GENERAL, "The communication stream of managed proxy '%s' " + "is '%s'. Most probably the managed proxy stopped running. " + "This might be a bug of the managed proxy, a bug of Tor, or " + "a misconfiguration. Please enable logging on your managed " + "proxy and check the logs for errors.", + mp->argv[0], stream_status_to_string(stream_status)); + } + + goto done; + } + + /* Handle lines. */ + SMARTLIST_FOREACH_BEGIN(proxy_output, const char *, line) { + handle_proxy_line(line, mp); + if (proxy_configuration_finished(mp)) + goto done; + } SMARTLIST_FOREACH_END(line); + + done: + /* if the proxy finished configuring, exit the loop. */ + if (proxy_configuration_finished(mp)) { + handle_finished_proxy(mp); + configuration_finished = 1; + } + + if (proxy_output) { + SMARTLIST_FOREACH(proxy_output, char *, cp, tor_free(cp)); + smartlist_free(proxy_output); + } + + return configuration_finished; +} + +/** Register server managed proxy <b>mp</b> transports to state */ +static void +register_server_proxy(const managed_proxy_t *mp) +{ + tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { + save_transport_to_state(t->name, &t->addr, t->port); + log_notice(LD_GENERAL, "Registered server transport '%s' at '%s'", + t->name, fmt_addrport(&t->addr, t->port)); + control_event_transport_launched("server", t->name, &t->addr, t->port); + } SMARTLIST_FOREACH_END(t); +} + +/** Register all the transports supported by client managed proxy + * <b>mp</b> to the bridge subsystem. */ +static void +register_client_proxy(const managed_proxy_t *mp) +{ + int r; + + tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { + transport_t *transport_tmp = transport_copy(t); + r = transport_add(transport_tmp); + switch (r) { + case -1: + log_notice(LD_GENERAL, "Could not add transport %s. Skipping.", t->name); + transport_free(transport_tmp); + break; + case 0: + log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); + break; + case 1: + log_info(LD_GENERAL, "Successfully registered transport %s", t->name); + control_event_transport_launched("client", t->name, &t->addr, t->port); + transport_free(transport_tmp); + break; + } + } SMARTLIST_FOREACH_END(t); +} + +/** Register the transports of managed proxy <b>mp</b>. */ +static inline void +register_proxy(const managed_proxy_t *mp) +{ + if (mp->is_server) + register_server_proxy(mp); + else + register_client_proxy(mp); +} + +/** Free memory allocated by managed proxy <b>mp</b>. */ +STATIC void +managed_proxy_destroy(managed_proxy_t *mp, + int also_terminate_process) +{ + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); + + /* free the transports smartlist */ + smartlist_free(mp->transports); + + /* free the transports_to_launch smartlist */ + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_free(mp->transports_to_launch); + + /* remove it from the list of managed proxies */ + if (managed_proxy_list) + smartlist_remove(managed_proxy_list, mp); + + /* free the argv */ + free_execve_args(mp->argv); + + /* free the outgoing proxy URI */ + tor_free(mp->proxy_uri); + + tor_process_handle_destroy(mp->process_handle, also_terminate_process); + mp->process_handle = NULL; + + tor_free(mp); +} + +/** Convert the tor proxy options to a URI suitable for TOR_PT_PROXY. + * Return a newly allocated string containing the URI, or NULL if no + * proxy is set. */ +STATIC char * +get_pt_proxy_uri(void) +{ + const or_options_t *options = get_options(); + char *uri = NULL; + + if (options->Socks4Proxy || options->Socks5Proxy || options->HTTPSProxy) { + char addr[TOR_ADDR_BUF_LEN+1]; + + if (options->Socks4Proxy) { + tor_addr_to_str(addr, &options->Socks4ProxyAddr, sizeof(addr), 1); + tor_asprintf(&uri, "socks4a://%s:%d", addr, options->Socks4ProxyPort); + } else if (options->Socks5Proxy) { + tor_addr_to_str(addr, &options->Socks5ProxyAddr, sizeof(addr), 1); + if (!options->Socks5ProxyUsername && !options->Socks5ProxyPassword) { + tor_asprintf(&uri, "socks5://%s:%d", addr, options->Socks5ProxyPort); + } else { + tor_asprintf(&uri, "socks5://%s:%s@%s:%d", + options->Socks5ProxyUsername, + options->Socks5ProxyPassword, + addr, options->Socks5ProxyPort); + } + } else if (options->HTTPSProxy) { + tor_addr_to_str(addr, &options->HTTPSProxyAddr, sizeof(addr), 1); + if (!options->HTTPSProxyAuthenticator) { + tor_asprintf(&uri, "http://%s:%d", addr, options->HTTPSProxyPort); + } else { + tor_asprintf(&uri, "http://%s@%s:%d", options->HTTPSProxyAuthenticator, + addr, options->HTTPSProxyPort); + } + } + } + + return uri; +} + +/** Handle a configured or broken managed proxy <b>mp</b>. */ +static void +handle_finished_proxy(managed_proxy_t *mp) +{ + switch (mp->conf_state) { + case PT_PROTO_BROKEN: /* if broken: */ + managed_proxy_destroy(mp, 1); /* annihilate it. */ + break; + case PT_PROTO_FAILED_LAUNCH: /* if it failed before launching: */ + managed_proxy_destroy(mp, 0); /* destroy it but don't terminate */ + break; + case PT_PROTO_CONFIGURED: /* if configured correctly: */ + if (mp->proxy_uri && !mp->proxy_supported) { + log_warn(LD_CONFIG, "Managed proxy '%s' did not configure the " + "specified outgoing proxy and will be terminated.", + mp->argv[0]); + managed_proxy_destroy(mp, 1); /* annihilate it. */ + break; + } + register_proxy(mp); /* register its transports */ + mp->conf_state = PT_PROTO_COMPLETED; /* and mark it as completed. */ + break; + case PT_PROTO_INFANT: + case PT_PROTO_LAUNCHED: + case PT_PROTO_ACCEPTING_METHODS: + case PT_PROTO_COMPLETED: + default: + log_warn(LD_CONFIG, "Unexpected state '%d' of managed proxy '%s'.", + (int)mp->conf_state, mp->argv[0]); + tor_assert(0); + } + + unconfigured_proxies_n--; +} + +/** Return true if the configuration of the managed proxy <b>mp</b> is + finished. */ +static inline int +proxy_configuration_finished(const managed_proxy_t *mp) +{ + return (mp->conf_state == PT_PROTO_CONFIGURED || + mp->conf_state == PT_PROTO_BROKEN || + mp->conf_state == PT_PROTO_FAILED_LAUNCH); +} + +/** This function is called when a proxy sends an {S,C}METHODS DONE message. */ +static void +handle_methods_done(const managed_proxy_t *mp) +{ + tor_assert(mp->transports); + + if (smartlist_len(mp->transports) == 0) + log_notice(LD_GENERAL, "Managed proxy '%s' was spawned successfully, " + "but it didn't launch any pluggable transport listeners!", + mp->argv[0]); + + log_info(LD_CONFIG, "%s managed proxy '%s' configuration completed!", + mp->is_server ? "Server" : "Client", + mp->argv[0]); +} + +/** Handle a configuration protocol <b>line</b> received from a + * managed proxy <b>mp</b>. */ +STATIC void +handle_proxy_line(const char *line, managed_proxy_t *mp) +{ + log_info(LD_GENERAL, "Got a line from managed proxy '%s': (%s)", + mp->argv[0], line); + + if (!strcmpstart(line, PROTO_ENV_ERROR)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + parse_env_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_FAIL)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + log_warn(LD_CONFIG, "Managed proxy could not pick a " + "configuration protocol version."); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_SUCCESS)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + if (parse_version(line,mp) < 0) + goto err; + + tor_assert(mp->conf_protocol != 0); + mp->conf_state = PT_PROTO_ACCEPTING_METHODS; + return; + } else if (!strcmpstart(line, PROTO_CMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_SMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_CMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_client_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_SMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_server_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_CMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_cmethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, PROTO_SMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_smethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, PROTO_PROXY_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (mp->proxy_uri) { + mp->proxy_supported = 1; + return; + } + + /* No proxy was configured, this should log */ + } else if (!strcmpstart(line, PROTO_PROXY_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_proxy_error(line); + goto err; + } else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) { + /* managed proxy launch failed: parse error message to learn why. */ + int retval, child_state, saved_errno; + retval = tor_sscanf(line, SPAWN_ERROR_MESSAGE "%x/%x", + &child_state, &saved_errno); + if (retval == 2) { + log_warn(LD_GENERAL, + "Could not launch managed proxy executable at '%s' ('%s').", + mp->argv[0], strerror(saved_errno)); + } else { /* failed to parse error message */ + log_warn(LD_GENERAL,"Could not launch managed proxy executable at '%s'.", + mp->argv[0]); + } + + mp->conf_state = PT_PROTO_FAILED_LAUNCH; + return; + } + + log_notice(LD_GENERAL, "Unknown line received by managed proxy (%s).", line); + return; + + err: + mp->conf_state = PT_PROTO_BROKEN; + log_warn(LD_CONFIG, "Managed proxy at '%s' failed the configuration protocol" + " and will be destroyed.", mp->argv[0]); +} + +/** Parses an ENV-ERROR <b>line</b> and warns the user accordingly. */ +STATIC void +parse_env_error(const char *line) +{ + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(PROTO_ENV_ERROR) + 2)) + log_notice(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", PROTO_ENV_ERROR); + + log_warn(LD_CONFIG, "Managed proxy couldn't understand the " + "pluggable transport environment variables. (%s)", + line+strlen(PROTO_ENV_ERROR)+1); +} + +/** Handles a VERSION <b>line</b>. Updates the configuration protocol + * version in <b>mp</b>. */ +STATIC int +parse_version(const char *line, managed_proxy_t *mp) +{ + if (strlen(line) < (strlen(PROTO_NEG_SUCCESS) + 2)) { + log_warn(LD_CONFIG, "Managed proxy sent us malformed %s line.", + PROTO_NEG_SUCCESS); + return -1; + } + + if (strcmp("1", line+strlen(PROTO_NEG_SUCCESS)+1)) { /* hardcoded temp */ + log_warn(LD_CONFIG, "Managed proxy tried to negotiate on version '%s'. " + "We only support version '1'", line+strlen(PROTO_NEG_SUCCESS)+1); + return -1; + } + + mp->conf_protocol = PROTO_VERSION_ONE; /* temp. till more versions appear */ + return 0; +} + +/** Parses {C,S}METHOD-ERROR <b>line</b> and warns the user + * accordingly. If <b>is_server</b> it is an SMETHOD-ERROR, + * otherwise it is a CMETHOD-ERROR. */ +static void +parse_method_error(const char *line, int is_server) +{ + const char* error = is_server ? + PROTO_SMETHOD_ERROR : PROTO_CMETHOD_ERROR; + + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(error) + 2)) + log_warn(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", error); + + log_warn(LD_CONFIG, "%s managed proxy encountered a method error. (%s)", + is_server ? "Server" : "Client", + line+strlen(error)+1); +} + +/** A helper for parse_{c,s}method_line(), bootstraps its + * functionalities. If <b>is_smethod</b> is true then the + * the line to parse is a SMETHOD line otherwise it is a + * CMETHOD line*/ +static int +parse_method_line_helper(const char *line, + managed_proxy_t *mp, + int is_smethod) +{ + int item_index = 0; + int r; + + char *transport_name=NULL; + char *args_string=NULL; + char *addrport=NULL; + int socks_ver=PROXY_NONE; + char *address=NULL; + uint16_t port = 0; + + const char *method_str = is_smethod ? PROTO_SMETHOD : PROTO_CMETHOD; + const int min_args_count = is_smethod ? 3 : 4; + + tor_addr_t tor_addr; + transport_t *transport=NULL; + smartlist_t *items= smartlist_new(); + + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < min_args_count) { + log_warn(LD_CONFIG, "Managed proxy sent us a %s line " + "with too few arguments.", method_str); + goto err; + } + + tor_assert(!strcmp(smartlist_get(items, item_index),method_str)); + ++item_index; + + transport_name = smartlist_get(items,item_index); + ++item_index; + if (!string_is_C_identifier(transport_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + transport_name); + goto err; + } + + /** Check for the proxy method sent to us in CMETHOD line. */ + if (!is_smethod) { + const char *socks_ver_str = smartlist_get(items,item_index); + ++item_index; + + if (!strcmp(socks_ver_str,"socks4")) { + socks_ver = PROXY_SOCKS4; + } else if (!strcmp(socks_ver_str,"socks5")) { + socks_ver = PROXY_SOCKS5; + } else { + log_warn(LD_CONFIG, "Client managed proxy sent us a proxy protocol " + "we don't recognize. (%s)", socks_ver_str); + goto err; + } + } + + addrport = smartlist_get(items, item_index); + ++item_index; + if (tor_addr_port_split(LOG_WARN, addrport, &address, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport address '%s'", addrport); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + if (tor_addr_parse(&tor_addr, address) < 0) { + log_warn(LD_CONFIG, "Error parsing transport address '%s'", address); + goto err; + } + + /** Check for options in the SMETHOD line. */ + if (is_smethod && smartlist_len(items) > min_args_count) { + /* Seems like there are also some [options] in the SMETHOD line. + Let's see if we can parse them. */ + char *options_string = smartlist_get(items, item_index); + log_debug(LD_CONFIG, "Got options_string: %s", options_string); + if (!strcmpstart(options_string, "ARGS:")) { + args_string = options_string+strlen("ARGS:"); + log_debug(LD_CONFIG, "Got ARGS: %s", args_string); + } + } + + transport = transport_new(&tor_addr, port, transport_name, + socks_ver, args_string); + + smartlist_add(mp->transports, transport); + + /** Logs info about line parsing success for client or server */ + if (is_smethod) { + log_info(LD_CONFIG, "Server transport %s at %s:%d.", + transport_name, address, (int)port); + } else { + log_info(LD_CONFIG, "Transport %s at %s:%d with SOCKS %d. " + "Attached to managed proxy.", + transport_name, address, (int)port, socks_ver); + } + + r=0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + tor_free(address); + return r; +} + +/** Parses an SMETHOD <b>line</b> and if well-formed it registers the + * new transport in <b>mp</b>. */ +STATIC int +parse_smethod_line(const char *line, managed_proxy_t *mp) +{ + /* Example of legit SMETHOD line: + SMETHOD obfs2 0.0.0.0:25612 ARGS:secret=supersekrit,key=superkey */ + return parse_method_line_helper(line, mp, 1); +} + +/** Parses a CMETHOD <b>line</b>, and if well-formed it registers + * the new transport in <b>mp</b>. */ +STATIC int +parse_cmethod_line(const char *line, managed_proxy_t *mp) +{ + /* Example of legit CMETHOD line: + CMETHOD obfs2 socks5 127.0.0.1:35713 */ + return parse_method_line_helper(line, mp, 0); +} + +/** Parses an PROXY-ERROR <b>line</b> and warns the user accordingly. */ +STATIC void +parse_proxy_error(const char *line) +{ + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(PROTO_PROXY_ERROR) + 2)) + log_notice(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", PROTO_PROXY_ERROR); + + log_warn(LD_CONFIG, "Managed proxy failed to configure the " + "pluggable transport's outgoing proxy. (%s)", + line+strlen(PROTO_PROXY_ERROR)+1); +} + +/** Return a newly allocated string that tor should place in + * TOR_PT_SERVER_TRANSPORT_OPTIONS while configuring the server + * manged proxy in <b>mp</b>. Return NULL if no such options are found. */ +STATIC char * +get_transport_options_for_server_proxy(const managed_proxy_t *mp) +{ + char *options_string = NULL; + smartlist_t *string_sl = smartlist_new(); + + tor_assert(mp->is_server); + + /** Loop over the transports of the proxy. If we have options for + any of them, format them appropriately and place them in our + smartlist. Finally, join our smartlist to get the final + string. */ + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, transport) { + smartlist_t *options_tmp_sl = NULL; + options_tmp_sl = get_options_for_server_transport(transport); + if (!options_tmp_sl) + continue; + + /** Loop over the options of this transport, escape them, and + place them in the smartlist. */ + SMARTLIST_FOREACH_BEGIN(options_tmp_sl, const char *, options) { + char *escaped_opts = tor_escape_str_for_pt_args(options, ":;\\"); + smartlist_add_asprintf(string_sl, "%s:%s", + transport, escaped_opts); + tor_free(escaped_opts); + } SMARTLIST_FOREACH_END(options); + + SMARTLIST_FOREACH(options_tmp_sl, char *, c, tor_free(c)); + smartlist_free(options_tmp_sl); + } SMARTLIST_FOREACH_END(transport); + + if (smartlist_len(string_sl)) { + options_string = smartlist_join_strings(string_sl, ";", 0, NULL); + } + + SMARTLIST_FOREACH(string_sl, char *, t, tor_free(t)); + smartlist_free(string_sl); + + return options_string; +} + +/** Return the string that tor should place in TOR_PT_SERVER_BINDADDR + * while configuring the server managed proxy in <b>mp</b>. The + * string is stored in the heap, and it's the responsibility of + * the caller to deallocate it after its use. */ +static char * +get_bindaddr_for_server_proxy(const managed_proxy_t *mp) +{ + char *bindaddr_result = NULL; + char *bindaddr_tmp = NULL; + smartlist_t *string_tmp = smartlist_new(); + + tor_assert(mp->is_server); + + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, char *, t) { + bindaddr_tmp = get_stored_bindaddr_for_server_transport(t); + + smartlist_add_asprintf(string_tmp, "%s-%s", t, bindaddr_tmp); + + tor_free(bindaddr_tmp); + } SMARTLIST_FOREACH_END(t); + + bindaddr_result = smartlist_join_strings(string_tmp, ",", 0, NULL); + + SMARTLIST_FOREACH(string_tmp, char *, t, tor_free(t)); + smartlist_free(string_tmp); + + return bindaddr_result; +} + +/** Return a newly allocated process_environment_t * for <b>mp</b>'s + * process. */ +static process_environment_t * +create_managed_proxy_environment(const managed_proxy_t *mp) +{ + const or_options_t *options = get_options(); + + /* Environment variables to be added to or set in mp's environment. */ + smartlist_t *envs = smartlist_new(); + /* XXXX The next time someone touches this code, shorten the name of + * set_environment_variable_in_smartlist, add a + * set_env_var_in_smartlist_asprintf function, and get rid of the + * silly extra envs smartlist. */ + + /* The final environment to be passed to mp. */ + smartlist_t *merged_env_vars = get_current_process_environment_variables(); + + process_environment_t *env; + + { + char *state_tmp = get_datadir_fname("pt_state/"); /* XXX temp */ + smartlist_add_asprintf(envs, "TOR_PT_STATE_LOCATION=%s", state_tmp); + tor_free(state_tmp); + } + + smartlist_add_strdup(envs, "TOR_PT_MANAGED_TRANSPORT_VER=1"); + + { + char *transports_to_launch = + smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL); + + smartlist_add_asprintf(envs, + mp->is_server ? + "TOR_PT_SERVER_TRANSPORTS=%s" : + "TOR_PT_CLIENT_TRANSPORTS=%s", + transports_to_launch); + + tor_free(transports_to_launch); + } + + if (mp->is_server) { + { + char *orport_tmp = + get_first_listener_addrport_string(CONN_TYPE_OR_LISTENER); + if (orport_tmp) { + smartlist_add_asprintf(envs, "TOR_PT_ORPORT=%s", orport_tmp); + tor_free(orport_tmp); + } + } + + { + char *bindaddr_tmp = get_bindaddr_for_server_proxy(mp); + smartlist_add_asprintf(envs, "TOR_PT_SERVER_BINDADDR=%s", bindaddr_tmp); + tor_free(bindaddr_tmp); + } + + { + char *server_transport_options = + get_transport_options_for_server_proxy(mp); + if (server_transport_options) { + smartlist_add_asprintf(envs, "TOR_PT_SERVER_TRANSPORT_OPTIONS=%s", + server_transport_options); + tor_free(server_transport_options); + } + } + + /* XXXX Remove the '=' here once versions of obfsproxy which + * assert that this env var exists are sufficiently dead. + * + * (If we remove this line entirely, some joker will stick this + * variable in Tor's environment and crash PTs that try to parse + * it even when not run in server mode.) */ + + if (options->ExtORPort_lines) { + char *ext_or_addrport_tmp = + get_first_listener_addrport_string(CONN_TYPE_EXT_OR_LISTENER); + char *cookie_file_loc = get_ext_or_auth_cookie_file_name(); + + if (ext_or_addrport_tmp) { + smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT=%s", + ext_or_addrport_tmp); + } + smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s", + cookie_file_loc); + + tor_free(ext_or_addrport_tmp); + tor_free(cookie_file_loc); + + } 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. + */ + + if (mp->proxy_uri) { + smartlist_add_asprintf(envs, "TOR_PT_PROXY=%s", mp->proxy_uri); + } + } + + SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) { + set_environment_variable_in_smartlist(merged_env_vars, env_var, + tor_free_, 1); + } SMARTLIST_FOREACH_END(env_var); + + env = process_environment_make(merged_env_vars); + + smartlist_free(envs); + + SMARTLIST_FOREACH(merged_env_vars, void *, x, tor_free(x)); + smartlist_free(merged_env_vars); + + return env; +} + +/** Create and return a new managed proxy for <b>transport</b> using + * <b>proxy_argv</b>. Also, add it to the global managed proxy list. If + * <b>is_server</b> is true, it's a server managed proxy. Takes ownership of + * <b>proxy_argv</b>. + * + * Requires that proxy_argv have at least one element. */ +STATIC managed_proxy_t * +managed_proxy_create(const smartlist_t *with_transport_list, + char **proxy_argv, int is_server) +{ + managed_proxy_t *mp = tor_malloc_zero(sizeof(managed_proxy_t)); + mp->conf_state = PT_PROTO_INFANT; + mp->is_server = is_server; + mp->argv = proxy_argv; + mp->transports = smartlist_new(); + mp->proxy_uri = get_pt_proxy_uri(); + + mp->transports_to_launch = smartlist_new(); + SMARTLIST_FOREACH(with_transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + + /* register the managed proxy */ + if (!managed_proxy_list) + managed_proxy_list = smartlist_new(); + smartlist_add(managed_proxy_list, mp); + unconfigured_proxies_n++; + + assert_unconfigured_count_ok(); + + return mp; +} + +/** Register proxy with <b>proxy_argv</b>, supporting transports in + * <b>transport_list</b>, to the managed proxy subsystem. + * If <b>is_server</b> is true, then the proxy is a server proxy. + * + * Takes ownership of proxy_argv. + * + * Requires that proxy_argv be a NULL-terminated array of command-line + * elements, containing at least one element. + **/ +MOCK_IMPL(void, +pt_kickstart_proxy, (const smartlist_t *with_transport_list, + char **proxy_argv, int is_server)) +{ + managed_proxy_t *mp=NULL; + transport_t *old_transport = NULL; + + if (!proxy_argv || !proxy_argv[0]) { + return; + } + + mp = get_managed_proxy_by_argv_and_type(proxy_argv, is_server); + + if (!mp) { /* we haven't seen this proxy before */ + managed_proxy_create(with_transport_list, proxy_argv, is_server); + + } else { /* known proxy. add its transport to its transport list */ + if (mp->was_around_before_config_read) { + /* If this managed proxy was around even before we read the + config this time, it means that it was already enabled before + and is not useless and should be kept. If it's marked for + removal, unmark it and make sure that we check whether it + needs to be restarted. */ + if (mp->marked_for_removal) { + mp->marked_for_removal = 0; + check_if_restarts_needed = 1; + } + + /* For each new transport, check if the managed proxy used to + support it before the SIGHUP. If that was the case, make sure + it doesn't get removed because we might reuse it. */ + SMARTLIST_FOREACH_BEGIN(with_transport_list, const char *, transport) { + old_transport = transport_get_by_name(transport); + if (old_transport) + old_transport->marked_for_removal = 0; + } SMARTLIST_FOREACH_END(transport); + } + + SMARTLIST_FOREACH(with_transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + free_execve_args(proxy_argv); + } +} + +/** Frees the array of pointers in <b>arg</b> used as arguments to + execve(2). */ +STATIC void +free_execve_args(char **arg) +{ + char **tmp = arg; + while (*tmp) /* use the fact that the last element of the array is a + NULL pointer to know when to stop freeing */ + tor_free_(*tmp++); + + tor_free(arg); +} + +/** Tor will read its config. + * Prepare the managed proxy list so that proxies not used in the new + * config will shutdown, and proxies that need to spawn different + * transports will do so. */ +void +pt_prepare_proxy_list_for_config_read(void) +{ + if (!managed_proxy_list) + return; + + assert_unconfigured_count_ok(); + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + /* Destroy unconfigured proxies. */ + if (mp->conf_state != PT_PROTO_COMPLETED) { + SMARTLIST_DEL_CURRENT(managed_proxy_list, mp); + managed_proxy_destroy(mp, 1); + unconfigured_proxies_n--; + continue; + } + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + /* Mark all proxies for removal, and also note that they have been + here before the config read. */ + mp->marked_for_removal = 1; + mp->was_around_before_config_read = 1; + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_clear(mp->transports_to_launch); + } SMARTLIST_FOREACH_END(mp); + + assert_unconfigured_count_ok(); + + tor_assert(unconfigured_proxies_n == 0); +} + +/** Return a smartlist containing the ports where our pluggable + * transports are listening. */ +smartlist_t * +get_transport_proxy_ports(void) +{ + smartlist_t *sl = NULL; + + if (!managed_proxy_list) + return NULL; + + /** XXX assume that external proxy ports have been forwarded + manually */ + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, const managed_proxy_t *, mp) { + if (!mp->is_server || mp->conf_state != PT_PROTO_COMPLETED) + continue; + + if (!sl) sl = smartlist_new(); + + tor_assert(mp->transports); + SMARTLIST_FOREACH(mp->transports, const transport_t *, t, + smartlist_add_asprintf(sl, "%u:%u", t->port, t->port)); + + } SMARTLIST_FOREACH_END(mp); + + return sl; +} + +/** Return the pluggable transport string that we should display in + * our extra-info descriptor. If we shouldn't display such a string, + * or we have nothing to display, return NULL. The string is + * allocated on the heap and it's the responsibility of the caller to + * free it. */ +char * +pt_get_extra_info_descriptor_string(void) +{ + char *the_string = NULL; + smartlist_t *string_chunks = NULL; + + if (!managed_proxy_list) + return NULL; + + string_chunks = smartlist_new(); + + /* For each managed proxy, add its transports to the chunks list. */ + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, const managed_proxy_t *, mp) { + if ((!mp->is_server) || (mp->conf_state != PT_PROTO_COMPLETED)) + continue; + + tor_assert(mp->transports); + + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + char *transport_args = NULL; + + /* If the transport proxy returned "0.0.0.0" as its address, and + * we know our external IP address, use it. Otherwise, use the + * returned address. */ + const char *addrport = NULL; + uint32_t external_ip_address = 0; + if (tor_addr_is_null(&t->addr) && + router_pick_published_address(get_options(), + &external_ip_address, 0) >= 0) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, external_ip_address); + addrport = fmt_addrport(&addr, t->port); + } else { + addrport = fmt_addrport(&t->addr, t->port); + } + + /* If this transport has any arguments with it, prepend a space + to them so that we can add them to the transport line. */ + if (t->extra_info_args) + tor_asprintf(&transport_args, " %s", t->extra_info_args); + + smartlist_add_asprintf(string_chunks, + "transport %s %s%s", + t->name, addrport, + transport_args ? transport_args : ""); + tor_free(transport_args); + } SMARTLIST_FOREACH_END(t); + + } SMARTLIST_FOREACH_END(mp); + + if (smartlist_len(string_chunks) == 0) { + smartlist_free(string_chunks); + return NULL; + } + + /* Join all the chunks into the final string. */ + the_string = smartlist_join_strings(string_chunks, "\n", 1, NULL); + + SMARTLIST_FOREACH(string_chunks, char *, s, tor_free(s)); + smartlist_free(string_chunks); + + return the_string; +} + +/** Stringify the SOCKS arguments in <b>socks_args</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_stringify_socks_args(const smartlist_t *socks_args) +{ + /* tmp place to store escaped socks arguments, so that we can + concatenate them up afterwards */ + smartlist_t *sl_tmp = NULL; + char *escaped_string = NULL; + char *new_string = NULL; + + tor_assert(socks_args); + tor_assert(smartlist_len(socks_args) > 0); + + sl_tmp = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(socks_args, const char *, s) { + /* Escape ';' and '\'. */ + escaped_string = tor_escape_str_for_pt_args(s, ";\\"); + if (!escaped_string) + goto done; + + smartlist_add(sl_tmp, escaped_string); + } SMARTLIST_FOREACH_END(s); + + new_string = smartlist_join_strings(sl_tmp, ";", 0, NULL); + + done: + SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s)); + smartlist_free(sl_tmp); + + return new_string; +} + +/** Return a string of the SOCKS arguments that we should pass to the + * pluggable transports proxy in <b>addr</b>:<b>port</b> according to + * 180_pluggable_transport.txt. The string is allocated on the heap + * and it's the responsibility of the caller to free it after use. */ +char * +pt_get_socks_args_for_proxy_addrport(const tor_addr_t *addr, uint16_t port) +{ + const smartlist_t *socks_args = NULL; + + socks_args = get_socks_args_by_bridge_addrport(addr, port); + if (!socks_args) + return NULL; + + return pt_stringify_socks_args(socks_args); +} + +/** The tor config was read. + * Destroy all managed proxies that were marked by a previous call to + * prepare_proxy_list_for_config_read() and are not used by the new + * config. */ +void +sweep_proxy_list(void) +{ + if (!managed_proxy_list) + return; + assert_unconfigured_count_ok(); + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (mp->marked_for_removal) { + SMARTLIST_DEL_CURRENT(managed_proxy_list, mp); + managed_proxy_destroy(mp, 1); + } + } SMARTLIST_FOREACH_END(mp); + assert_unconfigured_count_ok(); +} + +/** Release all storage held by the pluggable transports subsystem. */ +void +pt_free_all(void) +{ + if (transport_list) { + clear_transport_list(); + smartlist_free(transport_list); + transport_list = NULL; + } + + if (managed_proxy_list) { + /* If the proxy is in PT_PROTO_COMPLETED, it has registered its + transports and it's the duty of the circuitbuild.c subsystem to + free them. Otherwise, it hasn't registered its transports yet + and we should free them here. */ + SMARTLIST_FOREACH(managed_proxy_list, managed_proxy_t *, mp, { + SMARTLIST_DEL_CURRENT(managed_proxy_list, mp); + managed_proxy_destroy(mp, 1); + }); + + smartlist_free(managed_proxy_list); + managed_proxy_list=NULL; + } +} + +/** Return a newly allocated string equal to <b>string</b>, except that every + * character in <b>chars_to_escape</b> is preceded by a backslash. */ +char * +tor_escape_str_for_pt_args(const char *string, const char *chars_to_escape) +{ + char *new_string = NULL; + char *new_cp = NULL; + size_t length, new_length; + + tor_assert(string); + + length = strlen(string); + + if (!length) /* If we were given the empty string, return the same. */ + return tor_strdup(""); + /* (new_length > SIZE_MAX) => ((length * 2) + 1 > SIZE_MAX) => + (length*2 > SIZE_MAX - 1) => (length > (SIZE_MAX - 1)/2) */ + if (length > (SIZE_MAX - 1)/2) /* check for overflow */ + return NULL; + + /* this should be enough even if all characters must be escaped */ + new_length = (length * 2) + 1; + + new_string = new_cp = tor_malloc(new_length); + + while (*string) { + if (strchr(chars_to_escape, *string)) + *new_cp++ = '\\'; + + *new_cp++ = *string++; + } + + *new_cp = '\0'; /* NUL-terminate the new string */ + + return new_string; +} |