#define PT_PRIVATE
#include "core/or/or.h"
#include "feature/client/bridges.h"
#include "app/config/config.h"
#include "core/mainloop/connection.h"
#include "core/or/circuitbuild.h"
#include "feature/client/transports.h"
#include "feature/relay/router.h"
#include "feature/relay/relay_find_addr.h"
#include "feature/relay/transport_config.h"
#include "app/config/statefile.h"
#include "core/or/connection_or.h"
#include "feature/relay/ext_orport.h"
#include "feature/control/control_events.h"
#include "lib/encoding/confline.h"
#include "lib/encoding/kvline.h"
#include "lib/process/process.h"
#include "lib/process/env.h"
static smartlist_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)
#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"
#define PROTO_LOG "LOG"
#define PROTO_STATUS "STATUS"
#define PROTO_VERSION_ONE 1
static smartlist_t *transport_list = NULL;
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;
}
void
transport_free_(transport_t *transport)
{
if (!transport)
return;
tor_free(transport->name);
tor_free(transport->extra_info_args);
tor_free(transport);
}
void
mark_transport_list(void)
{
if (!transport_list)
transport_list = smartlist_new();
SMARTLIST_FOREACH(transport_list, transport_t *, t,
t->marked_for_removal = 1);
}
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);
}
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);
}
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;
}
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;
}
static int
transport_resolve_conflicts(const transport_t *t)
{
transport_t *t_tmp = transport_get_by_name(t->name);
if (t_tmp) {
if (tor_addr_eq(&t->addr, &t_tmp->addr) && (t->port == t_tmp->port)) {
t_tmp->marked_for_removal = 0;
return 1;
} else {
char *new_transport_addrport =
tor_strdup(fmt_addrport(&t->addr, t->port));
if (t_tmp->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 {
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;
}
static int
transport_add(transport_t *t)
{
int r;
tor_assert(t);
r = transport_resolve_conflicts(t);
switch (r) {
case 0:
if (!transport_list)
transport_list = smartlist_new();
smartlist_add(transport_list, t);
return 0;
default:
return r;
}
}
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);
return 0;
case 0:
log_info(LD_GENERAL, "Successfully registered transport %s at %s.",
t->name, fmt_addrport(&t->addr, t->port));
return 0;
}
}
static smartlist_t *managed_proxy_list = NULL;
static int unconfigured_proxies_n = 0;
static int check_if_restarts_needed = 0;
bool
managed_proxy_has_transport(const char *transport_name)
{
tor_assert(transport_name);
if (!managed_proxy_list) {
return false;
}
SMARTLIST_FOREACH_BEGIN(managed_proxy_list, const managed_proxy_t *, mp) {
SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, name) {
if (!strcasecmp(name, transport_name)) {
return true;
}
} SMARTLIST_FOREACH_END(name);
} SMARTLIST_FOREACH_END(mp);
return false;
}
int
pt_proxies_configuration_pending(void)
{
return unconfigured_proxies_n || check_if_restarts_needed;
}
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));
}
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;
}
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;
}
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);
}
static int
proxy_needs_restart(const managed_proxy_t *mp)
{
int ret = 1;
char* proxy_uri;
proxy_uri = get_pt_proxy_uri();
if (strcmp_opt(proxy_uri, mp->proxy_uri) != 0)
goto needs_restart;
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;
}
static void
proxy_prepare_for_restart(managed_proxy_t *mp)
{
transport_t *t_tmp = NULL;
tor_assert(mp->conf_state == PT_PROTO_COMPLETED);
process_set_data(mp->process, NULL);
process_terminate(mp->process);
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();
SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t));
smartlist_clear(mp->transports);
tor_free(mp->proxy_uri);
mp->proxy_uri = get_pt_proxy_uri();
mp->proxy_supported = 0;
mp->conf_state = PT_PROTO_INFANT;
unconfigured_proxies_n++;
}
static int
launch_managed_proxy(managed_proxy_t *mp)
{
tor_assert(mp);
smartlist_t *env = create_managed_proxy_environment(mp);
process_set_data(mp->process, mp);
process_set_stdout_read_callback(mp->process, managed_proxy_stdout_callback);
process_set_stderr_read_callback(mp->process, managed_proxy_stderr_callback);
process_set_exit_callback(mp->process, managed_proxy_exit_callback);
process_set_protocol(mp->process, PROCESS_PROTOCOL_LINE);
process_reset_environment(mp->process, env);
SMARTLIST_FOREACH(env, char *, x, tor_free(x));
smartlist_free(env);
for (int i = 1; mp->argv[i] != NULL; ++i)
process_append_argument(mp->process, mp->argv[i]);
if (process_exec(mp->process) != PROCESS_STATUS_RUNNING) {
log_warn(LD_CONFIG, "Managed proxy at '%s' failed at launch.",
mp->argv[0]);
return -1;
}
log_info(LD_CONFIG,
"Managed proxy at '%s' has spawned with PID '%" PRIu64 "'.",
mp->argv[0], process_get_pid(mp->process));
mp->conf_state = PT_PROTO_LAUNCHED;
return 0;
}
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);
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) {
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 {
log_info(LD_GENERAL, "Nothing changed for managed proxy '%s' after "
"HUP: not restarting.", mp->argv[0]);
}
continue;
}
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");
}
STATIC int
configure_proxy(managed_proxy_t *mp)
{
if (mp->conf_state == PT_PROTO_INFANT) {
if (launch_managed_proxy(mp) < 0) {
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);
return mp->conf_state == PT_PROTO_COMPLETED;
}
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);
}
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);
}
static inline void
register_proxy(const managed_proxy_t *mp)
{
if (mp->is_server)
register_server_proxy(mp);
else
register_client_proxy(mp);
}
STATIC void
managed_proxy_destroy(managed_proxy_t *mp,
int also_terminate_process)
{
SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t));
smartlist_free(mp->transports);
SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t));
smartlist_free(mp->transports_to_launch);
if (managed_proxy_list)
smartlist_remove(managed_proxy_list, mp);
free_execve_args(mp->argv);
tor_free(mp->proxy_uri);
if (also_terminate_process && mp->process) {
process_set_data(mp->process, NULL);
process_terminate(mp->process);
}
tor_free(mp);
}
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;
}
static void
handle_finished_proxy(managed_proxy_t *mp)
{
switch (mp->conf_state) {
case PT_PROTO_BROKEN:
managed_proxy_destroy(mp, 1);
break;
case PT_PROTO_FAILED_LAUNCH:
managed_proxy_destroy(mp, 0);
break;
case PT_PROTO_CONFIGURED:
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);
break;
}
register_proxy(mp);
mp->conf_state = PT_PROTO_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--;
}
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);
}
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]);
}
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;
}
} 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, PROTO_LOG " ")) {
parse_log_line(line, mp);
return;
} else if (!strcmpstart(line, PROTO_STATUS " ")) {
parse_status_line(line, mp);
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]);
}
STATIC void
parse_env_error(const char *line)
{
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);
}
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)) {
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;
return 0;
}
static void
parse_method_error(const char *line, int is_server)
{
const char* error = is_server ?
PROTO_SMETHOD_ERROR : PROTO_CMETHOD_ERROR;
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);
}
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;
}
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;
}
if (is_smethod && smartlist_len(items) > min_args_count) {
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);
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;
}
STATIC int
parse_smethod_line(const char *line, managed_proxy_t *mp)
{
return parse_method_line_helper(line, mp, 1);
}
STATIC int
parse_cmethod_line(const char *line, managed_proxy_t *mp)
{
return parse_method_line_helper(line, mp, 0);
}
STATIC void
parse_proxy_error(const char *line)
{
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);
}
STATIC void
parse_log_line(const char *line, managed_proxy_t *mp)
{
tor_assert(line);
tor_assert(mp);
config_line_t *values = NULL;
char *log_message = NULL;
if (strlen(line) < (strlen(PROTO_LOG) + 1)) {
log_warn(LD_PT, "Managed proxy sent us a %s line "
"with missing argument.", PROTO_LOG);
goto done;
}
const char *data = line + strlen(PROTO_LOG) + 1;
values = kvline_parse(data, KV_QUOTED);
if (! values) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote an invalid LOG message: %s",
mp->argv[0], data);
goto done;
}
const config_line_t *severity = config_line_find(values, "SEVERITY");
const config_line_t *message = config_line_find(values, "MESSAGE");
if (! message) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line without "
"MESSAGE: %s", mp->argv[0], escaped(data));
goto done;
}
if (! severity) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line without "
"SEVERITY: %s", mp->argv[0], escaped(data));
goto done;
}
int log_severity = managed_proxy_severity_parse(severity->value);
if (log_severity == -1) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote a LOG line with an "
"invalid severity level: %s",
mp->argv[0], severity->value);
goto done;
}
tor_log(log_severity, LD_PT, "Managed proxy \"%s\": %s",
mp->argv[0], message->value);
config_line_prepend(&values, "PT", mp->argv[0]);
log_message = kvline_encode(values, KV_QUOTED);
control_event_pt_log(log_message);
done:
config_free_lines(values);
tor_free(log_message);
}
STATIC void
parse_status_line(const char *line, managed_proxy_t *mp)
{
tor_assert(line);
tor_assert(mp);
config_line_t *values = NULL;
char *status_message = NULL;
if (strlen(line) < (strlen(PROTO_STATUS) + 1)) {
log_warn(LD_PT, "Managed proxy sent us a %s line "
"with missing argument.", PROTO_STATUS);
goto done;
}
const char *data = line + strlen(PROTO_STATUS) + 1;
values = kvline_parse(data, KV_QUOTED);
if (! values) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote an invalid "
"STATUS message: %s", mp->argv[0], escaped(data));
goto done;
}
const config_line_t *type = config_line_find(values, "TRANSPORT");
if (! type) {
log_warn(LD_PT, "Managed proxy \"%s\" wrote a STATUS line without "
"TRANSPORT: %s", mp->argv[0], escaped(data));
goto done;
}
config_line_prepend(&values, "PT", mp->argv[0]);
status_message = kvline_encode(values, KV_QUOTED);
control_event_pt_status(status_message);
done:
config_free_lines(values);
tor_free(status_message);
}
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);
SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, const char *, transport) {
smartlist_t *options_tmp_sl = NULL;
options_tmp_sl = pt_get_options_for_server_transport(transport);
if (!options_tmp_sl)
continue;
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;
}
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;
}
static smartlist_t *
create_managed_proxy_environment(const managed_proxy_t *mp)
{
const or_options_t *options = get_options();
smartlist_t *envs = smartlist_new();
smartlist_t *merged_env_vars = get_current_process_environment_variables();
{
char *state_tmp = get_datadir_fname("pt_state/");
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);
}
}
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);
}
if (cookie_file_loc) {
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=");
}
} else {
if (mp->proxy_uri) {
smartlist_add_asprintf(envs, "TOR_PT_PROXY=%s", mp->proxy_uri);
}
}
smartlist_add_asprintf(envs, "TOR_PT_EXIT_ON_STDIN_CLOSE=1");
{
const tor_addr_t *ipv4_addr = managed_proxy_outbound_address(options,
AF_INET);
if (ipv4_addr) {
char *ipv4_addr_str = tor_addr_to_str_dup(ipv4_addr);
smartlist_add_asprintf(envs,
"TOR_PT_OUTBOUND_BIND_ADDRESS_V4=%s",
ipv4_addr_str);
tor_free(ipv4_addr_str);
}
const tor_addr_t *ipv6_addr = managed_proxy_outbound_address(options,
AF_INET6);
if (ipv6_addr) {
char *ipv6_addr_str = tor_addr_to_str_dup(ipv6_addr);
smartlist_add_asprintf(envs,
"TOR_PT_OUTBOUND_BIND_ADDRESS_V6=[%s]",
ipv6_addr_str);
tor_free(ipv6_addr_str);
}
}
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);
smartlist_free(envs);
return merged_env_vars;
}
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->process = process_new(proxy_argv[0]);
mp->transports_to_launch = smartlist_new();
SMARTLIST_FOREACH(with_transport_list, const char *, transport,
add_transport_to_proxy(transport, mp));
if (!managed_proxy_list)
managed_proxy_list = smartlist_new();
smartlist_add(managed_proxy_list, mp);
unconfigured_proxies_n++;
assert_unconfigured_count_ok();
return mp;
}
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) {
managed_proxy_create(with_transport_list, proxy_argv, is_server);
} else {
if (mp->was_around_before_config_read) {
if (mp->marked_for_removal) {
mp->marked_for_removal = 0;
check_if_restarts_needed = 1;
}
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);
}
}
STATIC void
free_execve_args(char **arg)
{
char **tmp = arg;
while (*tmp)
tor_free_(*tmp++);
tor_free(arg);
}
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) {
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);
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);
}
smartlist_t *
get_transport_proxy_ports(void)
{
smartlist_t *sl = NULL;
if (!managed_proxy_list)
return NULL;
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;
}
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();
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;
const char *addrport = NULL;
if (tor_addr_is_null(&t->addr)) {
tor_addr_t addr;
bool found = relay_find_addr_to_publish(get_options(), AF_INET,
RELAY_FIND_ADDR_NO_FLAG,
&addr);
if (!found) {
found = relay_find_addr_to_publish(get_options(), AF_INET6,
RELAY_FIND_ADDR_NO_FLAG, &addr);
}
if (!found) {
log_err(LD_PT, "Unable to find address for transport %s", t->name);
continue;
}
addrport = fmt_addrport(&addr, t->port);
} else {
addrport = fmt_addrport(&t->addr, t->port);
}
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;
}
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;
}
char *
pt_stringify_socks_args(const smartlist_t *socks_args)
{
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) {
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;
}
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);
}
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();
}
void
pt_free_all(void)
{
if (transport_list) {
clear_transport_list();
smartlist_free(transport_list);
transport_list = NULL;
}
if (managed_proxy_list) {
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;
}
}
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)
return tor_strdup("");
if (length > (SIZE_MAX - 1)/2)
return NULL;
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';
return new_string;
}
STATIC void
managed_proxy_stdout_callback(process_t *process,
const char *line,
size_t size)
{
tor_assert(process);
tor_assert(line);
(void)size;
managed_proxy_t *mp = process_get_data(process);
if (mp == NULL)
return;
handle_proxy_line(line, mp);
if (proxy_configuration_finished(mp))
handle_finished_proxy(mp);
}
STATIC void
managed_proxy_stderr_callback(process_t *process,
const char *line,
size_t size)
{
tor_assert(process);
tor_assert(line);
(void)size;
managed_proxy_t *mp = process_get_data(process);
if (BUG(mp == NULL))
return;
log_info(LD_PT,
"Managed proxy at '%s' reported via standard error: %s",
mp->argv[0], line);
}
STATIC bool
managed_proxy_exit_callback(process_t *process, process_exit_code_t exit_code)
{
tor_assert(process);
log_warn(LD_PT,
"Pluggable Transport process terminated with status code %" PRIu64,
exit_code);
return true;
}
STATIC int
managed_proxy_severity_parse(const char *severity)
{
tor_assert(severity);
if (! strcmp(severity, "debug"))
return LOG_DEBUG;
if (! strcmp(severity, "info"))
return LOG_INFO;
if (! strcmp(severity, "notice"))
return LOG_NOTICE;
if (! strcmp(severity, "warning"))
return LOG_WARN;
if (! strcmp(severity, "error"))
return LOG_ERR;
return -1;
}
STATIC const tor_addr_t *
managed_proxy_outbound_address(const or_options_t *options, sa_family_t family)
{
tor_assert(options);
const tor_addr_t *address = NULL;
int family_index;
switch (family) {
case AF_INET:
family_index = 0;
break;
case AF_INET6:
family_index = 1;
break;
default:
tor_assert_unreached();
return NULL;
}
address = &options->OutboundBindAddresses[OUTBOUND_ADDR_PT][family_index];
if (! tor_addr_is_null(address))
return address;
address = &options->OutboundBindAddresses[OUTBOUND_ADDR_ANY][family_index];
if (! tor_addr_is_null(address))
return address;
return NULL;
}