diff options
Diffstat (limited to 'src/or/rendservice.c')
-rw-r--r-- | src/or/rendservice.c | 731 |
1 files changed, 528 insertions, 203 deletions
diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 6c934c8c12..0329d70924 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -15,6 +15,7 @@ #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "control.h" #include "directory.h" #include "main.h" #include "networkstatus.h" @@ -42,9 +43,15 @@ static int intro_point_accepted_intro_count(rend_intro_point_t *intro); static int intro_point_should_expire_now(rend_intro_point_t *intro, time_t now); struct rend_service_t; +static int rend_service_derive_key_digests(struct rend_service_t *s); static int rend_service_load_keys(struct rend_service_t *s); static int rend_service_load_auth_keys(struct rend_service_t *s, const char *hfname); +static struct rend_service_t *rend_service_get_by_pk_digest( + const char* digest); +static struct rend_service_t *rend_service_get_by_service_id(const char *id); +static const char *rend_service_escaped_dir( + const struct rend_service_t *s); static ssize_t rend_service_parse_intro_for_v0_or_v1( rend_intro_cell_t *intro, @@ -65,7 +72,7 @@ static ssize_t rend_service_parse_intro_for_v3( /** Represents the mapping from a virtual port of a rendezvous service to * a real port on some IP. */ -typedef struct rend_service_port_config_t { +struct rend_service_port_config_s { /* The incoming HS virtual port we're mapping */ uint16_t virtual_port; /* Is this an AF_UNIX port? */ @@ -76,7 +83,7 @@ typedef struct rend_service_port_config_t { tor_addr_t real_addr; /* The socket path to connect to, if is_unix_addr */ char unix_addr[FLEXIBLE_ARRAY_MEMBER]; -} rend_service_port_config_t; +}; /** Try to maintain this many intro points per service by default. */ #define NUM_INTRO_POINTS_DEFAULT 3 @@ -90,7 +97,7 @@ typedef struct rend_service_port_config_t { #define MAX_INTRO_CIRCS_PER_PERIOD 10 /** How many times will a hidden service operator attempt to connect to * a requested rendezvous point before giving up? */ -#define MAX_REND_FAILURES 8 +#define MAX_REND_FAILURES 1 /** How many seconds should we spend trying to connect to a requested * rendezvous point before giving up? */ #define MAX_REND_TIMEOUT 30 @@ -102,7 +109,8 @@ typedef struct rend_service_port_config_t { /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ - char *directory; /**< where in the filesystem it stores it */ + char *directory; /**< where in the filesystem it stores it. Will be NULL if + * this service is ephemeral. */ int dir_group_readable; /**< if 1, allow group read permissions on directory */ smartlist_t *ports; /**< List of rend_service_port_config_t */ @@ -139,8 +147,23 @@ typedef struct rend_service_t { /** If true, we don't close circuits for making requests to unsupported * ports. */ int allow_unknown_ports; + /** The maximum number of simultanious streams-per-circuit that are allowed + * to be established, or 0 if no limit is set. + */ + int max_streams_per_circuit; + /** If true, we close circuits that exceed the max_streams_per_circuit + * limit. */ + int max_streams_close_circuit; } rend_service_t; +/** Returns a escaped string representation of the service, <b>s</b>. + */ +static const char * +rend_service_escaped_dir(const struct rend_service_t *s) +{ + return (s->directory) ? escaped(s->directory) : "[EPHEMERAL]"; +} + /** A list of rend_service_t's for services run on this OP. */ static smartlist_t *rend_service_list = NULL; @@ -173,7 +196,7 @@ rend_authorized_client_free(rend_authorized_client_t *client) return; if (client->client_key) crypto_pk_free(client->client_key); - tor_strclear(client->client_name); + memwipe(client->client_name, 0, strlen(client->client_name)); tor_free(client->client_name); memwipe(client->descriptor_cookie, 0, sizeof(client->descriptor_cookie)); tor_free(client); @@ -195,7 +218,8 @@ rend_service_free(rend_service_t *service) return; tor_free(service->directory); - SMARTLIST_FOREACH(service->ports, void*, p, tor_free(p)); + SMARTLIST_FOREACH(service->ports, rend_service_port_config_t*, p, + rend_service_port_config_free(p)); smartlist_free(service->ports); if (service->private_key) crypto_pk_free(service->private_key); @@ -232,8 +256,9 @@ rend_service_free_all(void) } /** Validate <b>service</b> and add it to rend_service_list if possible. + * Return 0 on success and -1 on failure. */ -static void +static int rend_add_service(rend_service_t *service) { int i; @@ -241,20 +266,38 @@ rend_add_service(rend_service_t *service) service->intro_nodes = smartlist_new(); + if (service->max_streams_per_circuit < 0) { + log_warn(LD_CONFIG, "Hidden service (%s) configured with negative max " + "streams per circuit; ignoring.", + rend_service_escaped_dir(service)); + rend_service_free(service); + return -1; + } + + if (service->max_streams_close_circuit < 0 || + service->max_streams_close_circuit > 1) { + log_warn(LD_CONFIG, "Hidden service (%s) configured with invalid " + "max streams handling; ignoring.", + rend_service_escaped_dir(service)); + rend_service_free(service); + return -1; + } + if (service->auth_type != REND_NO_AUTH && smartlist_len(service->clients) == 0) { log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no " "clients; ignoring.", - escaped(service->directory)); + rend_service_escaped_dir(service)); rend_service_free(service); - return; + return -1; } if (!smartlist_len(service->ports)) { log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; " "ignoring.", - escaped(service->directory)); + rend_service_escaped_dir(service)); rend_service_free(service); + return -1; } else { int dupe = 0; /* XXX This duplicate check has two problems: @@ -272,14 +315,17 @@ rend_add_service(rend_service_t *service) * lock file. But this is enough to detect a simple mistake that * at least one person has actually made. */ - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - dupe = dupe || - !strcmp(ptr->directory, service->directory)); - if (dupe) { - log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s, ignoring.", service->directory); - rend_service_free(service); - return; + if (service->directory != NULL) { /* Skip dupe for ephemeral services. */ + SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, + dupe = dupe || + !strcmp(ptr->directory, service->directory)); + if (dupe) { + log_warn(LD_REND, "Another hidden service is already configured for " + "directory %s, ignoring.", + rend_service_escaped_dir(service)); + rend_service_free(service); + return -1; + } } smartlist_add(rend_service_list, service); log_debug(LD_REND,"Configuring service with directory \"%s\"", @@ -305,7 +351,9 @@ rend_add_service(rend_service_t *service) #endif /* defined(HAVE_SYS_UN_H) */ } } + return 0; } + /* NOTREACHED */ } /** Return a new rend_service_port_config_t with its path set to @@ -324,15 +372,17 @@ rend_service_port_config_new(const char *socket_path) return conf; } -/** Parses a real-port to virtual-port mapping and returns a new - * rend_service_port_config_t. +/** Parses a real-port to virtual-port mapping separated by the provided + * separator and returns a new rend_service_port_config_t, or NULL and an + * optional error string on failure. * - * The format is: VirtualPort (IP|RealPort|IP:RealPort|'socket':path)? + * The format is: VirtualPort SEP (IP|RealPort|IP:RealPort|'socket':path)? * * IP defaults to 127.0.0.1; RealPort defaults to VirtualPort. */ -static rend_service_port_config_t * -parse_port_config(const char *string) +rend_service_port_config_t * +rend_service_parse_port_config(const char *string, const char *sep, + char **err_msg_out) { smartlist_t *sl; int virtport; @@ -343,19 +393,24 @@ parse_port_config(const char *string) rend_service_port_config_t *result = NULL; unsigned int is_unix_addr = 0; char *socket_path = NULL; + char *err_msg = NULL; sl = smartlist_new(); - smartlist_split_string(sl, string, " ", + smartlist_split_string(sl, string, sep, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); if (smartlist_len(sl) < 1 || smartlist_len(sl) > 2) { - log_warn(LD_CONFIG, "Bad syntax in hidden service port configuration."); + 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) { - log_warn(LD_CONFIG, "Missing or invalid port %s in hidden service port " - "configuration", escaped(smartlist_get(sl,0))); + if (err_msg_out) + tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service " + "port configuration", escaped(smartlist_get(sl,0))); + goto err; } @@ -369,10 +424,11 @@ parse_port_config(const char *string) addrport = smartlist_get(sl,1); ret = config_parse_unix_port(addrport, &socket_path); if (ret < 0 && ret != -ENOENT) { - if (ret == -EINVAL) { - log_warn(LD_CONFIG, - "Empty socket path in hidden service port configuration."); - } + if (ret == -EINVAL) + if (err_msg_out) + err_msg = tor_strdup("Empty socket path in hidden service port " + "configuration."); + goto err; } if (socket_path) { @@ -380,8 +436,10 @@ parse_port_config(const char *string) } else if (strchr(addrport, ':') || strchr(addrport, '.')) { /* else try it as an IP:port pair if it has a : or . in it */ if (tor_addr_port_lookup(addrport, &addr, &p)<0) { - log_warn(LD_CONFIG,"Unparseable address in hidden service port " - "configuration."); + if (err_msg_out) + err_msg = tor_strdup("Unparseable address in hidden service port " + "configuration."); + goto err; } realport = p?p:virtport; @@ -389,8 +447,11 @@ parse_port_config(const char *string) /* No addr:port, no addr -- must be port. */ realport = (int)tor_parse_long(addrport, 10, 1, 65535, NULL, NULL); if (!realport) { - log_warn(LD_CONFIG,"Unparseable or out-of-range port %s in hidden " - "service port configuration.", escaped(addrport)); + if (err_msg_out) + tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in " + "hidden service port configuration.", + escaped(addrport)); + goto err; } tor_addr_from_ipv4h(&addr, 0x7F000001u); /* Default to 127.0.0.1 */ @@ -408,6 +469,7 @@ parse_port_config(const char *string) } err: + 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); @@ -415,6 +477,13 @@ parse_port_config(const char *string) return result; } +/** Release all storage held in a rend_service_port_config_t. */ +void +rend_service_port_config_free(rend_service_port_config_t *p) +{ + tor_free(p); +} + /** Set up rend_service_list, based on the values of HiddenServiceDir and * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on * failure. (If <b>validate_only</b> is set, parse, warn and return as @@ -439,113 +508,145 @@ rend_config_services(const or_options_t *options, int validate_only) if (service) { /* register the one we just finished parsing */ if (validate_only) rend_service_free(service); - else - rend_add_service(service); - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - service->ports = smartlist_new(); - service->intro_period_started = time(NULL); - service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; - continue; - } - if (!service) { - log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", - line->key); - rend_service_free(service); - return -1; - } - if (!strcasecmp(line->key, "HiddenServicePort")) { - portcfg = parse_port_config(line->value); - if (!portcfg) { - rend_service_free(service); - return -1; - } - smartlist_add(service->ports, portcfg); - } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { - service->allow_unknown_ports = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s", - line->value); - rend_service_free(service); - return -1; - } - log_info(LD_CONFIG, - "HiddenServiceAllowUnknownPorts=%d for %s", - (int)service->allow_unknown_ports, service->directory); - } else if (!strcasecmp(line->key, - "HiddenServiceDirGroupReadable")) { - service->dir_group_readable = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceDirGroupReadable should be 0 or 1, not %s", - line->value); - rend_service_free(service); - return -1; - } - log_info(LD_CONFIG, - "HiddenServiceDirGroupReadable=%d for %s", - service->dir_group_readable, service->directory); - } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { - /* Parse auth type and comma-separated list of client names and add a - * rend_authorized_client_t for each client to the service's list - * of authorized clients. */ - smartlist_t *type_names_split, *clients; - const char *authname; - int num_clients; - if (service->auth_type != REND_NO_AUTH) { - log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " - "lines for a single service."); - rend_service_free(service); - return -1; - } - type_names_split = smartlist_new(); - smartlist_split_string(type_names_split, line->value, " ", 0, 2); - if (smartlist_len(type_names_split) < 1) { - log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " - "should have been prevented when parsing the " - "configuration."); - smartlist_free(type_names_split); - rend_service_free(service); - return -1; - } - authname = smartlist_get(type_names_split, 0); - if (!strcasecmp(authname, "basic")) { - service->auth_type = REND_BASIC_AUTH; - } else if (!strcasecmp(authname, "stealth")) { - service->auth_type = REND_STEALTH_AUTH; - } else { - log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " - "unrecognized auth-type '%s'. Only 'basic' or 'stealth' " - "are recognized.", - (char *) smartlist_get(type_names_split, 0)); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - rend_service_free(service); - return -1; - } - service->clients = smartlist_new(); - if (smartlist_len(type_names_split) < 2) { - log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " - "auth-type '%s', but no client names.", - service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - continue; - } - clients = smartlist_new(); - smartlist_split_string(clients, smartlist_get(type_names_split, 1), - ",", SPLIT_SKIP_SPACE, 0); - SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); - smartlist_free(type_names_split); - /* Remove duplicate client names. */ - num_clients = smartlist_len(clients); - smartlist_sort_strings(clients); - smartlist_uniq_strings(clients); - if (smartlist_len(clients) < num_clients) { + else + rend_add_service(service); + } + service = tor_malloc_zero(sizeof(rend_service_t)); + service->directory = tor_strdup(line->value); + service->ports = smartlist_new(); + service->intro_period_started = time(NULL); + service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; + continue; + } + if (!service) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + rend_service_free(service); + return -1; + } + if (!strcasecmp(line->key, "HiddenServicePort")) { + char *err_msg = NULL; + portcfg = rend_service_parse_port_config(line->value, " ", &err_msg); + if (!portcfg) { + if (err_msg) + log_warn(LD_CONFIG, "%s", err_msg); + tor_free(err_msg); + rend_service_free(service); + return -1; + } + tor_assert(!err_msg); + smartlist_add(service->ports, portcfg); + } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + service->allow_unknown_ports = (int)tor_parse_long(line->value, + 10, 0, 1, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s", + line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceAllowUnknownPorts=%d for %s", + (int)service->allow_unknown_ports, service->directory); + } else if (!strcasecmp(line->key, + "HiddenServiceDirGroupReadable")) { + service->dir_group_readable = (int)tor_parse_long(line->value, + 10, 0, 1, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceDirGroupReadable should be 0 or 1, not %s", + line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceDirGroupReadable=%d for %s", + service->dir_group_readable, service->directory); + } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { + service->max_streams_per_circuit = (int)tor_parse_long(line->value, + 10, 0, 65535, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceMaxStreams should be between 0 and %d, not %s", + 65535, line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceMaxStreams=%d for %s", + service->max_streams_per_circuit, service->directory); + } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { + service->max_streams_close_circuit = (int)tor_parse_long(line->value, + 10, 0, 1, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, + "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, not %s", + line->value); + rend_service_free(service); + return -1; + } + log_info(LD_CONFIG, + "HiddenServiceMaxStreamsCloseCircuit=%d for %s", + (int)service->max_streams_close_circuit, service->directory); + + } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + /* Parse auth type and comma-separated list of client names and add a + * rend_authorized_client_t for each client to the service's list + * of authorized clients. */ + smartlist_t *type_names_split, *clients; + const char *authname; + int num_clients; + if (service->auth_type != REND_NO_AUTH) { + log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " + "lines for a single service."); + rend_service_free(service); + return -1; + } + type_names_split = smartlist_new(); + smartlist_split_string(type_names_split, line->value, " ", 0, 2); + if (smartlist_len(type_names_split) < 1) { + log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " + "should have been prevented when parsing the " + "configuration."); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + authname = smartlist_get(type_names_split, 0); + if (!strcasecmp(authname, "basic")) { + service->auth_type = REND_BASIC_AUTH; + } else if (!strcasecmp(authname, "stealth")) { + service->auth_type = REND_STEALTH_AUTH; + } else { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "unrecognized auth-type '%s'. Only 'basic' or 'stealth' " + "are recognized.", + (char *) smartlist_get(type_names_split, 0)); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + rend_service_free(service); + return -1; + } + service->clients = smartlist_new(); + if (smartlist_len(type_names_split) < 2) { + log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " + "auth-type '%s', but no client names.", + service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + continue; + } + clients = smartlist_new(); + smartlist_split_string(clients, smartlist_get(type_names_split, 1), + ",", SPLIT_SKIP_SPACE, 0); + SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); + smartlist_free(type_names_split); + /* Remove duplicate client names. */ + num_clients = smartlist_len(clients); + smartlist_sort_strings(clients); + smartlist_uniq_strings(clients); + if (smartlist_len(clients) < num_clients) { log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " "duplicate client name(s); removing.", num_clients - smartlist_len(clients)); @@ -632,12 +733,35 @@ rend_config_services(const or_options_t *options, int validate_only) if (old_service_list && !validate_only) { smartlist_t *surviving_services = smartlist_new(); + /* Preserve the existing ephemeral services. + * + * This is the ephemeral service equivalent of the "Copy introduction + * points to new services" block, except there's no copy required since + * the service structure isn't regenerated. + * + * After this is done, all ephemeral services will be: + * * Removed from old_service_list, so the equivalent non-ephemeral code + * will not attempt to preserve them. + * * Added to the new rend_service_list (that previously only had the + * services listed in the configuration). + * * Added to surviving_services, which is the list of services that + * will NOT have their intro point closed. + */ + SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, { + if (!old->directory) { + SMARTLIST_DEL_CURRENT(old_service_list, old); + smartlist_add(surviving_services, old); + smartlist_add(rend_service_list, old); + } + }); + /* Copy introduction points to new services. */ /* XXXX This is O(n^2), but it's only called on reconfigure, so it's * probably ok? */ SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, new) { SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) { - if (!strcmp(old->directory, new->directory)) { + if (new->directory && old->directory && + !strcmp(old->directory, new->directory)) { smartlist_add_all(new->intro_nodes, old->intro_nodes); smartlist_clear(old->intro_nodes); smartlist_add(surviving_services, old); @@ -685,6 +809,125 @@ rend_config_services(const or_options_t *options, int validate_only) return 0; } +/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, with + * <b>max_streams_per_circuit</b> streams allowed per rendezvous circuit, + * and circuit closure on max streams being exceeded set by + * <b>max_streams_close_circuit</b>. + * + * Regardless of sucess/failure, callers should not touch pk/ports after + * calling this routine, and may assume that correct cleanup has been done + * on failure. + * + * Return an appropriate rend_service_add_ephemeral_status_t. + */ +rend_service_add_ephemeral_status_t +rend_service_add_ephemeral(crypto_pk_t *pk, + smartlist_t *ports, + int max_streams_per_circuit, + int max_streams_close_circuit, + char **service_id_out) +{ + *service_id_out = NULL; + /* Allocate the service structure, and initialize the key, and key derived + * parameters. + */ + rend_service_t *s = tor_malloc_zero(sizeof(rend_service_t)); + s->directory = NULL; /* This indicates the service is ephemeral. */ + s->private_key = pk; + s->auth_type = REND_NO_AUTH; + s->ports = ports; + s->intro_period_started = time(NULL); + s->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; + s->max_streams_per_circuit = max_streams_per_circuit; + s->max_streams_close_circuit = max_streams_close_circuit; + if (rend_service_derive_key_digests(s) < 0) { + rend_service_free(s); + return RSAE_BADPRIVKEY; + } + + if (!s->ports || smartlist_len(s->ports) == 0) { + log_warn(LD_CONFIG, "At least one VIRTPORT/TARGET must be specified."); + rend_service_free(s); + return RSAE_BADVIRTPORT; + } + + /* Enforcing pk/id uniqueness should be done by rend_service_load_keys(), but + * it's not, see #14828. + */ + if (rend_service_get_by_pk_digest(s->pk_digest)) { + log_warn(LD_CONFIG, "Onion Service private key collides with an " + "existing service."); + rend_service_free(s); + return RSAE_ADDREXISTS; + } + if (rend_service_get_by_service_id(s->service_id)) { + log_warn(LD_CONFIG, "Onion Service id collides with an existing service."); + rend_service_free(s); + return RSAE_ADDREXISTS; + } + + /* Initialize the service. */ + if (rend_add_service(s)) { + rend_service_free(s); + return RSAE_INTERNAL; + } + *service_id_out = tor_strdup(s->service_id); + + log_debug(LD_CONFIG, "Added ephemeral Onion Service: %s", s->service_id); + return RSAE_OKAY; +} + +/** Remove the ephemeral service <b>service_id</b> if possible. Returns 0 on + * success, and -1 on failure. + */ +int +rend_service_del_ephemeral(const char *service_id) +{ + rend_service_t *s; + if (!rend_valid_service_id(service_id)) { + log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal."); + return -1; + } + if ((s = rend_service_get_by_service_id(service_id)) == NULL) { + log_warn(LD_CONFIG, "Requested non-existent Onion Service id for " + "removal."); + return -1; + } + if (s->directory) { + log_warn(LD_CONFIG, "Requested non-ephemeral Onion Service for removal."); + return -1; + } + + /* Kill the intro point circuit for the Onion Service, and remove it from + * the list. Closing existing connections is the application's problem. + * + * XXX: As with the comment in rend_config_services(), a nice abstraction + * would be ideal here, but for now just duplicate the code. + */ + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!circ->marked_for_close && + circ->state == CIRCUIT_STATE_OPEN && + (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { + origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); + tor_assert(oc->rend_data); + if (!tor_memeq(s->pk_digest, oc->rend_data->rend_pk_digest, DIGEST_LEN)) + continue; + log_debug(LD_REND, "Closing intro point %s for service %s.", + safe_str_client(extend_info_describe( + oc->build_state->chosen_exit)), + oc->rend_data->onion_address); + circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); + } + } SMARTLIST_FOREACH_END(circ); + smartlist_remove(rend_service_list, s); + rend_service_free(s); + + log_debug(LD_CONFIG, "Removed ephemeral Onion Service: %s", service_id); + + return 0; +} + /** Replace the old value of <b>service</b>-\>desc with one that reflects * the other fields in service. */ @@ -769,6 +1012,7 @@ rend_service_add_filenames_to_list(smartlist_t *lst, const rend_service_t *s) { tor_assert(lst); tor_assert(s); + tor_assert(s->directory); smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"private_key", s->directory); smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"hostname", @@ -787,11 +1031,31 @@ rend_services_add_filenames_to_lists(smartlist_t *open_lst, if (!rend_service_list) return; SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) { - rend_service_add_filenames_to_list(open_lst, s); - smartlist_add(stat_lst, tor_strdup(s->directory)); + if (s->directory) { + rend_service_add_filenames_to_list(open_lst, s); + smartlist_add(stat_lst, tor_strdup(s->directory)); + } } SMARTLIST_FOREACH_END(s); } +/** Derive all rend_service_t internal material based on the service's key. + * Returns 0 on sucess, -1 on failure. + */ +static int +rend_service_derive_key_digests(struct rend_service_t *s) +{ + if (rend_get_service_id(s->private_key, s->service_id)<0) { + log_warn(LD_BUG, "Internal error: couldn't encode service ID."); + return -1; + } + if (crypto_pk_get_digest(s->private_key, s->pk_digest)<0) { + log_warn(LD_BUG, "Couldn't compute hash of public key."); + return -1; + } + + return 0; +} + /** Load and/or generate private keys for the hidden service <b>s</b>, * possibly including keys for client authorization. Return 0 on success, -1 * on failure. */ @@ -830,15 +1094,10 @@ rend_service_load_keys(rend_service_t *s) if (!s->private_key) return -1; - /* Create service file */ - if (rend_get_service_id(s->private_key, s->service_id)<0) { - log_warn(LD_BUG, "Internal error: couldn't encode service ID."); - return -1; - } - if (crypto_pk_get_digest(s->private_key, s->pk_digest)<0) { - log_warn(LD_BUG, "Couldn't compute hash of public key."); + if (rend_service_derive_key_digests(s) < 0) return -1; - } + + /* Create service file */ if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || strlcat(fname,PATH_SEPARATOR"hostname",sizeof(fname)) >= sizeof(fname)) { @@ -941,7 +1200,7 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname) } if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, client->descriptor_cookie, - REND_DESC_COOKIE_LEN) < 0) { + REND_DESC_COOKIE_LEN, 0) < 0) { log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); goto err; } @@ -968,7 +1227,6 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname) client->client_key = prkey; } /* Add entry to client_keys file. */ - desc_cook_out[strlen(desc_cook_out)-1] = '\0'; /* Remove newline. */ written = tor_snprintf(buf, sizeof(buf), "client-name %s\ndescriptor-cookie %s\n", client->client_name, desc_cook_out); @@ -1023,12 +1281,11 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname) ((int)s->auth_type - 1) << 4; if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, extended_desc_cookie, - REND_DESC_COOKIE_LEN+1) < 0) { + REND_DESC_COOKIE_LEN+1, 0) < 0) { log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); goto err; } - desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove A= and - newline. */ + desc_cook_out[strlen(desc_cook_out)-2] = '\0'; /* Remove A=. */ tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", service_id, desc_cook_out, client->client_name); } @@ -1052,7 +1309,7 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname) abort_writing_to_file(open_hfile); done: if (client_keys_str) { - tor_strclear(client_keys_str); + memwipe(client_keys_str, 0, strlen(client_keys_str)); tor_free(client_keys_str); } strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); @@ -1080,6 +1337,20 @@ rend_service_get_by_pk_digest(const char* digest) return NULL; } +/** Return the service whose service id is <b>id</b>, or NULL if no such + * service exists. + */ +static struct rend_service_t * +rend_service_get_by_service_id(const char *id) +{ + tor_assert(strlen(id) == REND_SERVICE_ID_LEN_BASE32); + SMARTLIST_FOREACH(rend_service_list, rend_service_t*, s, { + if (tor_memeq(s->service_id, id, REND_SERVICE_ID_LEN_BASE32)) + return s; + }); + return NULL; +} + /** Return 1 if any virtual port in <b>service</b> wants a circuit * to have good uptime. Else return 0. */ @@ -1098,11 +1369,13 @@ rend_service_requires_uptime(rend_service_t *service) return 0; } -/** Check client authorization of a given <b>descriptor_cookie</b> for - * <b>service</b>. Return 1 for success and 0 for failure. */ +/** Check client authorization of a given <b>descriptor_cookie</b> of + * length <b>cookie_len</b> for <b>service</b>. Return 1 for success + * and 0 for failure. */ static int rend_check_authorization(rend_service_t *service, - const char *descriptor_cookie) + const char *descriptor_cookie, + size_t cookie_len) { rend_authorized_client_t *auth_client = NULL; tor_assert(service); @@ -1113,6 +1386,13 @@ rend_check_authorization(rend_service_t *service, return 0; } + if (cookie_len != REND_DESC_COOKIE_LEN) { + log_info(LD_REND, "Descriptor cookie is %lu bytes, but we expected " + "%lu bytes. Dropping cell.", + (unsigned long)cookie_len, (unsigned long)REND_DESC_COOKIE_LEN); + return 0; + } + /* Look up client authorization by descriptor cookie. */ SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, client, { if (tor_memeq(client->descriptor_cookie, descriptor_cookie, @@ -1124,7 +1404,7 @@ rend_check_authorization(rend_service_t *service, if (!auth_client) { char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64]; base64_encode(descriptor_cookie_base64, sizeof(descriptor_cookie_base64), - descriptor_cookie, REND_DESC_COOKIE_LEN); + descriptor_cookie, REND_DESC_COOKIE_LEN, 0); log_info(LD_REND, "No authorization found for descriptor cookie '%s'! " "Dropping cell!", descriptor_cookie_base64); @@ -1158,16 +1438,17 @@ rend_service_note_removing_intro_point(rend_service_t *service, /* This intro point was never used. Don't change * n_intro_points_wanted. */ } else { + /* We want to increase the number of introduction points service * operates if intro was heavily used, or decrease the number of * intro points if intro was lightly used. * * We consider an intro point's target 'usage' to be - * INTRO_POINT_LIFETIME_INTRODUCTIONS introductions in + * maximum of INTRODUCE2 cells divided by * INTRO_POINT_LIFETIME_MIN_SECONDS seconds. To calculate intro's - * fraction of target usage, we divide the fraction of - * _LIFETIME_INTRODUCTIONS introductions that it has handled by - * the fraction of _LIFETIME_MIN_SECONDS for which it existed. + * fraction of target usage, we divide the amount of INTRODUCE2 cells + * that it has handled by the fraction of _LIFETIME_MIN_SECONDS for + * which it existed. * * Then we multiply that fraction of desired usage by a fudge * factor of 1.5, to decide how many new introduction points @@ -1189,7 +1470,7 @@ rend_service_note_removing_intro_point(rend_service_t *service, intro_point_accepted_intro_count(intro) / (double)(now - intro->time_published); const double intro_point_target_usage = - INTRO_POINT_LIFETIME_INTRODUCTIONS / + intro->max_introductions / (double)INTRO_POINT_LIFETIME_MIN_SECONDS; const double fractional_n_intro_points_wanted_to_replace_this_one = (1.5 * (intro_point_usage / intro_point_target_usage)); @@ -1459,7 +1740,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, if (service->clients) { if (parsed_req->version == 3 && parsed_req->u.v3.auth_len > 0) { if (rend_check_authorization(service, - (const char*)parsed_req->u.v3.auth_data)) { + (const char*)parsed_req->u.v3.auth_data, + parsed_req->u.v3.auth_len)) { log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid."); } else { log_info(LD_REND, "The authorization data that are contained in " @@ -1523,13 +1805,11 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, hexcookie, serviceid); tor_assert(launched->build_state); /* Fill in the circuit's state. */ - launched->rend_data = tor_malloc_zero(sizeof(rend_data_t)); - memcpy(launched->rend_data->rend_pk_digest, - circuit->rend_data->rend_pk_digest, - DIGEST_LEN); - memcpy(launched->rend_data->rend_cookie, parsed_req->rc, REND_COOKIE_LEN); - strlcpy(launched->rend_data->onion_address, service->service_id, - sizeof(launched->rend_data->onion_address)); + + launched->rend_data = + rend_data_service_create(service->service_id, + circuit->rend_data->rend_pk_digest, + parsed_req->rc, service->auth_type); launched->build_state->service_pending_final_cpath_ref = tor_malloc_zero(sizeof(crypt_path_reference_t)); @@ -1940,6 +2220,16 @@ rend_service_parse_intro_for_v2( goto err; } + if (128 != crypto_pk_keysize(extend_info->onion_key)) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "invalid onion key size in version %d INTRODUCE%d cell", + intro->version, + (intro->type)); + } + + goto err; + } ver_specific_len = 7+DIGEST_LEN+2+klen; @@ -2491,10 +2781,9 @@ rend_service_launch_establish_intro(rend_service_t *service, intro->extend_info = extend_info_dup(launched->build_state->chosen_exit); } - launched->rend_data = tor_malloc_zero(sizeof(rend_data_t)); - strlcpy(launched->rend_data->onion_address, service->service_id, - sizeof(launched->rend_data->onion_address)); - memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN); + launched->rend_data = rend_data_service_create(service->service_id, + service->pk_digest, NULL, + service->auth_type); launched->intro_key = crypto_pk_dup_key(intro->intro_key); if (launched->base_.state == CIRCUIT_STATE_OPEN) rend_service_intro_has_opened(launched); @@ -2879,14 +3168,16 @@ find_intro_point(origin_circuit_t *circ) return NULL; } -/** Determine the responsible hidden service directories for the - * rend_encoded_v2_service_descriptor_t's in <b>descs</b> and upload them; - * <b>service_id</b> and <b>seconds_valid</b> are only passed for logging - * purposes. */ -static void +/** Upload the rend_encoded_v2_service_descriptor_t's in <b>descs</b> + * associated with the rend_service_descriptor_t <b>renddesc</b> to + * the responsible hidden service directories OR the hidden service + * directories specified by <b>hs_dirs</b>; <b>service_id</b> and + * <b>seconds_valid</b> are only passed for logging purposes. + */ +void directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, - smartlist_t *descs, const char *service_id, - int seconds_valid) + smartlist_t *descs, smartlist_t *hs_dirs, + const char *service_id, int seconds_valid) { int i, j, failed_upload = 0; smartlist_t *responsible_dirs = smartlist_new(); @@ -2894,14 +3185,21 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, routerstatus_t *hs_dir; for (i = 0; i < smartlist_len(descs); i++) { rend_encoded_v2_service_descriptor_t *desc = smartlist_get(descs, i); - /* Determine responsible dirs. */ - if (hid_serv_get_responsible_directories(responsible_dirs, - desc->desc_id) < 0) { - log_warn(LD_REND, "Could not determine the responsible hidden service " - "directories to post descriptors to."); - smartlist_free(responsible_dirs); - smartlist_free(successful_uploads); - return; + /** If any HSDirs are specified, they should be used instead of + * the responsible directories */ + if (hs_dirs && smartlist_len(hs_dirs) > 0) { + smartlist_add_all(responsible_dirs, hs_dirs); + } else { + /* Determine responsible dirs. */ + if (hid_serv_get_responsible_directories(responsible_dirs, + desc->desc_id) < 0) { + log_warn(LD_REND, "Could not determine the responsible hidden service " + "directories to post descriptors to."); + control_event_hs_descriptor_upload(service_id, + "UNKNOWN", + "UNKNOWN"); + goto done; + } } for (j = 0; j < smartlist_len(responsible_dirs); j++) { char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; @@ -2941,6 +3239,9 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, hs_dir->nickname, hs_dir_ip, hs_dir->or_port); + control_event_hs_descriptor_upload(service_id, + hs_dir->identity_digest, + desc_id_base32); tor_free(hs_dir_ip); /* Remember successful upload to this router for next time. */ if (!smartlist_contains_digest(successful_uploads, @@ -2968,6 +3269,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, } }); } + done: smartlist_free(responsible_dirs); smartlist_free(successful_uploads); } @@ -3032,7 +3334,7 @@ upload_service_descriptor(rend_service_t *service) rend_get_service_id(service->desc->pk, serviceid); log_info(LD_REND, "Launching upload for hidden service %s", serviceid); - directory_post_to_hs_dir(service->desc, descs, serviceid, + directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, seconds_valid); /* Free memory for descriptors. */ for (i = 0; i < smartlist_len(descs); i++) @@ -3061,7 +3363,7 @@ upload_service_descriptor(rend_service_t *service) smartlist_free(client_cookies); return; } - directory_post_to_hs_dir(service->desc, descs, serviceid, + directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, seconds_valid); /* Free memory for descriptors. */ for (i = 0; i < smartlist_len(descs); i++) @@ -3113,7 +3415,7 @@ intro_point_should_expire_now(rend_intro_point_t *intro, } if (intro_point_accepted_intro_count(intro) >= - INTRO_POINT_LIFETIME_INTRODUCTIONS) { + intro->max_introductions) { /* This intro point has been used too many times. Expire it now. */ return 1; } @@ -3122,9 +3424,8 @@ intro_point_should_expire_now(rend_intro_point_t *intro, /* This intro point has been published, but we haven't picked an * expiration time for it. Pick one now. */ int intro_point_lifetime_seconds = - INTRO_POINT_LIFETIME_MIN_SECONDS + - crypto_rand_int(INTRO_POINT_LIFETIME_MAX_SECONDS - - INTRO_POINT_LIFETIME_MIN_SECONDS); + crypto_rand_int_range(INTRO_POINT_LIFETIME_MIN_SECONDS, + INTRO_POINT_LIFETIME_MAX_SECONDS); /* Start the expiration timer now, rather than when the intro * point was first published. There shouldn't be much of a time @@ -3309,7 +3610,8 @@ rend_services_introduce(void) log_warn(LD_REND, "Could only establish %d introduction points for %s; " "wanted %u.", - smartlist_len(service->intro_nodes), service->service_id, + smartlist_len(service->intro_nodes), + safe_str_client(service->service_id), n_intro_points_to_open); break; } @@ -3320,10 +3622,14 @@ rend_services_introduce(void) intro = tor_malloc_zero(sizeof(rend_intro_point_t)); intro->extend_info = extend_info_from_node(node, 0); intro->intro_key = crypto_pk_new(); - tor_assert(!crypto_pk_generate_key(intro->intro_key)); + const int fail = crypto_pk_generate_key(intro->intro_key); + tor_assert(!fail); intro->time_published = -1; intro->time_to_expire = -1; intro->time_expiring = -1; + intro->max_introductions = + crypto_rand_int_range(INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS, + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS); smartlist_add(service->intro_nodes, intro); log_info(LD_REND, "Picked router %s as an intro point for %s.", safe_str_client(node_describe(node)), @@ -3547,6 +3853,25 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, serviceid, (unsigned)circ->base_.n_circ_id); return -2; } + if (service->max_streams_per_circuit > 0) { + /* Enforce the streams-per-circuit limit, and refuse to provide a + * mapping if this circuit will exceed the limit. */ +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + if (circ->rend_data->nr_streams >= service->max_streams_per_circuit) { + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on rendezvous " + "circuit %u; %s. Circuit has %d out of %d streams.", + (unsigned)circ->base_.n_circ_id, + service->max_streams_close_circuit ? + "closing circuit" : + "ignoring open stream request", + circ->rend_data->nr_streams, + service->max_streams_per_circuit); + return service->max_streams_close_circuit ? -2 : -1; + } + } matching_ports = smartlist_new(); SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, { |