Back to index

tor  0.2.3.18-rc
Functions
rendservice.h File Reference

Header file for rendservice.c. More...

This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

int num_rend_services (void)
 Return the number of rendezvous services we have configured.
int rend_config_services (const or_options_t *options, int validate_only)
 Set up rend_service_list, based on the values of HiddenServiceDir and HiddenServicePort in options.
int rend_service_load_keys (void)
 Load and/or generate private keys for all hidden services, possibly including keys for client authorization.
void rend_services_introduce (void)
 For every service, check how many intro points it currently has, and:
void rend_consider_services_upload (time_t now)
 Regenerate and upload rendezvous service descriptors for all services, if necessary.
void rend_hsdir_routers_changed (void)
 Called when our internal view of the directory has changed, so that we might have router descriptors of hidden service directories available that we did not have before.
void rend_consider_descriptor_republication (void)
 Consider republication of v2 rendezvous service descriptors that failed previously, but without regenerating descriptor contents.
void rend_service_intro_has_opened (origin_circuit_t *circuit)
 Called when we're done building a circuit to an introduction point: sends a RELAY_ESTABLISH_INTRO cell.
int rend_service_intro_established (origin_circuit_t *circuit, const uint8_t *request, size_t request_len)
 Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a live introduction point, and note that the service descriptor is now out-of-date.
void rend_service_rendezvous_has_opened (origin_circuit_t *circuit)
 Called once a circuit to a rendezvous point is established: sends a RELAY_COMMAND_RENDEZVOUS1 cell.
int rend_service_introduce (origin_circuit_t *circuit, const uint8_t *request, size_t request_len)
 Respond to an INTRODUCE2 cell by launching a circuit to the chosen rendezvous point.
void rend_service_relaunch_rendezvous (origin_circuit_t *oldcirc)
 Called when we fail building a rendezvous circuit at some point other than the last hop: launches a new circuit to the same rendezvous point.
int rend_service_set_connection_addr_port (edge_connection_t *conn, origin_circuit_t *circ)
 Given conn, a rendezvous exit stream, look up the hidden service for 'circ', and look up the port and address based on conn->port.
void rend_service_dump_stats (int severity)
 Log the status of introduction points for all rendezvous services at log severity severity.
void rend_service_free_all (void)
 Release all the storage held in rend_service_list.

Detailed Description

Header file for rendservice.c.

Definition in file rendservice.h.


Function Documentation

int num_rend_services ( void  )

Return the number of rendezvous services we have configured.

Definition at line 112 of file rendservice.c.

{
  if (!rend_service_list)
    return 0;
  return smartlist_len(rend_service_list);
}

Here is the caller graph for this function:

int rend_config_services ( const or_options_t options,
int  validate_only 
)

Set up rend_service_list, based on the values of HiddenServiceDir and HiddenServicePort in options.

Return 0 on success and -1 on failure. (If validate_only is set, parse, warn and return as normal, but don't actually change the configured services.)

Definition at line 327 of file rendservice.c.

{
  config_line_t *line;
  rend_service_t *service = NULL;
  rend_service_port_config_t *portcfg;
  smartlist_t *old_service_list = NULL;

  if (!validate_only) {
    old_service_list = rend_service_list;
    rend_service_list = smartlist_new();
  }

  for (line = options->RendConfigLines; line; line = line->next) {
    if (!strcasecmp(line->key, "HiddenServiceDir")) {
      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, "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));
        num_clients = smartlist_len(clients);
      }
      SMARTLIST_FOREACH_BEGIN(clients, const char *, client_name)
      {
        rend_authorized_client_t *client;
        size_t len = strlen(client_name);
        if (len < 1 || len > REND_CLIENTNAME_MAX_LEN) {
          log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an "
                              "illegal client name: '%s'. Length must be "
                              "between 1 and %d characters.",
                   client_name, REND_CLIENTNAME_MAX_LEN);
          SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
          smartlist_free(clients);
          rend_service_free(service);
          return -1;
        }
        if (strspn(client_name, REND_LEGAL_CLIENTNAME_CHARACTERS) != len) {
          log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an "
                              "illegal client name: '%s'. Valid "
                              "characters are [A-Za-z0-9+_-].",
                   client_name);
          SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
          smartlist_free(clients);
          rend_service_free(service);
          return -1;
        }
        client = tor_malloc_zero(sizeof(rend_authorized_client_t));
        client->client_name = tor_strdup(client_name);
        smartlist_add(service->clients, client);
        log_debug(LD_REND, "Adding client name '%s'", client_name);
      }
      SMARTLIST_FOREACH_END(client_name);
      SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
      smartlist_free(clients);
      /* Ensure maximum number of clients. */
      if ((service->auth_type == REND_BASIC_AUTH &&
            smartlist_len(service->clients) > 512) ||
          (service->auth_type == REND_STEALTH_AUTH &&
            smartlist_len(service->clients) > 16)) {
        log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d "
                            "client authorization entries, but only a "
                            "maximum of %d entries is allowed for "
                            "authorization type '%s'.",
                 smartlist_len(service->clients),
                 service->auth_type == REND_BASIC_AUTH ? 512 : 16,
                 service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
        rend_service_free(service);
        return -1;
      }
    } else {
      tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
      if (strcmp(line->value, "2")) {
        log_warn(LD_CONFIG,
                 "The only supported HiddenServiceVersion is 2.");
        rend_service_free(service);
        return -1;
      }
    }
  }
  if (service) {
    if (validate_only)
      rend_service_free(service);
    else
      rend_add_service(service);
  }

  /* If this is a reload and there were hidden services configured before,
   * keep the introduction points that are still needed and close the
   * other ones. */
  if (old_service_list && !validate_only) {
    smartlist_t *surviving_services = smartlist_new();
    circuit_t *circ;

    /* 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(rend_service_list, rend_service_t *, new, {
      SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, {
        if (!strcmp(old->directory, new->directory)) {
          smartlist_add_all(new->intro_nodes, old->intro_nodes);
          smartlist_clear(old->intro_nodes);
          smartlist_add(surviving_services, old);
          break;
        }
      });
    });

    /* Close introduction circuits of services we don't serve anymore. */
    /* XXXX it would be nicer if we had a nicer abstraction to use here,
     * so we could just iterate over the list of services to close, but
     * once again, this isn't critical-path code. */
    for (circ = _circuit_get_global_list(); circ; circ = circ->next) {
      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);
        int keep_it = 0;
        tor_assert(oc->rend_data);
        SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, {
          if (tor_memeq(ptr->pk_digest, oc->rend_data->rend_pk_digest,
                      DIGEST_LEN)) {
            keep_it = 1;
            break;
          }
        });
        if (keep_it)
          continue;
        log_info(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);
        /* XXXX Is there another reason we should use here? */
      }
    }
    smartlist_free(surviving_services);
    SMARTLIST_FOREACH(old_service_list, rend_service_t *, ptr,
                      rend_service_free(ptr));
    smartlist_free(old_service_list);
  }

  return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Consider republication of v2 rendezvous service descriptors that failed previously, but without regenerating descriptor contents.

