Commit 05aed5b6 authored by Nick Mathewson's avatar Nick Mathewson 🐻
Browse files

Allow a unix: address to contain a C-style quoted string.

Feature 18753 -- all this to allow spaces.
parent 68450051
o Minor features (unix domain sockets):
- When configuring a unix domain socket for a SocksPort,
ControlPort, or Hidden service, you can now wrap the address
in quotes, using C-style escapes inside the quotes. This
allows unix domain socket paths to contain spaces.
......@@ -323,6 +323,8 @@ GENERAL OPTIONS
any process on the local host to control it. (Setting both authentication
methods means either method is sufficient to authenticate to Tor.) This
option is required for many Tor controllers; most use the value of 9051.
If a unix domain socket is used, you may quote the path using standard
C escape sequences.
Set it to "auto" to have Tor pick a port for you. (Default: 0) +
+
Recognized flags are...
......@@ -1032,7 +1034,9 @@ The following options are useful only for clients (that is, if
applications. Set this to 0 if you don't want to allow application
connections via SOCKS. Set it to "auto" to have Tor pick a port for
you. This directive can be specified multiple times to bind
to multiple addresses/ports. (Default: 9050) +
to multiple addresses/ports. If a unix domain socket is used, you may
quote the path using standard C escape sequences.
(Default: 9050) +
+
NOTE: Although this option allows you to specify an IP address
other than localhost, you should do so only with extreme caution.
......@@ -2342,7 +2346,8 @@ The following options are used to configure a hidden service.
recent HiddenServiceDir. By default, this option maps the virtual port to
the same port on 127.0.0.1 over TCP. You may override the target port,
address, or both by specifying a target of addr, port, addr:port, or
**unix:**__path__. (You can specify an IPv6 target as [addr]:port.)
**unix:**__path__. (You can specify an IPv6 target as [addr]:port. Unix
paths may be quoted, and may use standard C escapes.)
You may also have multiple lines with the same VIRTPORT: when a user
connects to that VIRTPORT, one of the TARGETs from those lines will be
chosen at random.
......
......@@ -2906,7 +2906,7 @@ read_file_to_str, (const char *filename, int flags, struct stat *stat_out))
* provided), and return a pointer to the position in <b>s</b> immediately
* after the string. On failure, return NULL.
*/
static const char *
const char *
unescape_string(const char *s, char **result, size_t *size_out)
{
const char *cp;
......
......@@ -385,6 +385,7 @@ MOCK_DECL_ATTR(char *, read_file_to_str,
char *read_file_to_str_until_eof(int fd, size_t max_bytes_to_read,
size_t *sz_out)
ATTR_MALLOC;
const char *unescape_string(const char *s, char **result, size_t *size_out);
const char *parse_config_line_from_str_verbose(const char *line,
char **key_out, char **value_out,
const char **err_out);
......
......@@ -68,6 +68,9 @@
/* Prefix used to indicate a Unix socket in a FooPort configuration. */
static const char unix_socket_prefix[] = "unix:";
/* Prefix used to indicate a Unix socket with spaces in it, in a FooPort
* configuration. */
static const char unix_q_socket_prefix[] = "unix:\"";
/** A list of abbreviations and aliases to map command-line options, obsolete
* option names, or alternative option names, to their current values. */
......@@ -6288,54 +6291,61 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid_nonlocal)
} SMARTLIST_FOREACH_END(port);
}
#ifdef HAVE_SYS_UN_H
/** Parse the given <b>addrport</b> and set <b>path_out</b> if a Unix socket
* path is found. Return 0 on success. On error, a negative value is
* returned, -ENOENT if no Unix statement found, -EINVAL if the socket path
* is empty and -ENOSYS if AF_UNIX is not supported (see function in the
* #else statement below). */
int
config_parse_unix_port(const char *addrport, char **path_out)
{
tor_assert(path_out);
tor_assert(addrport);
if (strcmpstart(addrport, unix_socket_prefix)) {
/* Not a Unix socket path. */
return -ENOENT;
}
if (strlen(addrport + strlen(unix_socket_prefix)) == 0) {
/* Empty socket path, not very usable. */
return -EINVAL;
}
*path_out = tor_strdup(addrport + strlen(unix_socket_prefix));
return 0;
}
#else /* defined(HAVE_SYS_UN_H) */
/**
* Take a string (<b>line</b>) that begins with either an address:port, a
* port, or an AF_UNIX address, optionally quoted, prefixed with
* "unix:". Parse that line, and on success, set <b>addrport_out</b> to a new
* string containing the beginning portion (without prefix). Iff there was a
* unix: prefix, set <b>is_unix_out</b> to true. On success, also set
* <b>rest_out</b> to point to the part of the line after the address portion.
*
* Return 0 on success, -1 on failure.
*/
int
config_parse_unix_port(const char *addrport, char **path_out)
port_cfg_line_extract_addrport(const char *line,
char **addrport_out,
int *is_unix_out,
const char **rest_out)
{
tor_assert(path_out);
tor_assert(addrport);
tor_assert(line);
tor_assert(addrport_out);
tor_assert(is_unix_out);
tor_assert(rest_out);
line = eat_whitespace(line);
if (!strcmpstart(line, unix_q_socket_prefix)) {
// It starts with unix:"
size_t sz;
*is_unix_out = 1;
*addrport_out = NULL;
line += strlen(unix_socket_prefix); /*No q: Keep the quote */
*rest_out = unescape_string(line, addrport_out, &sz);
if (!*rest_out || (*addrport_out && sz != strlen(*addrport_out))) {
tor_free(*addrport_out);
return -1;
}
*rest_out = eat_whitespace(*rest_out);
return 0;
} else {
// Is there a unix: prefix?
if (!strcmpstart(line, unix_socket_prefix)) {
line += strlen(unix_socket_prefix);
*is_unix_out = 1;
} else {
*is_unix_out = 0;
}
if (strcmpstart(addrport, unix_socket_prefix)) {
/* Not a Unix socket path. */
return -ENOENT;
const char *end = find_whitespace(line);
if (BUG(!end)) {
end = strchr(line, '\0'); // LCOV_EXCL_LINE -- this can't be NULL
}
tor_assert(end && end >= line);
*addrport_out = tor_strndup(line, end - line);
*rest_out = eat_whitespace(end);
return 0;
}
log_warn(LD_CONFIG,
"Port configuration %s is for an AF_UNIX socket, but we have no"
"support available on this platform",
escaped(addrport));
return -ENOSYS;
}
#endif /* defined(HAVE_SYS_UN_H) */
static void
warn_client_dns_cache(const char *option, int disabling)
......@@ -6515,16 +6525,16 @@ parse_port_config(smartlist_t *out,
/* At last we can actually parse the FooPort lines. The syntax is:
* [Addr:](Port|auto) [Options].*/
elts = smartlist_new();
char *addrport = NULL;
for (; ports; ports = ports->next) {
tor_addr_t addr;
int port, ret;
int port;
int sessiongroup = SESSION_GROUP_UNSET;
unsigned isolation = ISO_DEFAULT;
int prefer_no_auth = 0;
int socks_iso_keep_alive = 0;
char *addrport;
uint16_t ptmp=0;
int ok;
/* This must be kept in sync with port_cfg_new's defaults */
......@@ -6538,23 +6548,31 @@ parse_port_config(smartlist_t *out,
relax_dirmode_check = 0,
has_used_unix_socket_only_option = 0;
smartlist_split_string(elts, ports->value, NULL,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (smartlist_len(elts) == 0) {
log_warn(LD_CONFIG, "Invalid %sPort line with no value", portname);
int is_unix_tagged_addr = 0;
const char *rest_of_line = NULL;
if (port_cfg_line_extract_addrport(ports->value,
&addrport, &is_unix_tagged_addr, &rest_of_line)<0) {
log_warn(LD_CONFIG, "Invalid %sPort line with unparsable address",
portname);
goto err;
}
if (strlen(addrport) == 0) {
log_warn(LD_CONFIG, "Invalid %sPort line with no address", portname);
goto err;
}
/* Now parse the addr/port value */
addrport = smartlist_get(elts, 0);
/* Split the remainder... */
smartlist_split_string(elts, rest_of_line, NULL,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
/* Let's start to check if it's a Unix socket path. */
ret = config_parse_unix_port(addrport, &unix_socket_path);
if (ret < 0 && ret != -ENOENT) {
if (ret == -EINVAL) {
log_warn(LD_CONFIG, "Empty Unix socket path.");
}
if (is_unix_tagged_addr) {
#ifndef HAVE_SYS_UN_H
log_warn(LD_CONFIG, "Unix sockets not supported on this system.");
goto err;
#endif
unix_socket_path = addrport;
addrport = NULL;
}
if (unix_socket_path &&
......@@ -6612,9 +6630,6 @@ parse_port_config(smartlist_t *out,
if (use_server_options) {
/* This is a server port; parse advertising options */
SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
if (elt_sl_idx == 0)
continue; /* Skip addr:port */
if (!strcasecmp(elt, "NoAdvertise")) {
no_advertise = 1;
} else if (!strcasecmp(elt, "NoListen")) {
......@@ -6662,8 +6677,6 @@ parse_port_config(smartlist_t *out,
SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
int no = 0, isoflag = 0;
const char *elt_orig = elt;
if (elt_sl_idx == 0)
continue; /* Skip addr:port */
if (!strcasecmpstart(elt, "SessionGroup=")) {
int group = (int)tor_parse_long(elt+strlen("SessionGroup="),
......@@ -6880,6 +6893,7 @@ parse_port_config(smartlist_t *out,
}
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_clear(elts);
tor_free(addrport);
}
if (warn_nonlocal && out) {
......@@ -6903,6 +6917,7 @@ parse_port_config(smartlist_t *out,
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_free(elts);
tor_free(unix_socket_path);
tor_free(addrport);
return retval;
}
......
......@@ -128,7 +128,11 @@ int addressmap_register_auto(const char *from, const char *to,
time_t expires,
addressmap_entry_source_t addrmap_source,
const char **msg);
int config_parse_unix_port(const char *addrport, char **path_out);
int port_cfg_line_extract_addrport(const char *line,
char **addrport_out,
int *is_unix_out,
const char **rest_out);
/** Represents the information stored in a torrc Bridge line. */
typedef struct bridge_line_t {
......
......@@ -347,22 +347,20 @@ rend_service_parse_port_config(const char *string, const char *sep,
int realport = 0;
uint16_t p;
tor_addr_t addr;
const char *addrport;
rend_service_port_config_t *result = NULL;
unsigned int is_unix_addr = 0;
char *socket_path = NULL;
const char *socket_path = NULL;
char *err_msg = NULL;
char *addrport = NULL;
sl = smartlist_new();
smartlist_split_string(sl, string, sep,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (smartlist_len(sl) < 1 || smartlist_len(sl) > 2) {
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) {
if (err_msg_out)
err_msg = tor_strdup("Bad syntax in hidden service port configuration.");
goto err;
}
virtport = (int)tor_parse_long(smartlist_get(sl,0), 10, 1, 65535, NULL,NULL);
if (!virtport) {
if (err_msg_out)
......@@ -371,7 +369,6 @@ rend_service_parse_port_config(const char *string, const char *sep,
goto err;
}
if (smartlist_len(sl) == 1) {
/* No addr:port part; use default. */
realport = virtport;
......@@ -379,17 +376,18 @@ rend_service_parse_port_config(const char *string, const char *sep,
} else {
int ret;
addrport = smartlist_get(sl,1);
ret = config_parse_unix_port(addrport, &socket_path);
if (ret < 0 && ret != -ENOENT) {
if (ret == -EINVAL)
if (err_msg_out)
err_msg = tor_strdup("Empty socket path in hidden service port "
"configuration.");
const char *addrport_element = smartlist_get(sl,1);
const char *rest = NULL;
int is_unix;
ret = port_cfg_line_extract_addrport(addrport_element, &addrport,
&is_unix, &rest);
if (ret < 0) {
tor_asprintf(&err_msg, "Couldn't process address <%s> from hidden service "
"configuration", addrport_element);
goto err;
}
if (socket_path) {
if (is_unix) {
socket_path = addrport;
is_unix_addr = 1;
} else if (strchr(addrport, ':') || strchr(addrport, '.')) {
/* else try it as an IP:port pair if it has a : or . in it */
......@@ -427,10 +425,10 @@ rend_service_parse_port_config(const char *string, const char *sep,
}
err:
tor_free(addrport);
if (err_msg_out) *err_msg_out = err_msg;
SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
smartlist_free(sl);
if (socket_path) tor_free(socket_path);
return result;
}
......
......@@ -3710,6 +3710,145 @@ test_config_default_fallback_dirs(void *arg)
clear_dir_servers();
}
static void
test_config_port_cfg_line_extract_addrport(void *arg)
{
(void)arg;
int unixy = 0;
const char *rest = NULL;
char *a = NULL;
tt_int_op(port_cfg_line_extract_addrport("", &a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("hello", &a, &unixy, &rest),
OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "hello");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport(" flipperwalt gersplut",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "flipperwalt");;
tt_str_op(rest, OP_EQ, "gersplut");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport(" flipperwalt \t gersplut",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "flipperwalt");;
tt_str_op(rest, OP_EQ, "gersplut");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("flipperwalt \t gersplut",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "flipperwalt");;
tt_str_op(rest, OP_EQ, "gersplut");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:flipperwalt \t gersplut",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "flipperwalt");;
tt_str_op(rest, OP_EQ, "gersplut");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("lolol",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:lolol",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:lolol ",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport(" unix:lolol",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("foobar:lolol",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, "foobar:lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport(":lolol",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 0);
tt_str_op(a, OP_EQ, ":lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\"",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\" ",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\" foo ",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lolol");;
tt_str_op(rest, OP_EQ, "foo ");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lol ol\" foo ",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lol ol");;
tt_str_op(rest, OP_EQ, "foo ");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lol\\\" ol\" foo ",
&a, &unixy, &rest), OP_EQ, 0);
tt_int_op(unixy, OP_EQ, 1);
tt_str_op(a, OP_EQ, "lol\" ol");;
tt_str_op(rest, OP_EQ, "foo ");
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lol\\\" ol foo ",
&a, &unixy, &rest), OP_EQ, -1);
tor_free(a);
tt_int_op(port_cfg_line_extract_addrport("unix:\"lol\\0\" ol foo ",
&a, &unixy, &rest), OP_EQ, -1);
tor_free(a);
done:
tor_free(a);
}
static config_line_t *
mock_config_line(const char *key, const char *val)
{
......@@ -4050,6 +4189,49 @@ test_config_parse_port_config__ports__ports_given(void *data)
tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1);
#endif
// Test success with quoted unix: address.
config_free_lines(config_port_valid); config_port_valid = NULL;
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar\" "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
CL_PORT_TAKES_HOSTNAMES);
#ifdef _WIN32
tt_int_op(ret, OP_EQ, -1);
#else
tt_int_op(ret, OP_EQ, 0);
tt_int_op(smartlist_len(slout), OP_EQ, 1);
port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
tt_int_op(port_cfg->entry_cfg.dns_request, OP_EQ, 0);
tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0);
tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1);
#endif
// Test failure with broken quoted unix: address.
config_free_lines(config_port_valid); config_port_valid = NULL;
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
CL_PORT_TAKES_HOSTNAMES);
tt_int_op(ret, OP_EQ, -1);
// Test failure with empty quoted unix: address.
config_free_lines(config_port_valid); config_port_valid = NULL;
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"\" "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
CL_PORT_TAKES_HOSTNAMES);
tt_int_op(ret, OP_EQ, -1);
// Test success with OnionTrafficOnly (no DNS, no ipv4, no ipv6)
config_free_lines(config_port_valid); config_port_valid = NULL;
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
......@@ -4690,6 +4872,15 @@ test_config_parse_port_config__ports__server_options(void *data)
0, CL_PORT_SERVER_OPTIONS);
tt_int_op(ret, OP_EQ, -1);
// Check for failure with empty unix: address.
config_free_lines(config_port_invalid); config_port_invalid = NULL;
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_invalid = mock_config_line("ORPort", "unix:\"\"");
ret = parse_port_config(slout, config_port_invalid, NULL, "ORPort", 0, NULL,
0, CL_PORT_SERVER_OPTIONS);
tt_int_op(ret, OP_EQ, -1);
done:
if (slout)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
......@@ -4719,6 +4910,7 @@ struct testcase_t config_tests[] = {
CONFIG_TEST(write_to_data_subdir, TT_FORK),
CONFIG_TEST(fix_my_family, 0),
CONFIG_TEST(directory_fetch, 0),
CONFIG_TEST(port_cfg_line_extract_addrport, 0),
CONFIG_TEST(parse_port_config__listenaddress, 0),
CONFIG_TEST(parse_port_config__ports__no_ports_given, 0),
CONFIG_TEST(parse_port_config__ports__server_options, 0),
......
......@@ -137,6 +137,8 @@ test_rend_service_parse_port_config(void *arg)
cfg = rend_service_parse_port_config("80,[2001:db8::1]:8080", sep, &err_msg);
tt_assert(cfg);
tt_assert(!err_msg);
rend_service_port_config_free(cfg);
cfg = NULL;
/* XXX: Someone should add tests for AF_UNIX targets if supported. */
......@@ -151,6 +153,53 @@ test_rend_service_parse_port_config(void *arg)
cfg = rend_service_parse_port_config("90001", sep, &err_msg);
tt_assert(!cfg);
tt_assert(err_msg);
tor_free(err_msg);
/* unix port */
cfg = NULL;
/* quoted unix port */
tor_free(err_msg);
cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar\"",
" ", &err_msg);
tt_assert(cfg);
tt_assert(!err_msg);
rend_service_port_config_free(cfg);
cfg = NULL;
/* quoted unix port */
tor_free(err_msg);
cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar\"",
" ", &err_msg);
tt_assert(cfg);
tt_assert(!err_msg);
rend_service_port_config_free(cfg);
cfg = NULL;
/* quoted unix port, missing end quote */
cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar",