Large diffs are not rendered by default.

@@ -1731,6 +1731,9 @@ typedef struct control_connection_t {
* connection. */
unsigned int is_owning_control_connection:1;

/** List of ephemeral onion services belonging to this connection. */
smartlist_t *ephemeral_onion_services;

/** If we have sent an AUTHCHALLENGE reply on this connection and
* have not received a successful AUTHENTICATE command, points to
* the value which the client must send to authenticate itself;
@@ -42,9 +42,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,
@@ -102,7 +108,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 */
@@ -141,6 +148,14 @@ typedef struct rend_service_t {
int allow_unknown_ports;
} 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;
@@ -233,7 +248,7 @@ rend_service_free_all(void)

/** Validate <b>service</b> and add it to rend_service_list if possible.
*/
static void
static int
rend_add_service(rend_service_t *service)
{
int i;
@@ -245,16 +260,17 @@ rend_add_service(rend_service_t *service)
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 +288,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 +324,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
@@ -632,6 +653,28 @@ 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? */
@@ -685,6 +728,128 @@ rend_config_services(const or_options_t *options, int validate_only)
return 0;
}

/** Add the ephemeral service <b>pk</b>/<b>port_cfg_strs</b> if possible.
* Returns 0 on success, and < 0 on failure, with -1 indicating an internal
* error, -2 indicating address generation failure, -3 indicating a collision
* with an existing address, and -4 indicating an invalid virtport/target.
* On success, service_id_out is set to a string suitable as a unique
* identifier for the newly created ephemeral service. If a service id
* pointer is provided, it is the caller's responsibility to sanitize and
* free any provided service id.
*/
int
rend_service_add_ephemeral(crypto_pk_t *pk,
const smartlist_t *port_cfg_strs,
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 = smartlist_new();
if (rend_service_derive_key_digests(s)<0) {
rend_service_free(s);
return -2;
}

/* 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 -3;
}
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 -3;
}

/* Do the rest of the initialization, now that the key has been validated. */
s->intro_period_started = time(NULL);
s->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
SMARTLIST_FOREACH(port_cfg_strs, const char*, cp, {
rend_service_port_config_t *p = parse_port_config(cp);
if (!p) {
rend_service_free(s);
return -4;
}
smartlist_add(s->ports, p);
});
if (smartlist_len(s->ports) == 0) {
log_warn(LD_CONFIG, "At least one VIRTPORT/TARGET must be specified.");
rend_service_free(s);
return -4;
}

/* Initialize the service. */
if (rend_add_service(s)) {
rend_service_free(s);
return -1;
}
*service_id_out = tor_strdup(s->service_id);

log_debug(LD_CONFIG, "Added ephemeral Onion Service: %s", s->service_id);
return 0;
}

/** 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 +934,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 +953,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 +1016,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)) {
@@ -1080,6 +1261,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.
*/
@@ -101,5 +101,10 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn,
void rend_service_dump_stats(int severity);
void rend_service_free_all(void);

int rend_service_add_ephemeral(crypto_pk_t *pk,
const smartlist_t *port_cfg_strs,
char **service_id_out);
int rend_service_del_ephemeral(const char *service_id);

#endif