Definition at line 2403 of file rendservice.c.

{
  int i;
  rend_service_t *service;

  if (!consider_republishing_rend_descriptors)
    return;
  consider_republishing_rend_descriptors = 0;

  if (!get_options()->PublishHidServDescriptors)
    return;

  for (i=0; i < smartlist_len(rend_service_list); ++i) {
    service = smartlist_get(rend_service_list, i);
    if (service->desc && !service->desc->all_uploads_performed) {
      /* If we failed in uploading a descriptor last time, try again *without*
       * updating the descriptor's contents. */
      upload_service_descriptor(service);
    }
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:

void rend_consider_services_upload ( time_t  now)

Regenerate and upload rendezvous service descriptors for all services, if necessary.

If the descriptor has been dirty enough for long enough, definitely upload; else only upload when the periodic timeout has expired.

For the first upload, pick a random time between now and two periods from now, and pick it independently for each service.

Definition at line 2356 of file rendservice.c.

{
  int i;
  rend_service_t *service;
  int rendpostperiod = get_options()->RendPostPeriod;

  if (!get_options()->PublishHidServDescriptors)
    return;

  for (i=0; i < smartlist_len(rend_service_list); ++i) {
    service = smartlist_get(rend_service_list, i);
    if (!service->next_upload_time) { /* never been uploaded yet */
      /* The fixed lower bound of 30 seconds ensures that the descriptor
       * is stable before being published. See comment below. */
      service->next_upload_time =
        now + 30 + crypto_rand_int(2*rendpostperiod);
    }
    if (service->next_upload_time < now ||
        (service->desc_is_dirty &&
         service->desc_is_dirty < now-30)) {
      /* if it's time, or if the directory servers have a wrong service
       * descriptor and ours has been stable for 30 seconds, upload a
       * new one of each format. */
      rend_service_update_descriptor(service);
      upload_service_descriptor(service);
    }
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:

void rend_hsdir_routers_changed ( void  )

Called when our internal view of the directory has changed, so that we might have router descriptors of hidden service directories available that we did not have before.

Definition at line 2394 of file rendservice.c.

Here is the caller graph for this function:

void rend_service_dump_stats ( int  severity)

Log the status of introduction points for all rendezvous services at log severity severity.

Definition at line 2429 of file rendservice.c.

{
  int i,j;
  rend_service_t *service;
  rend_intro_point_t *intro;
  const char *safe_name;
  origin_circuit_t *circ;

  for (i=0; i < smartlist_len(rend_service_list); ++i) {
    service = smartlist_get(rend_service_list, i);
    log(severity, LD_GENERAL, "Service configured in \"%s\":",
        service->directory);
    for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
      intro = smartlist_get(service->intro_nodes, j);
      safe_name = safe_str_client(intro->extend_info->nickname);

      circ = find_intro_circuit(intro, service->pk_digest);
      if (!circ) {
        log(severity, LD_GENERAL, "  Intro point %d at %s: no circuit",
            j, safe_name);
        continue;
      }
      log(severity, LD_GENERAL, "  Intro point %d at %s: circuit is %s",
          j, safe_name, circuit_state_to_string(circ->_base.state));
    }
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:

void rend_service_free_all ( void  )

Release all the storage held in rend_service_list.

Definition at line 181 of file rendservice.c.

Here is the call graph for this function:

Here is the caller graph for this function:

int rend_service_intro_established ( origin_circuit_t circuit,
const uint8_t *  request,
size_t  request_len 
)

Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a live introduction point, and note that the service descriptor is now out-of-date.

Definition at line 1680 of file rendservice.c.

{
  rend_service_t *service;
  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
  (void) request;
  (void) request_len;

  if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
    log_warn(LD_PROTOCOL,
             "received INTRO_ESTABLISHED cell on non-intro circuit.");
    goto err;
  }
  tor_assert(circuit->rend_data);
  service = rend_service_get_by_pk_digest(
                circuit->rend_data->rend_pk_digest);
  if (!service) {
    log_warn(LD_REND, "Unknown service on introduction circuit %d.",
             circuit->_base.n_circ_id);
    goto err;
  }
  service->desc_is_dirty = time(NULL);
  circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_S_INTRO);

  base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
  log_info(LD_REND,
           "Received INTRO_ESTABLISHED cell on circuit %d for service %s",
           circuit->_base.n_circ_id, serviceid);

  return 0;
 err:
  circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_TORPROTOCOL);
  return -1;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Called when we're done building a circuit to an introduction point: sends a RELAY_ESTABLISH_INTRO cell.

Definition at line 1562 of file rendservice.c.

{
  rend_service_t *service;
  size_t len;
  int r;
  char buf[RELAY_PAYLOAD_SIZE];
  char auth[DIGEST_LEN + 9];
  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
  int reason = END_CIRC_REASON_TORPROTOCOL;
  crypto_pk_t *intro_key;

  tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
#ifndef NON_ANONYMOUS_MODE_ENABLED
  tor_assert(!(circuit->build_state->onehop_tunnel));
#endif
  tor_assert(circuit->cpath);
  tor_assert(circuit->rend_data);

  base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);

  service = rend_service_get_by_pk_digest(
                circuit->rend_data->rend_pk_digest);
  if (!service) {
    log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
             serviceid, circuit->_base.n_circ_id);
    reason = END_CIRC_REASON_NOSUCHSERVICE;
    goto err;
  }

  /* If we already have enough introduction circuits for this service,
   * redefine this one as a general circuit or close it, depending. */
  if (count_established_intro_points(serviceid) >
      (int)service->n_intro_points_wanted) { /* XXX023 remove cast */
    const or_options_t *options = get_options();
    if (options->ExcludeNodes) {
      /* XXXX in some future version, we can test whether the transition is
         allowed or not given the actual nodes in the circuit.  But for now,
         this case, we might as well close the thing. */
      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
               "circuit, but we already have enough.  Closing it.");
      circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_NONE);
      return;
    } else {
      tor_assert(circuit->build_state->is_internal);
      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
               "circuit, but we already have enough. Redefining purpose to "
               "general; leaving as internal.");

      circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_C_GENERAL);

      {
        rend_data_t *rend_data = circuit->rend_data;
        circuit->rend_data = NULL;
        rend_data_free(rend_data);
      }
      {
        crypto_pk_t *intro_key = circuit->intro_key;
        circuit->intro_key = NULL;
        crypto_pk_free(intro_key);
      }

      circuit_has_opened(circuit);
      return;
    }
  }

  log_info(LD_REND,
           "Established circuit %d as introduction point for service %s",
           circuit->_base.n_circ_id, serviceid);

  /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
  intro_key = circuit->intro_key;
  /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
  r = crypto_pk_asn1_encode(intro_key, buf+2,
                            RELAY_PAYLOAD_SIZE-2);
  if (r < 0) {
    log_warn(LD_BUG, "Internal error; failed to establish intro point.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }
  len = r;
  set_uint16(buf, htons((uint16_t)len));
  len += 2;
  memcpy(auth, circuit->cpath->prev->handshake_digest, DIGEST_LEN);
  memcpy(auth+DIGEST_LEN, "INTRODUCE", 9);
  if (crypto_digest(buf+len, auth, DIGEST_LEN+9))
    goto err;
  len += 20;
  note_crypto_pk_op(REND_SERVER);
  r = crypto_pk_private_sign_digest(intro_key, buf+len, sizeof(buf)-len,
                                    buf, len);
  if (r<0) {
    log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }
  len += r;

  if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
                                   RELAY_COMMAND_ESTABLISH_INTRO,
                                   buf, len, circuit->cpath->prev)<0) {
    log_info(LD_GENERAL,
             "Couldn't send introduction request for service %s on circuit %d",
             serviceid, circuit->_base.n_circ_id);
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }

  return;
 err:
  circuit_mark_for_close(TO_CIRCUIT(circuit), reason);
}

Here is the call graph for this function:

Here is the caller graph for this function:

int rend_service_introduce ( origin_circuit_t circuit,
const uint8_t *  request,
size_t  request_len 
)

Respond to an INTRODUCE2 cell by launching a circuit to the chosen rendezvous point.

Definition at line 1038 of file rendservice.c.

{
  char *ptr, *r_cookie;
  extend_info_t *extend_info = NULL;
  char buf[RELAY_PAYLOAD_SIZE];
  char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
  rend_service_t *service;
  rend_intro_point_t *intro_point;
  int r, i, v3_shift = 0;
  size_t len, keylen;
  crypto_dh_t *dh = NULL;
  origin_circuit_t *launched = NULL;
  crypt_path_t *cpath = NULL;
  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
  char hexcookie[9];
  int circ_needs_uptime;
  int reason = END_CIRC_REASON_TORPROTOCOL;
  crypto_pk_t *intro_key;
  char intro_key_digest[DIGEST_LEN];
  int auth_type;
  size_t auth_len = 0;
  char auth_data[REND_DESC_COOKIE_LEN];
  crypto_digest_t *digest = NULL;
  time_t now = time(NULL);
  char diffie_hellman_hash[DIGEST_LEN];
  time_t *access_time;
  const or_options_t *options = get_options();

  if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) {
    log_warn(LD_PROTOCOL,
             "Got an INTRODUCE2 over a non-introduction circuit %d.",
             circuit->_base.n_circ_id);
    return -1;
  }

#ifndef NON_ANONYMOUS_MODE_ENABLED
  tor_assert(!(circuit->build_state->onehop_tunnel));
#endif
  tor_assert(circuit->rend_data);

  base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
  log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
           escaped(serviceid), circuit->_base.n_circ_id);

  /* min key length plus digest length plus nickname length */
  if (request_len < DIGEST_LEN+REND_COOKIE_LEN+(MAX_NICKNAME_LEN+1)+
      DH_KEY_LEN+42) {
    log_warn(LD_PROTOCOL, "Got a truncated INTRODUCE2 cell on circ %d.",
             circuit->_base.n_circ_id);
    return -1;
  }

  /* look up service depending on circuit. */
  service = rend_service_get_by_pk_digest(
                circuit->rend_data->rend_pk_digest);
  if (!service) {
    log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro "
             "circ for an unrecognized service %s.",
             escaped(serviceid));
    return -1;
  }

  /* use intro key instead of service key. */
  intro_key = circuit->intro_key;

  /* first DIGEST_LEN bytes of request is intro or service pk digest */
  crypto_pk_get_digest(intro_key, intro_key_digest);
  if (tor_memneq(intro_key_digest, request, DIGEST_LEN)) {
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                  (char*)request, REND_SERVICE_ID_LEN);
    log_warn(LD_REND, "Got an INTRODUCE2 cell for the wrong service (%s).",
             escaped(serviceid));
    return -1;
  }

  keylen = crypto_pk_keysize(intro_key);
  if (request_len < keylen+DIGEST_LEN) {
    log_warn(LD_PROTOCOL,
             "PK-encrypted portion of INTRODUCE2 cell was truncated.");
    return -1;
  }

  intro_point = find_intro_point(circuit);
  if (intro_point == NULL) {
    log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro circ "
             "(for service %s) with no corresponding rend_intro_point_t.",
             escaped(serviceid));
    return -1;
  }

  if (!service->accepted_intro_dh_parts)
    service->accepted_intro_dh_parts = digestmap_new();

  if (!intro_point->accepted_intro_rsa_parts)
    intro_point->accepted_intro_rsa_parts = digestmap_new();

  {
    char pkpart_digest[DIGEST_LEN];
    /* Check for replay of PK-encrypted portion. */
    crypto_digest(pkpart_digest, (char*)request+DIGEST_LEN, keylen);
    access_time = digestmap_get(intro_point->accepted_intro_rsa_parts,
                                pkpart_digest);
    if (access_time != NULL) {
      log_warn(LD_REND, "Possible replay detected! We received an "
               "INTRODUCE2 cell with same PK-encrypted part %d seconds ago. "
               "Dropping cell.", (int)(now-*access_time));
      return -1;
    }
    access_time = tor_malloc(sizeof(time_t));
    *access_time = now;
    digestmap_set(intro_point->accepted_intro_rsa_parts,
                  pkpart_digest, access_time);
  }

  /* Next N bytes is encrypted with service key */
  note_crypto_pk_op(REND_SERVER);
  r = crypto_pk_private_hybrid_decrypt(
       intro_key,buf,sizeof(buf),
       (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
       PK_PKCS1_OAEP_PADDING,1);
  if (r<0) {
    log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell.");
    return -1;
  }
  len = r;
  if (*buf == 3) {
    /* Version 3 INTRODUCE2 cell. */
    v3_shift = 1;
    auth_type = buf[1];
    switch (auth_type) {
      case REND_BASIC_AUTH:
        /* fall through */
      case REND_STEALTH_AUTH:
        auth_len = ntohs(get_uint16(buf+2));
        if (auth_len != REND_DESC_COOKIE_LEN) {
          log_info(LD_REND, "Wrong auth data size %d, should be %d.",
                   (int)auth_len, REND_DESC_COOKIE_LEN);
          return -1;
        }
        memcpy(auth_data, buf+4, sizeof(auth_data));
        v3_shift += 2+REND_DESC_COOKIE_LEN;
        break;
      case REND_NO_AUTH:
        break;
      default:
        log_info(LD_REND, "Unknown authorization type '%d'", auth_type);
    }

    /* Skip the timestamp field.  We no longer use it. */
    v3_shift += 4;
  }
  if (*buf == 2 || *buf == 3) {
    /* Version 2 INTRODUCE2 cell. */
    int klen;
    extend_info = tor_malloc_zero(sizeof(extend_info_t));
    tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+v3_shift+1));
    extend_info->port = ntohs(get_uint16(buf+v3_shift+5));
    memcpy(extend_info->identity_digest, buf+v3_shift+7,
           DIGEST_LEN);
    extend_info->nickname[0] = '$';
    base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
                  extend_info->identity_digest, DIGEST_LEN);

    klen = ntohs(get_uint16(buf+v3_shift+7+DIGEST_LEN));
    if ((int)len != v3_shift+7+DIGEST_LEN+2+klen+20+128) {
      log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.",
               (int)len, *buf);
      reason = END_CIRC_REASON_TORPROTOCOL;
      goto err;
    }
    extend_info->onion_key =
        crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen);
    if (!extend_info->onion_key) {
      log_warn(LD_PROTOCOL, "Error decoding onion key in version %d "
                            "INTRODUCE2 cell.", *buf);
      reason = END_CIRC_REASON_TORPROTOCOL;
      goto err;
    }
    ptr = buf+v3_shift+7+DIGEST_LEN+2+klen;
    len -= v3_shift+7+DIGEST_LEN+2+klen;
  } else {
    char *rp_nickname;
    size_t nickname_field_len;
    const node_t *node;
    int version;
    if (*buf == 1) {
      rp_nickname = buf+1;
      nickname_field_len = MAX_HEX_NICKNAME_LEN+1;
      version = 1;
    } else {
      nickname_field_len = MAX_NICKNAME_LEN+1;
      rp_nickname = buf;
      version = 0;
    }
    ptr=memchr(rp_nickname,0,nickname_field_len);
    if (!ptr || ptr == rp_nickname) {
      log_warn(LD_PROTOCOL,
               "Couldn't find a nul-padded nickname in INTRODUCE2 cell.");
      return -1;
    }
    if ((version == 0 && !is_legal_nickname(rp_nickname)) ||
        (version == 1 && !is_legal_nickname_or_hexdigest(rp_nickname))) {
      log_warn(LD_PROTOCOL, "Bad nickname in INTRODUCE2 cell.");
      return -1;
    }
    /* Okay, now we know that a nickname is at the start of the buffer. */
    ptr = rp_nickname+nickname_field_len;
    len -= nickname_field_len;
    len -= rp_nickname - buf; /* also remove header space used by version, if
                               * any */
    node = node_get_by_nickname(rp_nickname, 0);
    if (!node) {
      log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.",
               escaped_safe_str_client(rp_nickname));
      /* XXXX Add a no-such-router reason? */
      reason = END_CIRC_REASON_TORPROTOCOL;
      goto err;
    }

    extend_info = extend_info_from_node(node, 0);
  }

  if (len != REND_COOKIE_LEN+DH_KEY_LEN) {
    log_warn(LD_PROTOCOL, "Bad length %u for INTRODUCE2 cell.", (int)len);
    reason = END_CIRC_REASON_TORPROTOCOL;
    goto err;
  }

  /* Check if we'd refuse to talk to this router */
  if (options->StrictNodes &&
      routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) {
    log_warn(LD_REND, "Client asked to rendezvous at a relay that we "
             "exclude, and StrictNodes is set. Refusing service.");
    reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */
    goto err;
  }

  r_cookie = ptr;
  base16_encode(hexcookie,9,r_cookie,4);

  /* Determine hash of Diffie-Hellman, part 1 to detect replays. */
  digest = crypto_digest_new();
  crypto_digest_add_bytes(digest, ptr+REND_COOKIE_LEN, DH_KEY_LEN);
  crypto_digest_get_digest(digest, diffie_hellman_hash, DIGEST_LEN);
  crypto_digest_free(digest);

  /* Check whether there is a past request with the same Diffie-Hellman,
   * part 1. */
  access_time = digestmap_get(service->accepted_intro_dh_parts,
                              diffie_hellman_hash);
  if (access_time != NULL) {
    /* A Tor client will send a new INTRODUCE1 cell with the same rend
     * cookie and DH public key as its previous one if its intro circ
     * times out while in state CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT .
     * If we received the first INTRODUCE1 cell (the intro-point relay
     * converts it into an INTRODUCE2 cell), we are already trying to
     * connect to that rend point (and may have already succeeded);
     * drop this cell. */
    log_info(LD_REND, "We received an "
             "INTRODUCE2 cell with same first part of "
             "Diffie-Hellman handshake %d seconds ago. Dropping "
             "cell.",
             (int) (now - *access_time));
    goto err;
  }

  /* Add request to access history, including time and hash of Diffie-Hellman,
   * part 1, and possibly remove requests from the history that are older than
   * one hour. */
  access_time = tor_malloc(sizeof(time_t));
  *access_time = now;
  digestmap_set(service->accepted_intro_dh_parts,
                diffie_hellman_hash, access_time);
  if (service->last_cleaned_accepted_intro_dh_parts + REND_REPLAY_TIME_INTERVAL
      < now)
    clean_accepted_intro_dh_parts(service, now);

  /* If the service performs client authorization, check included auth data. */
  if (service->clients) {
    if (auth_len > 0) {
      if (rend_check_authorization(service, auth_data)) {
        log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid.");
      } else {
        log_info(LD_REND, "The authorization data that are contained in "
                 "the INTRODUCE2 cell are invalid. Dropping cell.");
        reason = END_CIRC_REASON_CONNECTFAILED;
        goto err;
      }
    } else {
      log_info(LD_REND, "INTRODUCE2 cell does not contain authentication "
               "data, but we require client authorization. Dropping cell.");
      reason = END_CIRC_REASON_CONNECTFAILED;
      goto err;
    }
  }

  /* Try DH handshake... */
  dh = crypto_dh_new(DH_TYPE_REND);
  if (!dh || crypto_dh_generate_public(dh)<0) {
    log_warn(LD_BUG,"Internal error: couldn't build DH state "
             "or generate public key.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }
  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN,
                               DH_KEY_LEN, keys,
                               DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
    log_warn(LD_BUG, "Internal error: couldn't complete DH handshake");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }

  circ_needs_uptime = rend_service_requires_uptime(service);

  /* help predict this next time */
  rep_hist_note_used_internal(now, circ_needs_uptime, 1);

  /* Launch a circuit to alice's chosen rendezvous point.
   */
  for (i=0;i<MAX_REND_FAILURES;i++) {
    int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
    if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
    launched = circuit_launch_by_extend_info(
                        CIRCUIT_PURPOSE_S_CONNECT_REND, extend_info, flags);

    if (launched)
      break;
  }
  if (!launched) { /* give up */
    log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous "
             "point %s for service %s.",
             safe_str_client(extend_info_describe(extend_info)),
             serviceid);
    reason = END_CIRC_REASON_CONNECTFAILED;
    goto err;
  }
  log_info(LD_REND,
           "Accepted intro; launching circuit to %s "
           "(cookie %s) for service %s.",
           safe_str_client(extend_info_describe(extend_info)),
           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, r_cookie, REND_COOKIE_LEN);
  strlcpy(launched->rend_data->onion_address, service->service_id,
          sizeof(launched->rend_data->onion_address));

  launched->build_state->service_pending_final_cpath_ref =
    tor_malloc_zero(sizeof(crypt_path_reference_t));
  launched->build_state->service_pending_final_cpath_ref->refcount = 1;

  launched->build_state->service_pending_final_cpath_ref->cpath = cpath =
    tor_malloc_zero(sizeof(crypt_path_t));
  cpath->magic = CRYPT_PATH_MAGIC;
  launched->build_state->expiry_time = now + MAX_REND_TIMEOUT;

  cpath->dh_handshake_state = dh;
  dh = NULL;
  if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
    goto err;
  memcpy(cpath->handshake_digest, keys, DIGEST_LEN);
  if (extend_info) extend_info_free(extend_info);

  memset(keys, 0, sizeof(keys));
  return 0;
 err:
  memset(keys, 0, sizeof(keys));
  if (dh) crypto_dh_free(dh);
  if (launched)
    circuit_mark_for_close(TO_CIRCUIT(launched), reason);
  if (extend_info) extend_info_free(extend_info);
  return -1;
}

