summaryrefslogtreecommitdiff
path: root/src/or/hs_config.c
diff options
context:
space:
mode:
authorDavid Goulet <dgoulet@torproject.org>2017-01-13 16:00:07 -0500
committerDavid Goulet <dgoulet@torproject.org>2017-07-13 16:49:44 -0400
commitc086a59ea1fe63e38b6f83fa0c2c19bf495e977d (patch)
tree8b1dc6f9b3b4e3ea9658a93eb0406f513a0ebe01 /src/or/hs_config.c
parent93774dcb5458115652e0be5cdfaf198967b8a31e (diff)
downloadtor-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.c318
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;
}