diff options
author | David Goulet <dgoulet@torproject.org> | 2017-01-13 16:00:07 -0500 |
---|---|---|
committer | David Goulet <dgoulet@torproject.org> | 2017-07-13 16:49:44 -0400 |
commit | c086a59ea1fe63e38b6f83fa0c2c19bf495e977d (patch) | |
tree | 8b1dc6f9b3b4e3ea9658a93eb0406f513a0ebe01 /src/or/hs_config.c | |
parent | 93774dcb5458115652e0be5cdfaf198967b8a31e (diff) | |
download | tor-c086a59ea1fe63e38b6f83fa0c2c19bf495e977d.tar.gz tor-c086a59ea1fe63e38b6f83fa0c2c19bf495e977d.zip |
prop224: Configure v3 service from options
This commit adds the support in the HS subsystem for loading a service from a
set of or_options_t and put them in a staging list.
To achieve this, service accessors have been created and a global hash map
containing service object indexed by master public key. However, this is not
used for now. It's ground work for registration process.
Signed-off-by: David Goulet <dgoulet@torproject.org>
Diffstat (limited to 'src/or/hs_config.c')
-rw-r--r-- | src/or/hs_config.c | 318 |
1 files changed, 261 insertions, 57 deletions
diff --git a/src/or/hs_config.c b/src/or/hs_config.c index 6326e90324..6bb422dbf1 100644 --- a/src/or/hs_config.c +++ b/src/or/hs_config.c @@ -31,20 +31,171 @@ #include "hs_service.h" #include "rendservice.h" -/* Configuration handler for a version 3 service. Return 0 on success else a - * negative value. */ +/* Using the given list of services, stage them into our global state. Every + * service version are handled. This function can remove entries in the given + * service_list. + * + * Staging a service means that we take all services in service_list and we + * put them in the staging list (global) which acts as a temporary list that + * is used by the service loading key process. In other words, staging a + * service puts it in a list to be considered when loading the keys and then + * moved to the main global list. */ +static void +stage_services(smartlist_t *service_list) +{ + tor_assert(service_list); + + /* This is v2 specific. Trigger service pruning which will make sure the + * just configured services end up in the main global list. It should only + * be done in non validation mode because v2 subsystem handles service + * object differently. */ + rend_service_prune_list(); + + /* Cleanup v2 service from the list, we don't need those object anymore + * because we validated them all against the others and we want to stage + * only >= v3 service. And remember, v2 has a different object type which is + * shadow copied from an hs_service_t type. */ + SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) { + if (s->version == HS_VERSION_TWO) { + SMARTLIST_DEL_CURRENT(service_list, s); + hs_service_free(s); + } + } SMARTLIST_FOREACH_END(s); + + /* This is >= v3 specific. Using the newly configured service list, stage + * them into our global state. Every object ownership is lost after. */ + hs_service_stage_services(service_list); +} + +/* Validate the given service against all service in the given list. If the + * service is ephemeral, this function ignores it. Services with the same + * directory path aren't allowed and will return an error. If a duplicate is + * found, 1 is returned else 0 if none found. */ +static int +service_is_duplicate_in_list(const smartlist_t *service_list, + const hs_service_t *service) +{ + int ret = 0; + + tor_assert(service_list); + tor_assert(service); + + /* Ephemeral service don't have a directory configured so no need to check + * for a service in the list having the same path. */ + if (service->config.is_ephemeral) { + goto end; + } + + /* XXX: Validate if we have any service that has the given service dir path. + * This has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. */ + SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) { + if (!strcmp(s->config.directory_path, service->config.directory_path)) { + log_warn(LD_REND, "Another hidden service is already configured " + "for directory %s", + escaped(service->config.directory_path)); + ret = 1; + goto end; + } + } SMARTLIST_FOREACH_END(s); + + end: + return ret; +} + +/* Validate service configuration. This is used when loading the configuration + * and once we've setup a service object, it's config object is passed to this + * function for further validation. This does not validate service key + * material. Return 0 if valid else -1 if invalid. */ +static int +config_validate_service(const hs_service_config_t *config) +{ + tor_assert(config); + + /* Amount of ports validation. */ + if (!config->ports || smartlist_len(config->ports) == 0) { + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", + escaped(config->directory_path)); + goto invalid; + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/* Configuration handler for a version 3 service. The line_ must be pointing + * to the directive directly after a HiddenServiceDir. That way, when hitting + * the next HiddenServiceDir line or reaching the end of the list of lines, we + * know that we have to stop looking for more options. The given service + * object must be already allocated and passed through + * config_generic_service() prior to calling this function. + * + * Return 0 on success else a negative value. */ static int -config_service_v3(const config_line_t *line, - const or_options_t *options, int validate_only, +config_service_v3(const config_line_t *line_, + const or_options_t *options, hs_service_t *service) { - (void) line; - (void) service; - (void) validate_only; (void) options; - /* XXX: Configure a v3 service with specific options. */ - /* XXX: Add service to v3 list and pruning on reload. */ + const config_line_t *line; + hs_service_config_t *config; + + tor_assert(service); + + config = &service->config; + + for (line = line_; line; line = line->next) { + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + break; + } + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + int ok = 0; + config->num_intro_points = + (unsigned int) tor_parse_ulong(line->value, 10, + NUM_INTRO_POINTS_DEFAULT, + HS_CONFIG_V3_MAX_INTRO_POINTS, + &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, "HiddenServiceNumIntroductionPoints " + "should be between %d and %d, not %s", + NUM_INTRO_POINTS_DEFAULT, HS_CONFIG_V3_MAX_INTRO_POINTS, + line->value); + goto err; + } + log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s", + config->num_intro_points, escaped(config->directory_path)); + continue; + } + } + + /* We do not load the key material for the service at this stage. This is + * done later once tor can confirm that it is in a running state. */ + + /* We are about to return a fully configured service so do one last pass of + * validation at it. */ + if (config_validate_service(config) < 0) { + goto err; + } + return 0; + err: + return -1; } /* Configure a service using the given options in line_ and options. This is @@ -98,7 +249,7 @@ config_generic_service(const config_line_t *line_, /* Version of the service. */ if (!strcasecmp(line->key, "HiddenServiceVersion")) { service->version = (uint32_t) tor_parse_ulong(line->value, - 10, HS_VERSION_TWO, + 10, HS_VERSION_MIN, HS_VERSION_MAX, &ok, NULL); if (!ok) { @@ -164,13 +315,13 @@ config_generic_service(const config_line_t *line_, } /* Maximum streams per circuit. */ if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { - config->max_streams_per_rdv_circuit = tor_parse_uint64(line->value, - 10, 0, 65535, - &ok, NULL); + config->max_streams_per_rdv_circuit = + tor_parse_uint64(line->value, 10, 0, + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok, NULL); if (!ok) { log_warn(LD_CONFIG, "HiddenServiceMaxStreams should be between 0 and %d, not %s", - 65535, line->value); + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, line->value); goto err; } log_info(LD_CONFIG, @@ -197,12 +348,6 @@ config_generic_service(const config_line_t *line_, } } - /* Check permission on service directory. */ - if (hs_check_service_private_dir(options->User, config->directory_path, - config->dir_group_readable, 0) < 0) { - goto err; - } - /* Check if we are configured in non anonymous mode and single hop mode * meaning every service become single onion. */ if (rend_service_allow_non_anonymous_connection(options) && @@ -220,7 +365,6 @@ config_generic_service(const config_line_t *line_, static int (*config_service_handlers[])(const config_line_t *line, const or_options_t *options, - int validate_only, hs_service_t *service) = { NULL, /* v0 */ @@ -229,64 +373,124 @@ static int config_service_v3, /* v3 */ }; +/* Configure a service using the given line and options. This function will + * call the corresponding version handler and validate the service against the + * other one. On success, add the service to the given list and return 0. On + * error, nothing is added to the list and a negative value is returned. */ +static int +config_service(const config_line_t *line, const or_options_t *options, + smartlist_t *service_list) +{ + hs_service_t *service = NULL; + + tor_assert(line); + tor_assert(options); + tor_assert(service_list); + + /* We have a new hidden service. */ + service = hs_service_new(options); + /* We'll configure that service as a generic one and then pass it to the + * specific handler according to the configured version number. */ + if (config_generic_service(line, options, service) < 0) { + goto err; + } + tor_assert(service->version <= HS_VERSION_MAX); + /* Check permission on service directory that was just parsed. And this must + * be done regardless of the service version. Do not ask for the directory + * to be created, this is done when the keys are loaded because we could be + * in validation mode right now. */ + if (hs_check_service_private_dir(options->User, + service->config.directory_path, + service->config.dir_group_readable, + 0) < 0) { + goto err; + } + /* The handler is in charge of specific options for a version. We start + * after this service directory line so once we hit another directory + * line, the handler knows that it has to stop. */ + if (config_service_handlers[service->version](line->next, options, + service) < 0) { + goto err; + } + /* We'll check if this service can be kept depending on the others + * configured previously. */ + if (service_is_duplicate_in_list(service_list, service)) { + goto err; + } + /* Passes, add it to the given list. */ + smartlist_add(service_list, service); + return 0; + + err: + hs_service_free(service); + return -1; +} + /* From a set of <b>options</b>, setup every hidden service found. Return 0 on * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and * return as normal, but don't actually change the configured services. */ int hs_config_service_all(const or_options_t *options, int validate_only) { - int dir_option_seen = 0; - hs_service_t *service = NULL; + int dir_option_seen = 0, ret = -1; const config_line_t *line; + smartlist_t *new_service_list = NULL; tor_assert(options); + /* Newly configured service are put in that list which is then used for + * validation and staging for >= v3. */ + new_service_list = smartlist_new(); + for (line = options->RendConfigLines; line; line = line->next) { - if (!strcasecmp(line->key, "HiddenServiceDir")) { - /* We have a new hidden service. */ - service = hs_service_new(options); - /* We'll configure that service as a generic one and then pass it to the - * specific handler according to the configured version number. */ - if (config_generic_service(line, options, service) < 0) { + /* Ignore all directives that aren't the start of a service. */ + if (strcasecmp(line->key, "HiddenServiceDir")) { + if (!dir_option_seen) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); goto err; } - tor_assert(service->version <= HS_VERSION_MAX); - /* The handler is in charge of specific options for a version. We start - * after this service directory line so once we hit another directory - * line, the handler knows that it has to stop. */ - if (config_service_handlers[service->version](line->next, options, - validate_only, - service) < 0) { - goto err; - } - /* Whatever happens, on success we loose the ownership of the service - * object so we nullify the pointer to be safe. */ - service = NULL; - /* Flag that we've seen a directory directive and we'll use that to make - * sure that the torrc options ordering are actually valid. */ - dir_option_seen = 1; continue; } - /* The first line must be a directory option else tor is misconfigured. */ - if (!dir_option_seen) { - log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", - line->key); + /* Flag that we've seen a directory directive and we'll use it to make + * sure that the torrc options ordering is actually valid. */ + dir_option_seen = 1; + + /* Try to configure this service now. On success, it will be added to the + * list and validated against the service in that same list. */ + if (config_service(line, options, new_service_list) < 0) { goto err; } } + /* In non validation mode, we'll stage those services we just successfully + * configured. Service ownership is transfered from the list to the global + * state. If any service is invalid, it will be removed from the list and + * freed. All versions are handled in that function. */ if (!validate_only) { - /* Trigger service pruning which will make sure the just configured - * services end up in the main global list. This is v2 specific. */ - rend_service_prune_list(); - /* XXX: Need the v3 one. */ + stage_services(new_service_list); + } else { + /* We've just validated that we were able to build a clean working list of + * services. We don't need those objects anymore. */ + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, + hs_service_free(s)); + /* For the v2 subsystem, the configuration handler adds the service object + * to the staging list and it is transferred in the main list through the + * prunning process. In validation mode, we thus have to purge the staging + * list so it's not kept in memory as valid service. */ + rend_service_free_staging_list(); } - /* Success. */ - return 0; + /* Success. Note that the service list has no ownership of its content. */ + ret = 0; + goto end; + err: - hs_service_free(service); - /* Tor main should call the free all function. */ - return -1; + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s)); + + end: + smartlist_free(new_service_list); + /* Tor main should call the free all function on error. */ + return ret; } |