Here is the caller graph for this function:

int rend_service_load_keys ( void  )

Load and/or generate private keys for all hidden services, possibly including keys for client authorization.

Return 0 on success, -1 on failure.

Definition at line 615 of file rendservice.c.

{
  int r = 0;
  char fname[512];
  char buf[1500];

  SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) {
    if (s->private_key)
      continue;
    log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
             s->directory);

    /* Check/create directory */
    if (check_private_dir(s->directory, CPD_CREATE, get_options()->User) < 0)
      return -1;

    /* Load key */
    if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) ||
        strlcat(fname,PATH_SEPARATOR"private_key",sizeof(fname))
                                                  >= sizeof(fname)) {
      log_warn(LD_CONFIG, "Directory name too long to store key file: \"%s\".",
               s->directory);
      return -1;
    }
    s->private_key = init_key_from_file(fname, 1, LOG_ERR);
    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.");
      return -1;
    }
    if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) ||
        strlcat(fname,PATH_SEPARATOR"hostname",sizeof(fname))
                                                  >= sizeof(fname)) {
      log_warn(LD_CONFIG, "Directory name too long to store hostname file:"
               " \"%s\".", s->directory);
      return -1;
    }
    tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id);
    if (write_str_to_file(fname,buf,0)<0) {
      log_warn(LD_CONFIG, "Could not write onion address to hostname file.");
      return -1;
    }

    /* If client authorization is configured, load or generate keys. */
    if (s->auth_type != REND_NO_AUTH) {
      char *client_keys_str = NULL;
      strmap_t *parsed_clients = strmap_new();
      char cfname[512];
      FILE *cfile, *hfile;
      open_file_t *open_cfile = NULL, *open_hfile = NULL;

      /* Load client keys and descriptor cookies, if available. */
      if (tor_snprintf(cfname, sizeof(cfname), "%s"PATH_SEPARATOR"client_keys",
                       s->directory)<0) {
        log_warn(LD_CONFIG, "Directory name too long to store client keys "
                 "file: \"%s\".", s->directory);
        goto err;
      }
      client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL);
      if (client_keys_str) {
        if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) {
          log_warn(LD_CONFIG, "Previously stored client_keys file could not "
                              "be parsed.");
          goto err;
        } else {
          log_info(LD_CONFIG, "Parsed %d previously stored client entries.",
                   strmap_size(parsed_clients));
          tor_free(client_keys_str);
        }
      }

      /* Prepare client_keys and hostname files. */
      if (!(cfile = start_writing_to_stdio_file(cfname,
                                                OPEN_FLAGS_REPLACE | O_TEXT,
                                                0600, &open_cfile))) {
        log_warn(LD_CONFIG, "Could not open client_keys file %s",
                 escaped(cfname));
        goto err;
      }
      if (!(hfile = start_writing_to_stdio_file(fname,
                                                OPEN_FLAGS_REPLACE | O_TEXT,
                                                0600, &open_hfile))) {
        log_warn(LD_CONFIG, "Could not open hostname file %s", escaped(fname));
        goto err;
      }

      /* Either use loaded keys for configured clients or generate new
       * ones if a client is new. */
      SMARTLIST_FOREACH_BEGIN(s->clients, rend_authorized_client_t *, client)
      {
        char desc_cook_out[3*REND_DESC_COOKIE_LEN_BASE64+1];
        char service_id[16+1];
        rend_authorized_client_t *parsed =
            strmap_get(parsed_clients, client->client_name);
        int written;
        size_t len;
        /* Copy descriptor cookie from parsed entry or create new one. */
        if (parsed) {
          memcpy(client->descriptor_cookie, parsed->descriptor_cookie,
                 REND_DESC_COOKIE_LEN);
        } else {
          crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN);
        }
        if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1,
                          client->descriptor_cookie,
                          REND_DESC_COOKIE_LEN) < 0) {
          log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
          strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
          return -1;
        }
        /* Copy client key from parsed entry or create new one if required. */
        if (parsed && parsed->client_key) {
          client->client_key = crypto_pk_dup_key(parsed->client_key);
        } else if (s->auth_type == REND_STEALTH_AUTH) {
          /* Create private key for client. */
          crypto_pk_t *prkey = NULL;
          if (!(prkey = crypto_pk_new())) {
            log_warn(LD_BUG,"Error constructing client key");
            goto err;
          }
          if (crypto_pk_generate_key(prkey)) {
            log_warn(LD_BUG,"Error generating client key");
            crypto_pk_free(prkey);
            goto err;
          }
          if (crypto_pk_check_key(prkey) <= 0) {
            log_warn(LD_BUG,"Generated client key seems invalid");
            crypto_pk_free(prkey);
            goto err;
          }
          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);
        if (written < 0) {
          log_warn(LD_BUG, "Could not write client entry.");
          goto err;
        }
        if (client->client_key) {
          char *client_key_out = NULL;
          crypto_pk_write_private_key_to_string(client->client_key,
                                                &client_key_out, &len);
          if (rend_get_service_id(client->client_key, service_id)<0) {
            log_warn(LD_BUG, "Internal error: couldn't encode service ID.");
            tor_free(client_key_out);
            goto err;
          }
          written = tor_snprintf(buf + written, sizeof(buf) - written,
                                 "client-key\n%s", client_key_out);
          tor_free(client_key_out);
          if (written < 0) {
            log_warn(LD_BUG, "Could not write client entry.");
            goto err;
          }
        }

        if (fputs(buf, cfile) < 0) {
          log_warn(LD_FS, "Could not append client entry to file: %s",
                   strerror(errno));
          goto err;
        }

        /* Add line to hostname file. */
        if (s->auth_type == REND_BASIC_AUTH) {
          /* Remove == signs (newline has been removed above). */
          desc_cook_out[strlen(desc_cook_out)-2] = '\0';
          tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n",
                       s->service_id, desc_cook_out, client->client_name);
        } else {
          char extended_desc_cookie[REND_DESC_COOKIE_LEN+1];
          memcpy(extended_desc_cookie, client->descriptor_cookie,
                 REND_DESC_COOKIE_LEN);
          extended_desc_cookie[REND_DESC_COOKIE_LEN] =
              ((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) {
            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. */
          tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n",
                       service_id, desc_cook_out, client->client_name);
        }

        if (fputs(buf, hfile)<0) {
          log_warn(LD_FS, "Could not append host entry to file: %s",
                   strerror(errno));
          goto err;
        }
      }
      SMARTLIST_FOREACH_END(client);

      goto done;
    err:
      r = -1;
    done:
      tor_free(client_keys_str);
      strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
      if (r<0) {
        if (open_cfile)
          abort_writing_to_file(open_cfile);
        if (open_hfile)
          abort_writing_to_file(open_hfile);
        return r;
      } else {
        finish_writing_to_file(open_cfile);
        finish_writing_to_file(open_hfile);
      }
    }
  } SMARTLIST_FOREACH_END(s);
  return r;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Called when we fail building a rendezvous circuit at some point other than the last hop: launches a new circuit to the same rendezvous point.

Definition at line 1422 of file rendservice.c.

{
  origin_circuit_t *newcirc;
  cpath_build_state_t *newstate, *oldstate;

  tor_assert(oldcirc->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);

  /* Don't relaunch the same rend circ twice. */
  if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) {
    log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; "
             "not relaunching it again.",
             oldcirc->build_state ?
             safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
             : "*unknown*");
    return;
  }
  oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1;

  if (!oldcirc->build_state ||
      oldcirc->build_state->failure_count > MAX_REND_FAILURES ||
      oldcirc->build_state->expiry_time < time(NULL)) {
    log_info(LD_REND,
             "Attempt to build circuit to %s for rendezvous has failed "
             "too many times or expired; giving up.",
             oldcirc->build_state ?
             safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
             : "*unknown*");
    return;
  }

  oldstate = oldcirc->build_state;
  tor_assert(oldstate);

  if (oldstate->service_pending_final_cpath_ref == NULL) {
    log_info(LD_REND,"Skipping relaunch of circ that failed on its first hop. "
             "Initiator will retry.");
    return;
  }

  log_info(LD_REND,"Reattempting rendezvous circuit to '%s'",
           safe_str(extend_info_describe(oldstate->chosen_exit)));

  newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
                            oldstate->chosen_exit,
                            CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL);

  if (!newcirc) {
    log_warn(LD_REND,"Couldn't relaunch rendezvous circuit to '%s'.",
             safe_str(extend_info_describe(oldstate->chosen_exit)));
    return;
  }
  newstate = newcirc->build_state;
  tor_assert(newstate);
  newstate->failure_count = oldstate->failure_count+1;
  newstate->expiry_time = oldstate->expiry_time;
  newstate->service_pending_final_cpath_ref =
    oldstate->service_pending_final_cpath_ref;
  ++(newstate->service_pending_final_cpath_ref->refcount);

  newcirc->rend_data = rend_data_dup(oldcirc->rend_data);
}

