Commit message: hs-v2: Only log once the connection warning to v2

Closes #40474

Signed-off-by: David Goulet <dgoulet@torproject.org>
CVE: CVE-2021-22929
CWE: CWE-532
Vulnerability description: An information disclosure exists in Brave Browser Desktop prior to version 1.28.62, where logged warning messages that included timestamps of connections to V2 onion domains in tor.log.

Project: tor
File: connection_edge.c

MOCK_IMPL(void,
connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason,
                                int line, const char *file))
{
  connection_t *base_conn = ENTRY_TO_CONN(conn);
  tor_assert(base_conn->type == CONN_TYPE_AP);
  ENTRY_TO_EDGE_CONN(conn)->edge_has_sent_end = 1; /* no circ yet */

  if (base_conn->marked_for_close) {
    /* This call will warn as appropriate. */
    connection_mark_for_close_(base_conn, line, file);
    return;
  }

  if (!conn->socks_request->has_finished) {
    if (endreason & END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED)
      log_warn(LD_BUG,
               "stream (marked at %s:%d) sending two socks replies?",
               file, line);

    if (SOCKS_COMMAND_IS_CONNECT(conn->socks_request->command))
      connection_ap_handshake_socks_reply(conn, NULL, 0, endreason);
    else if (SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command))
      connection_ap_handshake_socks_resolved(conn,
                                             RESOLVED_TYPE_ERROR_TRANSIENT,
                                             0, NULL, -1, -1);
    else /* unknown or no handshake at all. send no response. */
      conn->socks_request->has_finished = 1;
  }

  connection_mark_and_flush_(base_conn, line, file);

  ENTRY_TO_EDGE_CONN(conn)->end_reason = endreason;
}

Project: tor
File: connection_edge.c

connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
                                           origin_circuit_t *circ,
                                           crypt_path_t *cpath)
{
  socks_request_t *socks = conn->socks_request;
  const or_options_t *options = get_options();
  connection_t *base_conn = ENTRY_TO_CONN(conn);
  time_t now = time(NULL);
  rewrite_result_t rr;

  /* First we'll do the rewrite part.  Let's see if we get a reasonable
   * answer.
   */
  memset(&rr, 0, sizeof(rr));
  connection_ap_handshake_rewrite(conn,&rr);

  if (rr.should_close) {
    /* connection_ap_handshake_rewrite told us to close the connection:
     * either because it sent back an answer, or because it sent back an
     * error */
    connection_mark_unattached_ap(conn, rr.end_reason);
    if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK))
      return 0;
    else
      return -1;
  }

  const time_t map_expires = rr.map_expires;
  const int automap = rr.automap;
  const addressmap_entry_source_t exit_source = rr.exit_source;

  /* Now see whether the hostname is bogus.  This could happen because of an
   * onion hostname whose format we don't recognize. */
  hostname_type_t addresstype;
  if (!parse_extended_hostname(socks->address, &addresstype)) {
    control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                escaped(socks->address));
    if (addresstype == BAD_HOSTNAME) {
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
    }
    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
    return -1;
  }

  /* If this is a .exit hostname, strip off the .name.exit part, and
   * see whether we're willing to connect there, and otherwise handle the
   * .exit address.
   *
   * We'll set chosen_exit_name and/or close the connection as appropriate.
   */
  if (addresstype == EXIT_HOSTNAME) {
    /* If StrictNodes is not set, then .exit overrides ExcludeNodes but
     * not ExcludeExitNodes. */
    routerset_t *excludeset = options->StrictNodes ?
      options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes;
    const node_t *node = NULL;

    /* If this .exit was added by an AUTOMAP, then it came straight from
     * a user.  That's not safe. */
    if (exit_source == ADDRMAPSRC_AUTOMAP) {
      /* Whoops; this one is stale.  It must have gotten added earlier?
       * (Probably this is not possible, since AllowDotExit no longer
       * exists.) */
      log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      tor_assert_nonfatal_unreached();
      return -1;
    }

    /* Double-check to make sure there are no .exits coming from
     * impossible/weird sources. */
    if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) {
      /* It shouldn't be possible to get a .exit address from any of these
       * sources. */
      log_warn(LD_BUG,"Address '%s.exit', with impossible source for the "
               ".exit part. Refusing.",
               safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(!automap);

    /* Now, find the character before the .(name) part.
     * (The ".exit" part got stripped off by "parse_extended_hostname").
     *
     * We're going to put the exit name into conn->chosen_exit_name, and
     * look up a node correspondingly. */
    char *s = strrchr(socks->address,'.');
    if (s) {
      /* The address was of the form "(stuff).(name).exit */
      if (s[1] != '\0') {
        /* Looks like a real .exit one. */
        conn->chosen_exit_name = tor_strdup(s+1);
        node = node_get_by_nickname(conn->chosen_exit_name, 0);

        if (exit_source == ADDRMAPSRC_TRACKEXIT) {
          /* We 5 tries before it expires the addressmap */
          conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES;
        }
        *s = 0;
      } else {
        /* Oops, the address was (stuff)..exit.  That's not okay. */
        log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.",
                 safe_str_client(socks->address));
        control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                    escaped(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
    } else {
      /* It looks like they just asked for "foo.exit".  That's a special
       * form that means (foo's address).foo.exit. */

      conn->chosen_exit_name = tor_strdup(socks->address);
      node = node_get_by_nickname(conn->chosen_exit_name, 0);
      if (node) {
        *socks->address = 0;
        node_get_address_string(node, socks->address, sizeof(socks->address));
      }
    }

    /* Now make sure that the chosen exit exists... */
    if (!node) {
      log_warn(LD_APP,
               "Unrecognized relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* ...and make sure that it isn't excluded. */
    if (routerset_contains_node(excludeset, node)) {
      log_warn(LD_APP,
               "Excluded relay in exit address '%s.exit'. Refusing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }
    /* XXXX-1090 Should we also allow foo.bar.exit if ExitNodes is set and
       Bar is not listed in it?  I say yes, but our revised manpage branch
       implies no. */
  }

  /* Now, we handle everything that isn't a .onion address. */
  if (addresstype != ONION_V3_HOSTNAME && addresstype != ONION_V2_HOSTNAME) {
    /* Not a hidden-service request.  It's either a hostname or an IP,
     * possibly with a .exit that we stripped off.  We're going to check
     * if we're allowed to connect/resolve there, and then launch the
     * appropriate request. */

    /* Check for funny characters in the address. */
    if (address_is_invalid_destination(socks->address, 1)) {
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      log_warn(LD_APP,
               "Destination '%s' seems to be an invalid hostname. Failing.",
               safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    /* socks->address is a non-onion hostname or IP address.
     * If we can't do any non-onion requests, refuse the connection.
     * If we have a hostname but can't do DNS, refuse the connection.
     * If we have an IP address, but we can't use that address family,
     * refuse the connection.
     *
     * If we can do DNS requests, and we can use at least one address family,
     * then we have to resolve the address first. Then we'll know if it
     * resolves to a usable address family. */

    /* First, check if all non-onion traffic is disabled */
    if (!conn->entry_cfg.dns_request && !conn->entry_cfg.ipv4_traffic
        && !conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname "
                 "or IP address %s because Port has OnionTrafficOnly set (or "
                 "NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic).",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
    }

    /* Then check if we have a hostname or IP address, and whether DNS or
     * the IP address family are permitted.  Reject if not. */
    tor_addr_t dummy_addr;
    int socks_family = tor_addr_parse(&dummy_addr, socks->address);
    /* family will be -1 for a non-onion hostname that's not an IP */
    if (socks_family == -1) {
      if (!conn->entry_cfg.dns_request) {
        log_warn(LD_APP, "Refusing to connect to hostname %s "
                 "because Port has NoDNSRequest set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET) {
      if (!conn->entry_cfg.ipv4_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
                 "Port has NoIPv4Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else if (socks_family == AF_INET6) {
      if (!conn->entry_cfg.ipv6_traffic) {
        log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
                 "Port has NoIPv6Traffic set.",
                 safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
        return -1;
      }
    } else {
      tor_assert_nonfatal_unreached_once();
    }

    /* See if this is a hostname lookup that we can answer immediately.
     * (For example, an attempt to look up the IP address for an IP address.)
     */
    if (socks->command == SOCKS_COMMAND_RESOLVE) {
      tor_addr_t answer;
      /* Reply to resolves immediately if we can. */
      if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */
        /* remember _what_ is supposed to have been resolved. */
        strlcpy(socks->address, rr.orig_address, sizeof(socks->address));
        connection_ap_handshake_socks_resolved_addr(conn, &answer, -1,
                                                    map_expires);
        connection_mark_unattached_ap(conn,
                                END_STREAM_REASON_DONE |
                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
        return 0;
      }
      tor_assert(!automap);
      rep_hist_note_used_resolve(now); /* help predict this next time */
    } else if (socks->command == SOCKS_COMMAND_CONNECT) {
      /* Now see if this is a connect request that we can reject immediately */

      tor_assert(!automap);
      /* Don't allow connections to port 0. */
      if (socks->port == 0) {
        log_notice(LD_APP,"Application asked to connect to port 0. Refusing.");
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
      /* You can't make connections to internal addresses, by default.
       * Exceptions are begindir requests (where the address is meaningless),
       * or cases where you've hand-configured a particular exit, thereby
       * making the local address meaningful. */
      if (options->ClientRejectInternalAddresses &&
          !conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* If we reach this point then we don't want to allow internal
         * addresses.  Check if we got one. */
        tor_addr_t addr;
        if (tor_addr_hostname_is_local(socks->address) ||
            (tor_addr_parse(&addr, socks->address) >= 0 &&
             tor_addr_is_internal(&addr, 0))) {
          /* If this is an explicit private address with no chosen exit node,
           * then we really don't want to try to connect to it.  That's
           * probably an error. */
          if (conn->is_transparent_ap) {
#define WARN_INTRVL_LOOP 300
            static ratelim_t loop_warn_limit = RATELIM_INIT(WARN_INTRVL_LOOP);
            char *m;
            if ((m = rate_limit_log(&loop_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting request for anonymous connection to private "
                       "address %s on a TransPort or NATDPort.  Possible loop "
                       "in your NAT rules?%s", safe_str_client(socks->address),
                       m);
              tor_free(m);
            }
          } else {
#define WARN_INTRVL_PRIV 300
            static ratelim_t priv_warn_limit = RATELIM_INIT(WARN_INTRVL_PRIV);
            char *m;
            if ((m = rate_limit_log(&priv_warn_limit, approx_time()))) {
              log_warn(LD_NET,
                       "Rejecting SOCKS request for anonymous connection to "
                       "private address %s.%s",
                       safe_str_client(socks->address),m);
              tor_free(m);
            }
          }
          connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR);
          return -1;
        }
      } /* end "if we should check for internal addresses" */

      /* Okay.  We're still doing a CONNECT, and it wasn't a private
       * address.  Here we do special handling for literal IP addresses,
       * to see if we should reject this preemptively, and to set up
       * fields in conn->entry_cfg to tell the exit what AF we want. */
      {
        tor_addr_t addr;
        /* XXX Duplicate call to tor_addr_parse. */
        if (tor_addr_parse(&addr, socks->address) >= 0) {
          /* If we reach this point, it's an IPv4 or an IPv6 address. */
          sa_family_t family = tor_addr_family(&addr);

          if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) ||
              (family == AF_INET6 && ! conn->entry_cfg.ipv6_traffic)) {
            /* You can't do an IPv4 address on a v6-only socks listener,
             * or vice versa. */
            log_warn(LD_NET, "Rejecting SOCKS request for an IP address "
                     "family that this listener does not support.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6 && socks->socks_version == 4) {
            /* You can't make a socks4 request to an IPv6 address. Socks4
             * doesn't support that. */
            log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (socks->socks_version == 4 &&
                     !conn->entry_cfg.ipv4_traffic) {
            /* You can't do any kind of Socks4 request when IPv4 is forbidden.
             *
             * XXX raise this check outside the enclosing block? */
            log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with "
                     "no IPv4 traffic supported.");
            connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
            return -1;
          } else if (family == AF_INET6) {
            /* Tell the exit: we won't accept any ipv4 connection to an IPv6
             * address. */
            conn->entry_cfg.ipv4_traffic = 0;
          } else if (family == AF_INET) {
            /* Tell the exit: we won't accept any ipv6 connection to an IPv4
             * address. */
            conn->entry_cfg.ipv6_traffic = 0;
          }

          /* Next, yet another check: we know it's a direct IP address. Is it
           * the IP address of a known relay and its ORPort, or of a directory
           * authority and its OR or Dir Port? If so, and if a consensus param
           * says to, then exit relays will refuse this request (see ticket
           * 2667 for details). Let's just refuse it locally right now, to
           * save time and network load but also to give the user a more
           * useful log message. */
          if (!network_reentry_is_allowed() &&
              nodelist_reentry_contains(&addr, socks->port)) {
            log_warn(LD_APP, "Not attempting connection to %s:%d because "
                     "the network would reject it. Are you trying to send "
                     "Tor traffic over Tor? This traffic can be harmful to "
                     "the Tor network. If you really need it, try using "
                     "a bridge as a workaround.",
                     safe_str_client(socks->address), socks->port);
            connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
            return -1;
          }
        }
      }

      /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */
      if (socks->socks_version == 4)
        conn->entry_cfg.ipv6_traffic = 0;

      /* Still handling CONNECT. Now, check for exit enclaves.  (Which we
       * don't do on BEGIN_DIR, or when there is a chosen exit.)
       *
       * TODO: Should we remove this?  Exit enclaves are nutty and don't
       * work very well
       */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
        /* see if we can find a suitable enclave exit */
        const node_t *r =
          router_find_exact_exit_enclave(socks->address, socks->port);
        if (r) {
          log_info(LD_APP,
                   "Redirecting address %s to exit at enclave router %s",
                   safe_str_client(socks->address), node_describe(r));
          /* use the hex digest, not nickname, in case there are two
             routers with this nickname */
          conn->chosen_exit_name =
            tor_strdup(hex_str(r->identity, DIGEST_LEN));
          conn->chosen_exit_optional = 1;
        }
      }

      /* Still handling CONNECT: warn or reject if it's using a dangerous
       * port. */
      if (!conn->use_begindir && !conn->chosen_exit_name && !circ)
        if (consider_plaintext_ports(conn, socks->port) < 0)
          return -1;

      /* Remember the port so that we will predict that more requests
         there will happen in the future. */
      if (!conn->use_begindir) {
        /* help predict this next time */
        rep_hist_note_used_port(now, socks->port);
      }
    } else if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
      rep_hist_note_used_resolve(now); /* help predict this next time */
      /* no extra processing needed */
    } else {
      /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */
      tor_fragile_assert();
    }

    /* Okay. At this point we've set chosen_exit_name if needed, rewritten the
     * address, and decided not to reject it for any number of reasons. Now
     * mark the connection as waiting for a circuit, and try to attach it!
     */
    base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;

    /* If we were given a circuit to attach to, try to attach. Otherwise,
     * try to find a good one and attach to that. */
    int rv;
    if (circ) {
      rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
    } else {
      /* We'll try to attach it at the next event loop, or whenever
       * we call connection_ap_attach_pending() */
      connection_ap_mark_as_pending_circuit(conn);
      rv = 0;
    }

    /* If the above function returned 0 then we're waiting for a circuit.
     * if it returned 1, we're attached.  Both are okay.  But if it returned
     * -1, there was an error, so make sure the connection is marked, and
     * return -1. */
    if (rv < 0) {
      if (!base_conn->marked_for_close)
        connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
      return -1;
    }

    return 0;
  } else {
    /* If we get here, it's a request for a .onion address! */

    /* We don't support v2 onions anymore. Log a warning and bail. */
    if (addresstype == ONION_V2_HOSTNAME) {
      static bool log_once = false;
      if (!log_once) {
        log_warn(LD_PROTOCOL, "Tried to connect to a v2 onion address, but "
                 "this version of Tor no longer supports them. Please "
                 "encourage the site operator to upgrade. For more "
                 "information see "
                 "https://blog.torproject.org/v2-deprecation-timeline.");
        log_once = true;
      }
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      /* Send back the 0xF6 extended code indicating a bad hostname. This is
       * mostly so Tor Browser can make a proper UX with regards to v2
       * addresses. */
      conn->socks_request->socks_extended_error_code = SOCKS5_HS_BAD_ADDRESS;
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
      return -1;
    }

    tor_assert(addresstype == ONION_V3_HOSTNAME);
    tor_assert(!automap);
    return connection_ap_handle_onion(conn, socks, circ);
  }

  return 0; /* unreached but keeps the compiler happy */
}

Project: tor
File: connection_edge.c

tell_controller_about_resolved_result(entry_connection_t *conn,
                                      int answer_type,
                                      size_t answer_len,
                                      const char *answer,
                                      int ttl,
                                      time_t expires)
{
  uint64_t stream_id = 0;

  if (BUG(!conn)) {
    return;
  }

  stream_id = ENTRY_TO_CONN(conn)->global_identifier;

  expires = time(NULL) + ttl;
  if (answer_type == RESOLVED_TYPE_IPV4 && answer_len >= 4) {
    char *cp = tor_dup_ip(ntohl(get_uint32(answer)));
    if (cp)
      control_event_address_mapped(conn->socks_request->address,
                                   cp, expires, NULL, 0, stream_id);
    tor_free(cp);
  } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) {
    char *cp = tor_strndup(answer, answer_len);
    control_event_address_mapped(conn->socks_request->address,
                                 cp, expires, NULL, 0, stream_id);
    tor_free(cp);
  } else {
    control_event_address_mapped(conn->socks_request->address,
                                 "<error>", time(NULL)+ttl,
                                 "error=yes", 0, stream_id);
  }
}

Project: tor
File: connection_edge.c

rend_valid_v2_service_id(const char *query)
{
  /** Length of 'y' portion of 'y.onion' URL. */
#define REND_SERVICE_ID_LEN_BASE32 16

  if (strlen(query) != REND_SERVICE_ID_LEN_BASE32)
    return 0;

  if (strspn(query, BASE32_CHARS) != REND_SERVICE_ID_LEN_BASE32)
    return 0;

  return 1;
}

Project: tor
File: connection_edge.c

connection_ap_handle_onion(entry_connection_t *conn,
                           socks_request_t *socks,
                           origin_circuit_t *circ)
{
  int retval;
  time_t now = approx_time();
  connection_t *base_conn = ENTRY_TO_CONN(conn);

  /* If .onion address requests are disabled, refuse the request */
  if (!conn->entry_cfg.onion_traffic) {
    log_warn(LD_APP, "Onion address %s requested from a port with .onion "
             "disabled", safe_str_client(socks->address));
    connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
    return -1;
  }

  /* Check whether it's RESOLVE or RESOLVE_PTR.  We don't handle those
   * for hidden service addresses. */
  if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
    /* if it's a resolve request, fail it right now, rather than
     * building all the circuits and then realizing it won't work. */
    log_warn(LD_APP,
             "Resolve requests to hidden services not allowed. Failing.");
    connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,
                                           0,NULL,-1,TIME_MAX);
    connection_mark_unattached_ap(conn,
                               END_STREAM_REASON_SOCKSPROTOCOL |
                               END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
    return -1;
  }

  /* If we were passed a circuit, then we need to fail.  .onion addresses
   * only work when we launch our own circuits for now. */
  if (circ) {
    log_warn(LD_CONTROL, "Attachstream to a circuit is not "
             "supported for .onion addresses currently. Failing.");
    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
    return -1;
  }

  int descriptor_is_usable = 0;

  /* Create HS conn identifier with HS pubkey */
  hs_ident_edge_conn_t *hs_conn_ident =
    tor_malloc_zero(sizeof(hs_ident_edge_conn_t));

  retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk,
                            NULL, NULL);
  if (retval < 0) {
    log_warn(LD_GENERAL, "failed to parse hs address");
    tor_free(hs_conn_ident);
    return -1;
  }
  ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident;

  /* Check the v3 desc cache */
  const hs_descriptor_t *cached_desc = NULL;
  unsigned int refetch_desc = 0;
  cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk);
  if (cached_desc) {
    descriptor_is_usable =
      hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk,
                                        cached_desc);
    log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.",
             (descriptor_is_usable) ? "usable" : "unusable",
             safe_str_client(socks->address),
             (descriptor_is_usable) ? "Not fetching." : "Refetching.");
  } else {
    /* We couldn't find this descriptor; we should look it up. */
    log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.",
             safe_str_client(socks->address));
    refetch_desc = 1;
  }

  /* Help predict that we'll want to do hidden service circuits in the
   * future. We're not sure if it will need a stable circuit yet, but
   * we know we'll need *something*. */
  rep_hist_note_used_internal(now, 0, 1);

  /* Now we have a descriptor but is it usable or not? If not, refetch.
   * Also, a fetch could have been requested if the onion address was not
   * found in the cache previously. */
  if (refetch_desc || !descriptor_is_usable) {
    edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
    connection_ap_mark_as_non_pending_circuit(conn);
    base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
    tor_assert(edge_conn->hs_ident);
    /* Attempt to fetch the hsv3 descriptor. Check the retval to see how it
     * went and act accordingly. */
    int ret = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
    switch (ret) {
    case HS_CLIENT_FETCH_MISSING_INFO:
      /* Keeping the connection in descriptor wait state is fine because
       * once we get enough dirinfo or a new live consensus, the HS client
       * subsystem is notified and every connection in that state will
       * trigger a fetch for the service key. */
    case HS_CLIENT_FETCH_LAUNCHED:
    case HS_CLIENT_FETCH_PENDING:
    case HS_CLIENT_FETCH_HAVE_DESC:
      return 0;
    case HS_CLIENT_FETCH_ERROR:
    case HS_CLIENT_FETCH_NO_HSDIRS:
    case HS_CLIENT_FETCH_NOT_ALLOWED:
      /* Can't proceed further and better close the SOCKS request. */
      return -1;
    }
  }

  /* We have the descriptor!  So launch a connection to the HS. */
  log_info(LD_REND, "Descriptor is here. Great.");

  base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
  /* We'll try to attach it at the next event loop, or whenever
   * we call connection_ap_attach_pending() */
  connection_ap_mark_as_pending_circuit(conn);
  return 0;
}

Project: tor
File: connection_edge.c

handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
{
  int ret;
  origin_circuit_t *origin_circ;

  assert_circuit_ok(circ);
  tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
  tor_assert(conn);

  log_debug(LD_REND, "Connecting the hidden service rendezvous circuit "
                     "to the service destination.");

  origin_circ = TO_ORIGIN_CIRCUIT(circ);
  conn->base_.address = tor_strdup("(rendezvous)");
  conn->base_.state = EXIT_CONN_STATE_CONNECTING;

  if (origin_circ->hs_ident) {
    /* Setup the identifier to be the one for the circuit service. */
    conn->hs_ident =
      hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk);
    tor_assert(connection_edge_is_rendezvous_stream(conn));
    ret = hs_service_set_conn_addr_port(origin_circ, conn);
  } else {
    /* We should never get here if the circuit's purpose is rendezvous. */
    tor_assert_nonfatal_unreached();
    return -1;
  }
  if (ret < 0) {
    log_info(LD_REND, "Didn't find rendezvous service at %s",
             connection_describe_peer(TO_CONN(conn)));
    /* Send back reason DONE because we want to make hidden service port
     * scanning harder thus instead of returning that the exit policy
     * didn't match, which makes it obvious that the port is closed,
     * return DONE and kill the circuit. That way, a user (malicious or
     * not) needs one circuit per bad port unless it matches the policy of
     * the hidden service. */
    relay_send_end_cell_from_edge(conn->stream_id, circ,
                                  END_STREAM_REASON_DONE,
                                  origin_circ->cpath->prev);
    connection_free_(TO_CONN(conn));

    /* Drop the circuit here since it might be someone deliberately
     * scanning the hidden service ports. Note that this mitigates port
     * scanning by adding more work on the attacker side to successfully
     * scan but does not fully solve it. */
    if (ret < -1) {
      return END_CIRC_AT_ORIGIN;
    } else {
      return 0;
    }
  }

  /* Link the circuit and the connection crypt path. */
  conn->cpath_layer = origin_circ->cpath->prev;

  /* If this is the first stream on this circuit, tell circpad */
  if (!origin_circ->p_streams)
    circpad_machine_event_circ_has_streams(origin_circ);

  /* Add it into the linked list of p_streams on this circuit */
  conn->next_stream = origin_circ->p_streams;
  origin_circ->p_streams = conn;
  conn->on_circuit = circ;
  assert_circuit_ok(circ);

  hs_inc_rdv_stream_counter(origin_circ);

  /* If it's an onion service connection, we might want to include the proxy
   * protocol header: */
  if (conn->hs_ident) {
    hs_circuit_id_protocol_t circuit_id_protocol =
      hs_service_exports_circuit_id(&conn->hs_ident->identity_pk);
    export_hs_client_circuit_id(conn, circuit_id_protocol);
  }

  /* Connect tor to the hidden service destination. */
  connection_exit_connect(conn);

  /* For path bias: This circuit was used successfully */
  pathbias_mark_use_success(origin_circ);
  return 0;
}