Commit 02e2edeb authored by David Goulet's avatar David Goulet
Browse files

prop224: Add hs_config.{c|h} with a refactoring



Add the hs_config.{c|h} files contains everything that the HS subsystem needs
to load and configure services. Ultimately, it should also contain client
functions such as client authorization.

This comes with a big refactoring of rend_config_services() which has now
changed to only configure a single service and it is stripped down of the
common directives which are now part of the generic handler.

This is ground work for prop224 of course but only touches version 2 services
and add XXX note for version 3.
Signed-off-by: David Goulet's avatarDavid Goulet <dgoulet@torproject.org>
parent b03853b6
......@@ -91,6 +91,7 @@
#include "relay.h"
#include "rendclient.h"
#include "rendservice.h"
#include "hs_config.h"
#include "rephist.h"
#include "router.h"
#include "sandbox.h"
......@@ -1681,7 +1682,7 @@ options_act(const or_options_t *old_options)
sweep_bridge_list();
}
if (running_tor && rend_config_services(options, 0)<0) {
if (running_tor && hs_config_service_all(options, 0)<0) {
log_warn(LD_BUG,
"Previously validated hidden services line could not be added!");
return -1;
......@@ -4009,7 +4010,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours.");
}
if (rend_config_services(options, 1) < 0)
if (hs_config_service_all(options, 1) < 0)
REJECT("Failed to configure rendezvous options. See logs for details.");
/* Parse client-side authorization for hidden services. */
......
......@@ -16,6 +16,9 @@
#define HS_VERSION_TWO 2
/* Version 3 of the protocol (prop224). */
#define HS_VERSION_THREE 3
/* Earliest and latest version we support. */
#define HS_VERSION_MIN HS_VERSION_TWO
#define HS_VERSION_MAX HS_VERSION_THREE
/** Try to maintain this many intro points per service by default. */
#define NUM_INTRO_POINTS_DEFAULT 3
......
/* Copyright (c) 2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file hs_config.c
* \brief Implement hidden service configuration subsystem.
*
* \details
*
* This file has basically one main entry point: hs_config_service_all(). It
* takes the torrc options and configure hidden service from it. In validate
* mode, nothing is added to the global service list or keys are not generated
* nor loaded.
*
* A service is configured in two steps. It is first created using the tor
* options and then put in a staging list. It will stay there until
* hs_service_load_all_keys() is called. That function is responsible to
* load/generate the keys for the service in the staging list and if
* successful, transfert the service to the main global service list where
* at that point it is ready to be used.
*
* Configuration handlers are per-version (see config_service_handlers[]) and
* there is a main generic one for every option that is common to all version
* (config_generic_service).
**/
#define HS_CONFIG_PRIVATE
#include "hs_common.h"
#include "hs_config.h"
#include "hs_service.h"
#include "rendservice.h"
/* Configuration handler for a version 3 service. 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,
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. */
return 0;
}
/* Configure a service using the given options in line_ and options. This is
* called for any service regardless of its version which means that all
* directives in this function are generic to any service version. This
* function will also check the validity of the service directory path.
*
* 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.
*
* Return 0 on success else -1. */
static int
config_generic_service(const config_line_t *line_,
const or_options_t *options,
hs_service_t *service)
{
int ok, dir_seen = 0;
const config_line_t *line;
hs_service_config_t *config;
tor_assert(line_);
tor_assert(options);
tor_assert(service);
/* Makes thing easier. */
config = &service->config;
memset(config, 0, sizeof(*config));
/* The first line starts with HiddenServiceDir so we consider what's next is
* the configuration of the service. */
for (line = line_; line ; line = line->next) {
/* This indicate that we have a new service to configure. */
if (!strcasecmp(line->key, "HiddenServiceDir")) {
/* This function only configures one service at a time so if we've
* already seen one, stop right now. */
if (dir_seen) {
break;
}
/* Ok, we've seen one and we are about to configure it. */
dir_seen = 1;
config->directory_path = tor_strdup(line->value);
log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...",
escaped(config->directory_path));
continue;
}
if (BUG(!dir_seen)) {
goto err;
}
/* Version of the service. */
if (!strcasecmp(line->key, "HiddenServiceVersion")) {
service->version = (uint32_t) tor_parse_ulong(line->value,
10, HS_VERSION_TWO,
HS_VERSION_MAX,
&ok, NULL);
if (!ok) {
log_warn(LD_CONFIG,
"HiddenServiceVersion be between %u and %u, not %s",
HS_VERSION_TWO, HS_VERSION_MAX, line->value);
goto err;
}
log_info(LD_CONFIG, "HiddenServiceVersion=%" PRIu32 " for %s",
service->version, escaped(config->directory_path));
continue;
}
/* Virtual port. */
if (!strcasecmp(line->key, "HiddenServicePort")) {
char *err_msg = NULL;
/* XXX: Can we rename this? */
rend_service_port_config_t *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);
goto err;
}
tor_assert(!err_msg);
smartlist_add(config->ports, portcfg);
log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
line->value, escaped(config->directory_path));
continue;
}
/* Do we allow unknown ports. */
if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
config->allow_unknown_ports = (unsigned 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);
goto err;
}
log_info(LD_CONFIG,
"HiddenServiceAllowUnknownPorts=%u for %s",
config->allow_unknown_ports, escaped(config->directory_path));
continue;
}
/* Directory group readable. */
if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) {
config->dir_group_readable = (unsigned 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);
goto err;
}
log_info(LD_CONFIG,
"HiddenServiceDirGroupReadable=%u for %s",
config->dir_group_readable, escaped(config->directory_path));
continue;
}
/* 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);
if (!ok) {
log_warn(LD_CONFIG,
"HiddenServiceMaxStreams should be between 0 and %d, not %s",
65535, line->value);
goto err;
}
log_info(LD_CONFIG,
"HiddenServiceMaxStreams=%" PRIu64 " for %s",
config->max_streams_per_rdv_circuit,
escaped(config->directory_path));
continue;
}
/* Maximum amount of streams before we close the circuit. */
if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
config->max_streams_close_circuit =
(unsigned 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);
goto err;
}
log_info(LD_CONFIG,
"HiddenServiceMaxStreamsCloseCircuit=%u for %s",
config->max_streams_close_circuit,
escaped(config->directory_path));
continue;
}
}
/* 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) &&
rend_service_non_anonymous_mode_enabled(options)) {
config->is_single_onion = 1;
}
/* Success */
return 0;
err:
return -1;
}
/* Configuration handler indexed by version number. */
static int
(*config_service_handlers[])(const config_line_t *line,
const or_options_t *options,
int validate_only,
hs_service_t *service) =
{
NULL, /* v0 */
NULL, /* v1 */
rend_config_service, /* v2 */
config_service_v3, /* v3 */
};
/* 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;
const config_line_t *line;
tor_assert(options);
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) {
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);
goto err;
}
}
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. */
}
/* Success. */
return 0;
err:
hs_service_free(service);
/* Tor main should call the free all function. */
return -1;
}
/* Copyright (c) 2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file hs_config.h
* \brief Header file containing configuration ABI/API for the HS subsytem.
**/
#ifndef TOR_HS_CONFIG_H
#define TOR_HS_CONFIG_H
#include "or.h"
/* API */
int hs_config_service_all(const or_options_t *options, int validate_only);
#endif /* TOR_HS_CONFIG_H */
......@@ -19,6 +19,91 @@
#include "hs/cell_establish_intro.h"
#include "hs/cell_common.h"
/* Set the default values for a service configuration object <b>c</b>. */
static void
set_service_default_config(hs_service_config_t *c,
const or_options_t *options)
{
tor_assert(c);
c->ports = smartlist_new();
c->directory_path = NULL;
c->descriptor_post_period = options->RendPostPeriod;
c->max_streams_per_rdv_circuit = 0;
c->max_streams_close_circuit = 0;
c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
c->allow_unknown_ports = 0;
c->is_single_onion = 0;
c->dir_group_readable = 0;
c->is_ephemeral = 0;
}
/* Allocate and initilize a service object. The service configuration will
* contain the default values. Return the newly allocated object pointer. This
* function can't fail. */
hs_service_t *
hs_service_new(const or_options_t *options)
{
hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
/* Set default configuration value. */
set_service_default_config(&service->config, options);
/* Set the default service version. */
service->version = HS_SERVICE_DEFAULT_VERSION;
return service;
}
/* Free the given <b>service</b> object and all its content. This function
* also takes care of wiping service keys from memory. It is safe to pass a
* NULL pointer. */
void
hs_service_free(hs_service_t *service)
{
if (service == NULL) {
return;
}
/* Free descriptors. */
if (service->desc_current) {
hs_descriptor_free(service->desc_current->desc);
/* Wipe keys. */
memwipe(&service->desc_current->signing_kp, 0,
sizeof(service->desc_current->signing_kp));
memwipe(&service->desc_current->blinded_kp, 0,
sizeof(service->desc_current->blinded_kp));
/* XXX: Free intro points. */
tor_free(service->desc_current);
}
if (service->desc_next) {
hs_descriptor_free(service->desc_next->desc);
/* Wipe keys. */
memwipe(&service->desc_next->signing_kp, 0,
sizeof(service->desc_next->signing_kp));
memwipe(&service->desc_next->blinded_kp, 0,
sizeof(service->desc_next->blinded_kp));
/* XXX: Free intro points. */
tor_free(service->desc_next);
}
/* Free service configuration. */
tor_free(service->config.directory_path);
if (service->config.ports) {
SMARTLIST_FOREACH(service->config.ports, rend_service_port_config_t *, p,
rend_service_port_config_free(p););
smartlist_free(service->config.ports);
}
/* Wipe service keys. */
memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
tor_free(service);
}
/* Release all global the storage of hidden service subsystem. */
void
hs_service_free_all(void)
{
rend_service_free_all();
}
/* XXX We don't currently use these functions, apart from generating unittest
data. When we start implementing the service-side support for prop224 we
should revisit these functions and use them. */
......
......@@ -192,6 +192,12 @@ typedef struct hs_service_t {
/* API */
int hs_service_config_all(const or_options_t *options, int validate_only);
void hs_service_free_all(void);
void hs_service_free(hs_service_t *service);
hs_service_t *hs_service_new(const or_options_t *options);
/* These functions are only used by unit tests and we need to expose them else
* hs_service.o ends up with no symbols in libor.a which makes clang throw a
* warning at compile time. See #21825. */
......
......@@ -50,19 +50,20 @@ LIBTOR_A_SOURCES = \
src/or/dnsserv.c \
src/or/fp_pair.c \
src/or/geoip.c \
src/or/entrynodes.c \
src/or/ext_orport.c \
src/or/hibernate.c \
src/or/hs_cache.c \
src/or/hs_circuit.c \
src/or/hs_circuitmap.c \
src/or/hs_client.c \
src/or/hs_common.c \
src/or/hs_circuit.c \
src/or/hs_config.c \
src/or/hs_descriptor.c \
src/or/hs_ident.c \
src/or/hs_intropoint.c \
src/or/hs_ntor.c \
src/or/hs_service.c \
src/or/hs_client.c \
src/or/entrynodes.c \
src/or/ext_orport.c \
src/or/hibernate.c \
src/or/keypin.c \
src/or/main.c \
src/or/microdesc.c \
......@@ -183,15 +184,16 @@ ORHEADERS = \
src/or/entrynodes.h \
src/or/hibernate.h \
src/or/hs_cache.h \
src/or/hs_common.h \
src/or/hs_circuit.h \
src/or/hs_circuitmap.h \
src/or/hs_client.h \
src/or/hs_common.h \
src/or/hs_config.h \
src/or/hs_descriptor.h \
src/or/hs_ident.h \
src/or/hs_intropoint.h \
src/or/hs_circuitmap.h \
src/or/hs_ntor.h \
src/or/hs_service.h \
src/or/hs_client.h \
src/or/hs_intropoint.h \
src/or/hs_ntor.h \
src/or/hs_service.h \
src/or/keypin.h \
src/or/main.h \
src/or/microdesc.h \
......
......@@ -3216,7 +3216,7 @@ tor_free_all(int postfork)
networkstatus_free_all();
addressmap_free_all();
dirserv_free_all();
rend_service_free_all();
hs_service_free_all();
rend_cache_free_all();
rend_service_authorization_free_all();
hs_cache_free_all();
......
......@@ -236,13 +236,18 @@ rend_service_free(rend_service_t *service)
void
rend_service_free_all(void)
{
if (!rend_service_list)
return;
SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
rend_service_free(ptr));
smartlist_free(rend_service_list);
rend_service_list = NULL;
if (rend_service_list) {
SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
rend_service_free(ptr));
smartlist_free(rend_service_list);
rend_service_list = NULL;
}
if (rend_service_staging_list) {
SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr,
rend_service_free(ptr));
smartlist_free(rend_service_staging_list);
rend_service_staging_list = NULL;
}
}
/* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there
......@@ -335,6 +340,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
/* We must have a service list, even if it's a temporary one, so we can
* check for duplicate services */
if (BUG(!s_list)) {
rend_service_free(service);
return -1;
}
......@@ -496,41 +502,6 @@ rend_service_port_config_free(rend_service_port_config_t *p)
tor_free(p);
}
/* Check the directory for <b>service</b>, and add the service to
* <b>service_list</b>, or to the global list if <b>service_list</b> is NULL.
* Only add the service to the list if <b>validate_only</b> is false.
* If <b>validate_only</b> is true, free the service.
* If <b>service</b> is NULL, ignore it, and return 0.
* Returns 0 on success, and -1 on failure.
* Takes ownership of <b>service</b>, either freeing it, or adding it to the
* global service list.
*/
STATIC int
rend_service_check_dir_and_add(smartlist_t *service_list,
const or_options_t *options,
rend_service_t *service,
int validate_only)
{
if (!service) {
/* It is ok for a service to be NULL, this means there are no services */
return 0;
}
if (rend_service_check_private_dir(options, service, !validate_only)
< 0) {
rend_service_free(service);
return -1;
}
smartlist_t *s_list = rend_get_service_list_mutable(service_list);
/* We must have a service list, even if it's a temporary one, so we can
* check for duplicate services */
if (BUG(!s_list)) {
return -1;
}
return rend_add_service(s_list, service);
}
/* Helper: Actual implementation of the pruning on reload which we've
* decoupled in order to make the unit test workeable without ugly hacks.
* Furthermore, this function does NOT free any memory but will nullify the
......@@ -657,19 +628,51 @@ rend_service_prune_list(void)
}
}
/** 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
* normal, but don't actually change the configured services.)
*/
/* Copy all the relevant data that the hs_service object contains over to the
* rend_service_t object. The reason to do so is because when configuring a
* service, we go through a generic handler that creates an hs_service_t
* object which so we have to copy the parsed values to a rend service object
* which is version 2 specific. */
static void
service_shadow_copy(rend_service_t *service, hs_service_t *hs_service)
{
hs_service_config_t *config;
tor_assert(service);
tor_assert(hs_service);
config = &hs_service->config;
service->directory = tor_strdup(config->directory_path);
service->dir_group_readable = config->dir_group_readable;
service->allow_unknown_ports = config->allow_unknown_ports;
service->max_streams_per_circuit = config->max_streams_per_rdv_circuit;
service->max_streams_close_circuit = config->max_streams_close_circuit;
service->n_intro_points_wanted = config->num_intro_points;
/* Switching ownership of the ports to the rend service object. */
smartlist_add_all(service->ports, config->ports);
smartlist_free(hs_service->config.ports);
hs_service->config.ports = NULL;
}
/* Parse the hidden service configuration starting at <b>line_</b> using the
* already configured generic service in <b>hs_service</b>. This function will
* translate the service object to a rend_service_t and add it to the
* temporary list if valid. If <b>validate_only</b> is set, parse, warn and
* return as normal but don't actually add the service to the list. */