Here is the call graph for this function:

Here is the caller graph for this function:

Called once a circuit to a rendezvous point is established: sends a RELAY_COMMAND_RENDEZVOUS1 cell.

Definition at line 1721 of file rendservice.c.

{
  rend_service_t *service;
  char buf[RELAY_PAYLOAD_SIZE];
  crypt_path_t *hop;
  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
  char hexcookie[9];
  int reason;

  tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
  tor_assert(circuit->cpath);
  tor_assert(circuit->build_state);
#ifndef NON_ANONYMOUS_MODE_ENABLED
  tor_assert(!(circuit->build_state->onehop_tunnel));
#endif
  tor_assert(circuit->rend_data);
  hop = circuit->build_state->service_pending_final_cpath_ref->cpath;

  base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4);
  base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);

  log_info(LD_REND,
           "Done building circuit %d to rendezvous with "
           "cookie %s for service %s",
           circuit->_base.n_circ_id, hexcookie, serviceid);

  /* Clear the 'in-progress HS circ has timed out' flag for
   * consistency with what happens on the client side; this line has
   * no effect on Tor's behaviour. */
  circuit->hs_circ_has_timed_out = 0;

  /* If hop is NULL, another rend circ has already connected to this
   * rend point.  Close this circ. */
  if (hop == NULL) {
    log_info(LD_REND, "Another rend circ has already reached this rend point; "
             "closing this rend circ.");
    reason = END_CIRC_REASON_NONE;
    goto err;
  }

  /* Remove our final cpath element from the reference, so that no
   * other circuit will try to use it.  Store it in
   * pending_final_cpath for now to ensure that it will be freed if
   * our rendezvous attempt fails. */
  circuit->build_state->pending_final_cpath = hop;
  circuit->build_state->service_pending_final_cpath_ref->cpath = NULL;

  service = rend_service_get_by_pk_digest(
                circuit->rend_data->rend_pk_digest);
  if (!service) {
    log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
             "rendezvous circuit.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }

  /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
  memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN);
  if (crypto_dh_get_public(hop->dh_handshake_state,
                           buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
    log_warn(LD_GENERAL,"Couldn't get DH public key.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }
  memcpy(buf+REND_COOKIE_LEN+DH_KEY_LEN, hop->handshake_digest,
         DIGEST_LEN);

  /* Send the cell */
  if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
                                   RELAY_COMMAND_RENDEZVOUS1,
                                   buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN,
                                   circuit->cpath->prev)<0) {
    log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell.");
    reason = END_CIRC_REASON_INTERNAL;
    goto err;
  }

  crypto_dh_free(hop->dh_handshake_state);
  hop->dh_handshake_state = NULL;

  /* Append the cpath entry. */
  hop->state = CPATH_STATE_OPEN;
  /* set the windows to default. these are the windows
   * that bob thinks alice has.
   */
  hop->package_window = circuit_initial_package_window();
  hop->deliver_window = CIRCWINDOW_START;

  onion_append_to_cpath(&circuit->cpath, hop);
  circuit->build_state->pending_final_cpath = NULL; /* prevent double-free */

  /* Change the circuit purpose. */
  circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_S_REND_JOINED);

  return;
 err:
  circuit_mark_for_close(TO_CIRCUIT(circuit), reason);
}

Here is the call graph for this function:

Here is the caller graph for this function:

Given conn, a rendezvous exit stream, look up the hidden service for 'circ', and look up the port and address based on conn->port.

Assign the actual conn->addr and conn->port. Return -1 if failure, or 0 for success.

Definition at line 2463 of file rendservice.c.

{
  rend_service_t *service;
  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
  smartlist_t *matching_ports;
  rend_service_port_config_t *chosen_port;

  tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
  tor_assert(circ->rend_data);
  log_debug(LD_REND,"beginning to hunt for addr/port");
  base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
  service = rend_service_get_by_pk_digest(
                circ->rend_data->rend_pk_digest);
  if (!service) {
    log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
             "rendezvous circuit %d; closing.",
             serviceid, circ->_base.n_circ_id);
    return -1;
  }
  matching_ports = smartlist_new();
  SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
  {
    if (conn->_base.port == p->virtual_port) {
      smartlist_add(matching_ports, p);
    }
  });
  chosen_port = smartlist_choose(matching_ports);
  smartlist_free(matching_ports);
  if (chosen_port) {
    tor_addr_copy(&conn->_base.addr, &chosen_port->real_addr);
    conn->_base.port = chosen_port->real_port;
    return 0;
  }
  log_info(LD_REND, "No virtual port mapping exists for port %d on service %s",
           conn->_base.port,serviceid);
  return -1;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void rend_services_introduce ( void  )

For every service, check how many intro points it currently has, and:

  • Pick new intro points as necessary.
  • Launch circuits to any new intro points.

Definition at line 2152 of file rendservice.c.

