mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-11 05:33:47 +01:00
Merge branch 'bug8929_rebase_2'
This commit is contained in:
commit
b551988ef4
4
changes/bug8929
Normal file
4
changes/bug8929
Normal file
@ -0,0 +1,4 @@
|
||||
o Minor features:
|
||||
- Add a new torrc option "ServerTransportOptions" that allows
|
||||
bridge operators to pass configuration parameters to their
|
||||
pluggable transports. Resolves ticket 8929.
|
@ -186,6 +186,11 @@ GENERAL OPTIONS
|
||||
listening address of any pluggable transport proxy that tries to
|
||||
launch __transport__.
|
||||
|
||||
**ServerTransportOptions** __transport__ __k=v__ __k=v__ ...::
|
||||
When this option is set, Tor will pass the __k=v__ parameters to
|
||||
any pluggable transport proxy that tries to launch __transport__. +
|
||||
(Example: ServerTransportOptions obfs45 shared-secret=bridgepasswd cache=/var/lib/tor/cache)
|
||||
|
||||
**ConnLimit** __NUM__::
|
||||
The minimum number of file descriptors that must be available to the Tor
|
||||
process before it will start. Tor will ask the OS for as many file
|
||||
|
@ -1223,17 +1223,14 @@ escaped(const char *s)
|
||||
return escaped_val_;
|
||||
}
|
||||
|
||||
/** Escape every ";" or "\" character of <b>string</b>. Use
|
||||
* <b>escape_char</b> as the character to use for escaping.
|
||||
* The returned string is allocated on the heap and it's the
|
||||
* responsibility of the caller to free it. */
|
||||
/** 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_socks_arg(const char *string)
|
||||
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;
|
||||
static const char *chars_to_escape = ";\\";
|
||||
|
||||
tor_assert(string);
|
||||
|
||||
|
@ -231,7 +231,8 @@ int tor_digest256_is_zero(const char *digest);
|
||||
char *esc_for_log(const char *string) ATTR_MALLOC;
|
||||
const char *escaped(const char *string);
|
||||
|
||||
char *tor_escape_str_for_socks_arg(const char *string);
|
||||
char *tor_escape_str_for_pt_args(const char *string,
|
||||
const char *chars_to_escape);
|
||||
|
||||
struct smartlist_t;
|
||||
int tor_vsscanf(const char *buf, const char *pattern, va_list ap) \
|
||||
|
@ -280,6 +280,7 @@ static config_var_t option_vars_[] = {
|
||||
V(IPv6Exit, BOOL, "0"),
|
||||
VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL),
|
||||
V(ServerTransportListenAddr, LINELIST, NULL),
|
||||
V(ServerTransportOptions, LINELIST, NULL),
|
||||
V(Socks4Proxy, STRING, NULL),
|
||||
V(Socks5Proxy, STRING, NULL),
|
||||
V(Socks5ProxyUsername, STRING, NULL),
|
||||
@ -3147,6 +3148,19 @@ options_validate(or_options_t *old_options, or_options_t *options,
|
||||
"ServerTransportListenAddr line will be ignored.");
|
||||
}
|
||||
|
||||
for (cl = options->ServerTransportOptions; cl; cl = cl->next) {
|
||||
/** If get_options_from_transport_options_line() fails with
|
||||
'transport' being NULL, it means that something went wrong
|
||||
while parsing the ServerTransportOptions line. */
|
||||
smartlist_t *options_sl =
|
||||
get_options_from_transport_options_line(cl->value, NULL);
|
||||
if (!options_sl)
|
||||
REJECT("ServerTransportOptions did not parse. See logs for details.");
|
||||
|
||||
SMARTLIST_FOREACH(options_sl, char *, cp, tor_free(cp));
|
||||
smartlist_free(options_sl);
|
||||
}
|
||||
|
||||
if (options->ConstrainedSockets) {
|
||||
/* If the user wants to constrain socket buffer use, make sure the desired
|
||||
* limit is between MIN|MAX_TCPSOCK_BUFFER in k increments. */
|
||||
@ -4580,6 +4594,63 @@ get_bindaddr_from_transport_listen_line(const char *line,const char *transport)
|
||||
return addrport;
|
||||
}
|
||||
|
||||
/** Given a ServerTransportOptions <b>line</b>, return a smartlist
|
||||
* with the options. Return NULL if the line was not well-formed.
|
||||
*
|
||||
* If <b>transport</b> is set, return NULL if the line is not
|
||||
* referring to <b>transport</b>.
|
||||
*
|
||||
* The returned smartlist and its strings are allocated on the heap
|
||||
* and it's the responsibility of the caller to free it. */
|
||||
smartlist_t *
|
||||
get_options_from_transport_options_line(const char *line,const char *transport)
|
||||
{
|
||||
smartlist_t *items = smartlist_new();
|
||||
smartlist_t *options = smartlist_new();
|
||||
const char *parsed_transport = NULL;
|
||||
|
||||
smartlist_split_string(items, line, NULL,
|
||||
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
|
||||
|
||||
if (smartlist_len(items) < 2) {
|
||||
log_warn(LD_CONFIG,"Too few arguments on ServerTransportOptions line.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
parsed_transport = smartlist_get(items, 0);
|
||||
/* If 'transport' is given, check if it matches the one on the line */
|
||||
if (transport && strcmp(transport, parsed_transport))
|
||||
goto err;
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(items, const char *, option) {
|
||||
if (option_sl_idx == 0) /* skip the transport field (first field)*/
|
||||
continue;
|
||||
|
||||
/* validate that it's a k=v value */
|
||||
if (!string_is_key_value(LOG_WARN, option)) {
|
||||
log_warn(LD_CONFIG, "%s is not a k=v value.", escaped(option));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* add it to the options smartlist */
|
||||
smartlist_add(options, tor_strdup(option));
|
||||
log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option));
|
||||
} SMARTLIST_FOREACH_END(option);
|
||||
|
||||
goto done;
|
||||
|
||||
err:
|
||||
SMARTLIST_FOREACH(options, char*, s, tor_free(s));
|
||||
smartlist_free(options);
|
||||
options = NULL;
|
||||
|
||||
done:
|
||||
SMARTLIST_FOREACH(items, char*, s, tor_free(s));
|
||||
smartlist_free(items);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/** Given the name of a pluggable transport in <b>transport</b>, check
|
||||
* the configuration file to see if the user has explicitly asked for
|
||||
* it to listen on a specific port. Return a <address:port> string if
|
||||
@ -4600,6 +4671,26 @@ get_transport_bindaddr_from_config(const char *transport)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Given the name of a pluggable transport in <b>transport</b>, check
|
||||
* the configuration file to see if the user has asked us to pass any
|
||||
* parameters to the pluggable transport. Return a smartlist
|
||||
* containing the parameters, otherwise NULL. */
|
||||
smartlist_t *
|
||||
get_options_for_server_transport(const char *transport)
|
||||
{
|
||||
config_line_t *cl;
|
||||
const or_options_t *options = get_options();
|
||||
|
||||
for (cl = options->ServerTransportOptions; cl; cl = cl->next) {
|
||||
smartlist_t *options_sl =
|
||||
get_options_from_transport_options_line(cl->value, transport);
|
||||
if (options_sl)
|
||||
return options_sl;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Read the contents of a ServerTransportPlugin line from
|
||||
* <b>line</b>. Return 0 if the line is well-formed, and -1 if it
|
||||
* isn't.
|
||||
|
@ -112,6 +112,9 @@ typedef struct bridge_line_t {
|
||||
|
||||
void bridge_line_free(bridge_line_t *bridge_line);
|
||||
bridge_line_t *parse_bridge_line(const char *line);
|
||||
smartlist_t *get_options_from_transport_options_line(const char *line,
|
||||
const char *transport);
|
||||
smartlist_t *get_options_for_server_transport(const char *transport);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -3500,6 +3500,9 @@ typedef struct {
|
||||
/** List of TCP/IP addresses that transports should listen at. */
|
||||
config_line_t *ServerTransportListenAddr;
|
||||
|
||||
/** List of options that must be passed to pluggable transports. */
|
||||
config_line_t *ServerTransportOptions;
|
||||
|
||||
int BridgeRelay; /**< Boolean: are we acting as a bridge relay? We make
|
||||
* this explicit so we can change how we behave in the
|
||||
* future. */
|
||||
|
@ -102,9 +102,6 @@ create_managed_proxy_environment(const managed_proxy_t *mp);
|
||||
|
||||
static INLINE int proxy_configuration_finished(const managed_proxy_t *mp);
|
||||
|
||||
static void managed_proxy_destroy(managed_proxy_t *mp,
|
||||
int also_terminate_process);
|
||||
|
||||
static void handle_finished_proxy(managed_proxy_t *mp);
|
||||
static void configure_proxy(managed_proxy_t *mp);
|
||||
|
||||
@ -694,7 +691,7 @@ register_proxy(const managed_proxy_t *mp)
|
||||
}
|
||||
|
||||
/** Free memory allocated by managed proxy <b>mp</b>. */
|
||||
static void
|
||||
STATIC void
|
||||
managed_proxy_destroy(managed_proxy_t *mp,
|
||||
int also_terminate_process)
|
||||
{
|
||||
@ -1100,6 +1097,50 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp)
|
||||
return r;
|
||||
}
|
||||
|
||||
/** 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 the responsibility of
|
||||
@ -1181,6 +1222,16 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX024 Remove the '=' here once versions of obfsproxy which
|
||||
* assert that this env var exists are sufficiently dead.
|
||||
*
|
||||
@ -1211,7 +1262,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
|
||||
* <b>proxy_argv</b>.
|
||||
*
|
||||
* Requires that proxy_argv have at least one element. */
|
||||
static managed_proxy_t *
|
||||
STATIC managed_proxy_t *
|
||||
managed_proxy_create(const smartlist_t *transport_list,
|
||||
char **proxy_argv, int is_server)
|
||||
{
|
||||
@ -1440,7 +1491,7 @@ pt_stringify_socks_args(const smartlist_t *socks_args)
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(socks_args, const char *, s) {
|
||||
/* Escape ';' and '\'. */
|
||||
escaped_string = tor_escape_str_for_socks_arg(s);
|
||||
escaped_string = tor_escape_str_for_pt_args(s, ";\\");
|
||||
if (!escaped_string)
|
||||
goto done;
|
||||
|
||||
|
@ -110,6 +110,12 @@ STATIC int parse_smethod_line(const char *line, managed_proxy_t *mp);
|
||||
STATIC int parse_version(const char *line, managed_proxy_t *mp);
|
||||
STATIC void parse_env_error(const char *line);
|
||||
STATIC void handle_proxy_line(const char *line, managed_proxy_t *mp);
|
||||
STATIC char *get_transport_options_for_server_proxy(const managed_proxy_t *mp);
|
||||
|
||||
STATIC void managed_proxy_destroy(managed_proxy_t *mp,
|
||||
int also_terminate_process);
|
||||
STATIC managed_proxy_t *managed_proxy_create(const smartlist_t *transport_list,
|
||||
char **proxy_argv, int is_server);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -438,12 +438,84 @@ test_config_parse_bridge_line(void *arg)
|
||||
"aa=b");
|
||||
}
|
||||
|
||||
static void
|
||||
test_config_parse_transport_options_line(void *arg)
|
||||
{
|
||||
smartlist_t *options_sl = NULL, *sl_tmp = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
{ /* too small line */
|
||||
options_sl = get_options_from_transport_options_line("valley", NULL);
|
||||
test_assert(!options_sl);
|
||||
}
|
||||
|
||||
{ /* no k=v values */
|
||||
options_sl = get_options_from_transport_options_line("hit it!", NULL);
|
||||
test_assert(!options_sl);
|
||||
}
|
||||
|
||||
{ /* correct line, but wrong transport specified */
|
||||
options_sl =
|
||||
get_options_from_transport_options_line("trebuchet k=v", "rook");
|
||||
test_assert(!options_sl);
|
||||
}
|
||||
|
||||
{ /* correct -- no transport specified */
|
||||
sl_tmp = smartlist_new();
|
||||
smartlist_add_asprintf(sl_tmp, "ladi=dadi");
|
||||
smartlist_add_asprintf(sl_tmp, "weliketo=party");
|
||||
|
||||
options_sl =
|
||||
get_options_from_transport_options_line("rook ladi=dadi weliketo=party",
|
||||
NULL);
|
||||
test_assert(options_sl);
|
||||
test_assert(smartlist_strings_eq(options_sl, sl_tmp));
|
||||
|
||||
SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
|
||||
smartlist_free(sl_tmp);
|
||||
sl_tmp = NULL;
|
||||
SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s));
|
||||
smartlist_free(options_sl);
|
||||
options_sl = NULL;
|
||||
}
|
||||
|
||||
{ /* correct -- correct transport specified */
|
||||
sl_tmp = smartlist_new();
|
||||
smartlist_add_asprintf(sl_tmp, "ladi=dadi");
|
||||
smartlist_add_asprintf(sl_tmp, "weliketo=party");
|
||||
|
||||
options_sl =
|
||||
get_options_from_transport_options_line("rook ladi=dadi weliketo=party",
|
||||
"rook");
|
||||
test_assert(options_sl);
|
||||
test_assert(smartlist_strings_eq(options_sl, sl_tmp));
|
||||
SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
|
||||
smartlist_free(sl_tmp);
|
||||
sl_tmp = NULL;
|
||||
SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s));
|
||||
smartlist_free(options_sl);
|
||||
options_sl = NULL;
|
||||
}
|
||||
|
||||
done:
|
||||
if (options_sl) {
|
||||
SMARTLIST_FOREACH(options_sl, char *, s, tor_free(s));
|
||||
smartlist_free(options_sl);
|
||||
}
|
||||
if (sl_tmp) {
|
||||
SMARTLIST_FOREACH(sl_tmp, char *, s, tor_free(s));
|
||||
smartlist_free(sl_tmp);
|
||||
}
|
||||
}
|
||||
|
||||
#define CONFIG_TEST(name, flags) \
|
||||
{ #name, test_config_ ## name, flags, NULL, NULL }
|
||||
|
||||
struct testcase_t config_tests[] = {
|
||||
CONFIG_TEST(addressmap, 0),
|
||||
CONFIG_TEST(parse_bridge_line, 0),
|
||||
CONFIG_TEST(parse_transport_options_line, 0),
|
||||
CONFIG_TEST(check_or_create_data_subdir, TT_FORK),
|
||||
CONFIG_TEST(write_to_data_subdir, TT_FORK),
|
||||
END_OF_TESTCASES
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "orconfig.h"
|
||||
#define PT_PRIVATE
|
||||
#include "or.h"
|
||||
#include "config.h"
|
||||
#include "confparse.h"
|
||||
#include "transports.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "test.h"
|
||||
@ -107,6 +109,58 @@ test_pt_parsing(void)
|
||||
tor_free(mp);
|
||||
}
|
||||
|
||||
static void
|
||||
test_pt_get_transport_options(void *arg)
|
||||
{
|
||||
char **execve_args;
|
||||
smartlist_t *transport_list = smartlist_new();
|
||||
managed_proxy_t *mp;
|
||||
or_options_t *options = get_options_mutable();
|
||||
char *opt_str = NULL;
|
||||
config_line_t *cl = NULL;
|
||||
(void)arg;
|
||||
|
||||
execve_args = tor_malloc(sizeof(char*)*2);
|
||||
execve_args[0] = tor_strdup("cheeseshop");
|
||||
execve_args[1] = NULL;
|
||||
|
||||
mp = managed_proxy_create(transport_list, execve_args, 1);
|
||||
tt_ptr_op(mp, !=, NULL);
|
||||
opt_str = get_transport_options_for_server_proxy(mp);
|
||||
tt_ptr_op(opt_str, ==, NULL);
|
||||
|
||||
smartlist_add(mp->transports_to_launch, tor_strdup("gruyere"));
|
||||
smartlist_add(mp->transports_to_launch, tor_strdup("roquefort"));
|
||||
smartlist_add(mp->transports_to_launch, tor_strdup("stnectaire"));
|
||||
|
||||
tt_assert(options);
|
||||
|
||||
cl = tor_malloc_zero(sizeof(config_line_t));
|
||||
cl->value = tor_strdup("gruyere melty=10 hardness=se;ven");
|
||||
options->ServerTransportOptions = cl;
|
||||
|
||||
cl = tor_malloc_zero(sizeof(config_line_t));
|
||||
cl->value = tor_strdup("stnectaire melty=4 hardness=three");
|
||||
cl->next = options->ServerTransportOptions;
|
||||
options->ServerTransportOptions = cl;
|
||||
|
||||
cl = tor_malloc_zero(sizeof(config_line_t));
|
||||
cl->value = tor_strdup("pepperjack melty=12 hardness=five");
|
||||
cl->next = options->ServerTransportOptions;
|
||||
options->ServerTransportOptions = cl;
|
||||
|
||||
opt_str = get_transport_options_for_server_proxy(mp);
|
||||
tt_str_op(opt_str, ==,
|
||||
"gruyere:melty=10;gruyere:hardness=se\\;ven;"
|
||||
"stnectaire:melty=4;stnectaire:hardness=three");
|
||||
|
||||
done:
|
||||
tor_free(opt_str);
|
||||
config_free_lines(cl);
|
||||
managed_proxy_destroy(mp, 0);
|
||||
smartlist_free(transport_list);
|
||||
}
|
||||
|
||||
static void
|
||||
test_pt_protocol(void)
|
||||
{
|
||||
@ -159,6 +213,8 @@ test_pt_protocol(void)
|
||||
struct testcase_t pt_tests[] = {
|
||||
PT_LEGACY(parsing),
|
||||
PT_LEGACY(protocol),
|
||||
{ "get_transport_options", test_pt_get_transport_options, TT_FORK,
|
||||
NULL, NULL },
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
||||
|
@ -796,37 +796,37 @@ test_util_expand_filename(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Test tor_escape_str_for_socks_arg(). */
|
||||
/** Test tor_escape_str_for_pt_args(). */
|
||||
static void
|
||||
test_util_escape_string_socks(void)
|
||||
{
|
||||
char *escaped_string = NULL;
|
||||
|
||||
/** Simple backslash escape. */
|
||||
escaped_string = tor_escape_str_for_socks_arg("This is a backslash: \\");
|
||||
escaped_string = tor_escape_str_for_pt_args("This is a backslash: \\",";\\");
|
||||
test_assert(escaped_string);
|
||||
test_streq(escaped_string, "This is a backslash: \\\\");
|
||||
tor_free(escaped_string);
|
||||
|
||||
/** Simple semicolon escape. */
|
||||
escaped_string = tor_escape_str_for_socks_arg("First rule: Do not use ;");
|
||||
escaped_string = tor_escape_str_for_pt_args("First rule:Do not use ;",";\\");
|
||||
test_assert(escaped_string);
|
||||
test_streq(escaped_string, "First rule: Do not use \\;");
|
||||
test_streq(escaped_string, "First rule:Do not use \\;");
|
||||
tor_free(escaped_string);
|
||||
|
||||
/** Empty string. */
|
||||
escaped_string = tor_escape_str_for_socks_arg("");
|
||||
escaped_string = tor_escape_str_for_pt_args("", ";\\");
|
||||
test_assert(escaped_string);
|
||||
test_streq(escaped_string, "");
|
||||
tor_free(escaped_string);
|
||||
|
||||
/** Escape all characters. */
|
||||
escaped_string = tor_escape_str_for_socks_arg(";\\;\\");
|
||||
escaped_string = tor_escape_str_for_pt_args(";\\;\\", ";\\");
|
||||
test_assert(escaped_string);
|
||||
test_streq(escaped_string, "\\;\\\\\\;\\\\");
|
||||
tor_free(escaped_string);
|
||||
|
||||
escaped_string = tor_escape_str_for_socks_arg(";");
|
||||
escaped_string = tor_escape_str_for_pt_args(";", ";\\");
|
||||
test_assert(escaped_string);
|
||||
test_streq(escaped_string, "\\;");
|
||||
tor_free(escaped_string);
|
||||
|
Loading…
Reference in New Issue
Block a user