{
  int i,j,r;
  const node_t *node;
  rend_service_t *service;
  rend_intro_point_t *intro;
  int intro_point_set_changed, prev_intro_nodes;
  unsigned int n_intro_points_unexpired;
  unsigned int n_intro_points_to_open;
  smartlist_t *intro_nodes;
  time_t now;
  const or_options_t *options = get_options();

  intro_nodes = smartlist_new();
  now = time(NULL);

  for (i=0; i < smartlist_len(rend_service_list); ++i) {
    smartlist_clear(intro_nodes);
    service = smartlist_get(rend_service_list, i);

    tor_assert(service);

    /* intro_point_set_changed becomes non-zero iff the set of intro
     * points to be published in service's descriptor has changed. */
    intro_point_set_changed = 0;

    /* n_intro_points_unexpired collects the number of non-expiring
     * intro points we have, so that we know how many new intro
     * circuits we need to launch for this service. */
    n_intro_points_unexpired = 0;

    if (now > service->intro_period_started+INTRO_CIRC_RETRY_PERIOD) {
      /* One period has elapsed; we can try building circuits again. */
      service->intro_period_started = now;
      service->n_intro_circuits_launched = 0;
    } else if (service->n_intro_circuits_launched >=
               MAX_INTRO_CIRCS_PER_PERIOD) {
      /* We have failed too many times in this period; wait for the next
       * one before we try again. */
      continue;
    }

    /* Find out which introduction points we have in progress for this
       service. */
    SMARTLIST_FOREACH_BEGIN(service->intro_nodes, rend_intro_point_t *,
                            intro) {
      origin_circuit_t *intro_circ =
        find_intro_circuit(intro, service->pk_digest);

      if (intro->time_expiring + INTRO_POINT_EXPIRATION_GRACE_PERIOD > now) {
        /* This intro point has completely expired.  Remove it, and
         * mark the circuit for close if it's still alive. */
        if (intro_circ != NULL) {
          circuit_mark_for_close(TO_CIRCUIT(intro_circ),
                                 END_CIRC_REASON_FINISHED);
        }
        rend_intro_point_free(intro);
        intro = NULL; /* SMARTLIST_DEL_CURRENT takes a name, not a value. */
        SMARTLIST_DEL_CURRENT(service->intro_nodes, intro);
        /* We don't need to set intro_point_set_changed here, because
         * this intro point wouldn't have been published in a current
         * descriptor anyway. */
        continue;
      }

      node = node_get_by_id(intro->extend_info->identity_digest);
      if (!node || !intro_circ) {
        int removing_this_intro_point_changes_the_intro_point_set = 1;
        log_info(LD_REND, "Giving up on %s as intro point for %s"
                 " (circuit disappeared).",
                 safe_str_client(extend_info_describe(intro->extend_info)),
                 safe_str_client(service->service_id));
        rend_service_note_removing_intro_point(service, intro);
        if (intro->time_expiring != -1) {
          log_info(LD_REND, "We were already expiring the intro point; "
                   "no need to mark the HS descriptor as dirty over this.");
          removing_this_intro_point_changes_the_intro_point_set = 0;
        } else if (intro->listed_in_last_desc) {
          log_info(LD_REND, "The intro point we are giving up on was "
                   "included in the last published descriptor. "
                   "Marking current descriptor as dirty.");
          service->desc_is_dirty = now;
        }
        rend_intro_point_free(intro);
        intro = NULL; /* SMARTLIST_DEL_CURRENT takes a name, not a value. */
        SMARTLIST_DEL_CURRENT(service->intro_nodes, intro);
        if (removing_this_intro_point_changes_the_intro_point_set)
          intro_point_set_changed = 1;
      }

      if (intro != NULL && intro_point_should_expire_now(intro, now)) {
        log_info(LD_REND, "Expiring %s as intro point for %s.",
                 safe_str_client(extend_info_describe(intro->extend_info)),
                 safe_str_client(service->service_id));

        rend_service_note_removing_intro_point(service, intro);

        /* The polite (and generally Right) way to expire an intro
         * point is to establish a new one to replace it, publish a
         * new descriptor that doesn't list any expiring intro points,
         * and *then*, once our upload attempts for the new descriptor
         * have ended (whether in success or failure), close the
         * expiring intro points.
         *
         * Unfortunately, we can't find out when the new descriptor
         * has actually been uploaded, so we'll have to settle for a
         * five-minute timer.  Start it.  XXXX024 This sucks. */
        intro->time_expiring = now;

        intro_point_set_changed = 1;
      }

      if (intro != NULL && intro->time_expiring == -1)
        ++n_intro_points_unexpired;

      if (node)
        smartlist_add(intro_nodes, (void*)node);
    } SMARTLIST_FOREACH_END(intro);

    if (!intro_point_set_changed &&
        (n_intro_points_unexpired >= service->n_intro_points_wanted)) {
      continue;
    }

    /* Remember how many introduction circuits we started with.
     *
     * prev_intro_nodes serves a different purpose than
     * n_intro_points_unexpired -- this variable tells us where our
     * previously-created intro points end and our new ones begin in
     * the intro-point list, so we don't have to launch the circuits
     * at the same time as we create the intro points they correspond
     * to.  XXXX This is daft. */
    prev_intro_nodes = smartlist_len(service->intro_nodes);

    /* We have enough directory information to start establishing our
     * intro points. We want to end up with n_intro_points_wanted
     * intro points, but if we're just starting, we launch two extra
     * circuits and use the first n_intro_points_wanted that complete.
     *
     * The ones after the first three will be converted to 'general'
     * internal circuits in rend_service_intro_has_opened(), and then
     * we'll drop them from the list of intro points next time we
     * go through the above "find out which introduction points we have
     * in progress" loop. */
    n_intro_points_to_open = (service->n_intro_points_wanted +
                              (prev_intro_nodes == 0 ? 2 : 0));
    for (j = (int)n_intro_points_unexpired;
         j < (int)n_intro_points_to_open;
         ++j) { /* XXXX remove casts */
      router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC;
      if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION)
        flags |= CRN_ALLOW_INVALID;
      node = router_choose_random_node(intro_nodes,
                                       options->ExcludeNodes, flags);
      if (!node) {
        log_warn(LD_REND,
                 "Could only establish %d introduction points for %s; "
                 "wanted %u.",
                 smartlist_len(service->intro_nodes), service->service_id,
                 n_intro_points_to_open);
        break;
      }
      intro_point_set_changed = 1;
      smartlist_add(intro_nodes, (void*)node);
      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));
      intro->time_published = -1;
      intro->time_to_expire = -1;
      intro->time_expiring = -1;
      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)),
               safe_str_client(service->service_id));
    }

    /* If there's no need to launch new circuits, stop here. */
    if (!intro_point_set_changed)
      continue;

    /* Establish new introduction points. */
    for (j=prev_intro_nodes; j < smartlist_len(service->intro_nodes); ++j) {
      intro = smartlist_get(service->intro_nodes, j);
      r = rend_service_launch_establish_intro(service, intro);
      if (r<0) {
        log_warn(LD_REND, "Error launching circuit to node %s for service %s.",
                 safe_str_client(extend_info_describe(intro->extend_info)),
                 safe_str_client(service->service_id));
      }
    }
  }
  smartlist_free(intro_nodes);
}

Here is the call graph for this function:

Here is the caller graph for this function: