Преглед изворни кода

Merge branch 'feature-17178-v7-squashed-v2'

Nick Mathewson пре 7 година
родитељ
комит
9f0cb5af15

+ 6 - 0
changes/bug19677

@@ -0,0 +1,6 @@
+  o Minor bug fixes (option parsing):
+    - Count unix sockets when counting client listeners (SOCKS, Trans,
+      NATD, and DNS). This has no user-visible behaviour changes: these
+      options are set once, and never read.
+      Required for correct behaviour in ticket #17178.
+      Fixes bug #19677, patch by teor.

+ 30 - 0
changes/feature17178

@@ -0,0 +1,30 @@
+  o Major features (onion services):
+    - Add experimental HiddenServiceSingleHopMode and
+      HiddenServiceNonAnonymousMode options. When both are set to 1, every
+      hidden service on a tor instance becomes a non-anonymous Single Onion
+      Service. Single Onions make one-hop (direct) connections to their
+      introduction and renzedvous points. One-hop circuits make Single Onion
+      servers easily locatable, but clients remain location-anonymous.
+      This is compatible with the existing hidden service implementation, and
+      works on the current tor network without any changes to older relays or
+      clients.
+      Implements proposal #260, completes ticket #17178. Patch by teor & asn.
+  o Minor features (Tor2web):
+    - Make Tor2web clients respect ReachableAddresses.
+      This feature was inadvertently enabled in 0.2.8.6, then removed
+      by bugfix #19973 on 0.2.8.7.
+      Implements feature #20034. Patch by teor.
+  o Minor bug fixes (Tor2web):
+    - Prevent Tor2web clients running hidden services, these services are
+      not anonymous due to the one-hop client paths.
+      Fixes bug #19678. Patch by teor.
+  o Minor bug fixes (circuits):
+    - Use CircuitBuildTimeout whenever LearnCircuitBuildTimeout is disabled.
+      Fixes bug #19678 in commit 5b0b51ca3 in 0.2.4.12-alpha. Patch by teor.
+  o Minor bug fixes (options):
+    - Stop changing the configured value of UseEntryGuards on authorities
+      and Tor2web clients.
+      Fixes bug #20074 in commits 51fc6799 in tor-0.1.1.16-rc and
+      acda1735 in tor-0.2.4.3-alpha. Patch by teor.
+    - Check the consistency of UseEntryGuards and EntryNodes more reliably.
+      Fixes bug #20074 in commit 686aaa5c in tor-0.2.4.12-alpha. Patch by teor.

+ 37 - 2
doc/tor.1.txt

@@ -1199,7 +1199,9 @@ The following options are useful only for clients (that is, if
     If this option is set to 1, we pick a few long-term entry servers, and try
     If this option is set to 1, we pick a few long-term entry servers, and try
     to stick with them. This is desirable because constantly changing servers
     to stick with them. This is desirable because constantly changing servers
     increases the odds that an adversary who owns some servers will observe a
     increases the odds that an adversary who owns some servers will observe a
-    fraction of your paths. (Default: 1)
+    fraction of your paths. Entry Guards can not be used by Directory
+    Authorities, Single Onion Services, and Tor2web clients. In these cases,
+    the this option is ignored. (Default: 1)
 
 
 [[UseEntryGuardsAsDirGuards]] **UseEntryGuardsAsDirGuards** **0**|**1**::
 [[UseEntryGuardsAsDirGuards]] **UseEntryGuardsAsDirGuards** **0**|**1**::
     If this option is set to 1, and UseEntryGuards is also set to 1,
     If this option is set to 1, and UseEntryGuards is also set to 1,
@@ -1440,7 +1442,9 @@ The following options are useful only for clients (that is, if
     non-hidden-service hostnames through Tor. It **must only** be used when
     non-hidden-service hostnames through Tor. It **must only** be used when
     running a tor2web Hidden Service web proxy.
     running a tor2web Hidden Service web proxy.
     To enable this option the compile time flag --enable-tor2web-mode must be
     To enable this option the compile time flag --enable-tor2web-mode must be
-    specified. (Default: 0)
+    specified. Since Tor2webMode is non-anonymous, you can not run an
+    anonymous Hidden Service on a tor version compiled with Tor2webMode.
+    (Default: 0)
 
 
 [[Tor2webRendezvousPoints]] **Tor2webRendezvousPoints** __node__,__node__,__...__::
 [[Tor2webRendezvousPoints]] **Tor2webRendezvousPoints** __node__,__node__,__...__::
     A list of identity fingerprints, nicknames, country codes and
     A list of identity fingerprints, nicknames, country codes and
@@ -2392,6 +2396,37 @@ The following options are used to configure a hidden service.
     Number of introduction points the hidden service will have. You can't
     Number of introduction points the hidden service will have. You can't
     have more than 10. (Default: 3)
     have more than 10. (Default: 3)
 
 
+[[HiddenServiceSingleHopMode]] **HiddenServiceSingleHopMode** **0**|**1**::
+    **Experimental - Non Anonymous** Hidden Services on a tor instance in
+    HiddenServiceSingleHopMode make one-hop (direct) circuits between the onion
+    service server, and the introduction and rendezvous points. (Onion service
+    descriptors are still posted using 3-hop paths, to avoid onion service
+    directories blocking the service.)
+    This option makes every hidden service instance hosted by a tor instance a
+    Single Onion Service. One-hop circuits make Single Onion servers easily
+    locatable, but clients remain location-anonymous. However, the fact that a
+    client is accessing a Single Onion rather than a Hidden Service may be
+    statistically distinguishable.
+
+    **WARNING:** Once a hidden service directory has been used by a tor
+    instance in HiddenServiceSingleHopMode, it can **NEVER** be used again for
+    a hidden service. It is best practice to create a new hidden service
+    directory, key, and address for each new Single Onion Service and Hidden
+    Service. It is not possible to run Single Onion Services and Hidden
+    Services from the same tor instance: they should be run on different
+    servers with different IP addresses.
+
+    HiddenServiceSingleHopMode requires HiddenServiceNonAnonymousMode to be set
+    to 1. Since a Single Onion is non-anonymous, you can not to run an
+    anonymous SOCKSPort on the same tor instance as a Single Onion service.
+    (Default: 0)
+
+[[HiddenServiceNonAnonymousMode]] **HiddenServiceNonAnonymousMode** **0**|**1**::
+    Makes hidden services non-anonymous on this tor instance. Allows the
+    non-anonymous HiddenServiceSingleHopMode. Enables direct connections in the
+    server-side hidden service protocol.
+    (Default: 0)
+
 TESTING NETWORK OPTIONS
 TESTING NETWORK OPTIONS
 -----------------------
 -----------------------
 
 

+ 27 - 5
src/or/circuitbuild.c

@@ -28,6 +28,7 @@
 #include "connection_edge.h"
 #include "connection_edge.h"
 #include "connection_or.h"
 #include "connection_or.h"
 #include "control.h"
 #include "control.h"
+#include "crypto.h"
 #include "directory.h"
 #include "directory.h"
 #include "entrynodes.h"
 #include "entrynodes.h"
 #include "main.h"
 #include "main.h"
@@ -38,14 +39,14 @@
 #include "onion_tap.h"
 #include "onion_tap.h"
 #include "onion_fast.h"
 #include "onion_fast.h"
 #include "policies.h"
 #include "policies.h"
-#include "transports.h"
 #include "relay.h"
 #include "relay.h"
+#include "rendcommon.h"
 #include "rephist.h"
 #include "rephist.h"
 #include "router.h"
 #include "router.h"
 #include "routerlist.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "routerparse.h"
 #include "routerset.h"
 #include "routerset.h"
-#include "crypto.h"
+#include "transports.h"
 
 
 static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
 static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
                                                uint16_t port,
                                                uint16_t port,
@@ -1859,13 +1860,32 @@ pick_rendezvous_node(router_crn_flags_t flags)
     flags |= CRN_ALLOW_INVALID;
     flags |= CRN_ALLOW_INVALID;
 
 
 #ifdef ENABLE_TOR2WEB_MODE
 #ifdef ENABLE_TOR2WEB_MODE
+  /* We want to connect directly to the node if we can */
+  router_crn_flags_t direct_flags = flags;
+  direct_flags |= CRN_PREF_ADDR;
+  direct_flags |= CRN_DIRECT_CONN;
+
   /* The user wants us to pick specific RPs. */
   /* The user wants us to pick specific RPs. */
   if (options->Tor2webRendezvousPoints) {
   if (options->Tor2webRendezvousPoints) {
-    const node_t *tor2web_rp = pick_tor2web_rendezvous_node(flags, options);
+    const node_t *tor2web_rp = pick_tor2web_rendezvous_node(direct_flags,
+                                                            options);
     if (tor2web_rp) {
     if (tor2web_rp) {
       return tor2web_rp;
       return tor2web_rp;
     }
     }
-    /* Else, if no tor2web RP was found, fall back to choosing a random node */
+  }
+
+  /* Else, if no direct, preferred tor2web RP was found, fall back to choosing
+   * a random direct node */
+  const node_t *node = router_choose_random_node(NULL, options->ExcludeNodes,
+                                                 direct_flags);
+  /* Return the direct node (if found), or log a message and fall back to an
+   * indirect connection. */
+  if (node) {
+    return node;
+  } else {
+    log_info(LD_REND,
+             "Unable to find a random rendezvous point that is reachable via "
+             "a direct connection, falling back to a 3-hop path.");
   }
   }
 #endif
 #endif
 
 
@@ -2000,7 +2020,9 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei)
   cpath_build_state_t *state = circ->build_state;
   cpath_build_state_t *state = circ->build_state;
 
 
   if (state->onehop_tunnel) {
   if (state->onehop_tunnel) {
-    log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel.");
+    log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel%s.",
+              (rend_allow_non_anonymous_connection(get_options()) ?
+               ", or intro or rendezvous connection" : ""));
     state->desired_path_len = 1;
     state->desired_path_len = 1;
   } else {
   } else {
     int r = new_route_len(circ->base_.purpose, exit_ei, nodelist_get_list());
     int r = new_route_len(circ->base_.purpose, exit_ei, nodelist_get_list());

+ 25 - 5
src/or/circuitstats.c

@@ -21,6 +21,8 @@
 #include "control.h"
 #include "control.h"
 #include "main.h"
 #include "main.h"
 #include "networkstatus.h"
 #include "networkstatus.h"
+#include "rendclient.h"
+#include "rendservice.h"
 #include "statefile.h"
 #include "statefile.h"
 
 
 #undef log
 #undef log
@@ -81,12 +83,14 @@ get_circuit_build_timeout_ms(void)
 
 
 /**
 /**
  * This function decides if CBT learning should be disabled. It returns
  * This function decides if CBT learning should be disabled. It returns
- * true if one or more of the following four conditions are met:
+ * true if one or more of the following conditions are met:
  *
  *
  *  1. If the cbtdisabled consensus parameter is set.
  *  1. If the cbtdisabled consensus parameter is set.
  *  2. If the torrc option LearnCircuitBuildTimeout is false.
  *  2. If the torrc option LearnCircuitBuildTimeout is false.
  *  3. If we are a directory authority
  *  3. If we are a directory authority
  *  4. If we fail to write circuit build time history to our state file.
  *  4. If we fail to write circuit build time history to our state file.
+ *  5. If we are compiled or configured in Tor2web mode
+ *  6. If we are configured in Single Onion mode
  */
  */
 int
 int
 circuit_build_times_disabled(void)
 circuit_build_times_disabled(void)
@@ -94,14 +98,30 @@ circuit_build_times_disabled(void)
   if (unit_tests) {
   if (unit_tests) {
     return 0;
     return 0;
   } else {
   } else {
+    const or_options_t *options = get_options();
     int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
     int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
                                                      0, 0, 1);
                                                      0, 0, 1);
-    int config_disabled = !get_options()->LearnCircuitBuildTimeout;
-    int dirauth_disabled = get_options()->AuthoritativeDir;
+    int config_disabled = !options->LearnCircuitBuildTimeout;
+    int dirauth_disabled = options->AuthoritativeDir;
     int state_disabled = did_last_state_file_write_fail() ? 1 : 0;
     int state_disabled = did_last_state_file_write_fail() ? 1 : 0;
+    /* LearnCircuitBuildTimeout and Tor2web/Single Onion Services are
+     * incompatible in two ways:
+     *
+     * - LearnCircuitBuildTimeout results in a low CBT, which
+     *   Single Onion use of one-hop intro and rendezvous circuits lowers
+     *   much further, producing *far* too many timeouts.
+     *
+     * - The adaptive CBT code does not update its timeout estimate
+     *   using build times for single-hop circuits.
+     *
+     * If we fix both of these issues someday, we should test
+     * these modes with LearnCircuitBuildTimeout on again. */
+    int tor2web_disabled = rend_client_allow_non_anonymous_connection(options);
+    int single_onion_disabled = rend_service_allow_non_anonymous_connection(
+                                                                      options);
 
 
     if (consensus_disabled || config_disabled || dirauth_disabled ||
     if (consensus_disabled || config_disabled || dirauth_disabled ||
-           state_disabled) {
+        state_disabled || tor2web_disabled || single_onion_disabled) {
 #if 0
 #if 0
       log_debug(LD_CIRC,
       log_debug(LD_CIRC,
                "CircuitBuildTime learning is disabled. "
                "CircuitBuildTime learning is disabled. "
@@ -469,7 +489,7 @@ circuit_build_times_get_initial_timeout(void)
    */
    */
   if (!unit_tests && get_options()->CircuitBuildTimeout) {
   if (!unit_tests && get_options()->CircuitBuildTimeout) {
     timeout = get_options()->CircuitBuildTimeout*1000;
     timeout = get_options()->CircuitBuildTimeout*1000;
-    if (get_options()->LearnCircuitBuildTimeout &&
+    if (!circuit_build_times_disabled() &&
         timeout < circuit_build_times_min_timeout()) {
         timeout < circuit_build_times_min_timeout()) {
       log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
       log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
                circuit_build_times_min_timeout()/1000);
                circuit_build_times_min_timeout()/1000);

+ 175 - 46
src/or/config.c

@@ -18,6 +18,7 @@
 #include "circuitlist.h"
 #include "circuitlist.h"
 #include "circuitmux.h"
 #include "circuitmux.h"
 #include "circuitmux_ewma.h"
 #include "circuitmux_ewma.h"
+#include "circuitstats.h"
 #include "config.h"
 #include "config.h"
 #include "connection.h"
 #include "connection.h"
 #include "connection_edge.h"
 #include "connection_edge.h"
@@ -297,6 +298,8 @@ static config_var_t option_vars_[] = {
   V(HidServAuth,                 LINELIST, NULL),
   V(HidServAuth,                 LINELIST, NULL),
   V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
   V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
   V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
   V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
+  V(HiddenServiceSingleHopMode,  BOOL,     "0"),
+  V(HiddenServiceNonAnonymousMode,BOOL,    "0"),
   V(HTTPProxy,                   STRING,   NULL),
   V(HTTPProxy,                   STRING,   NULL),
   V(HTTPProxyAuthenticator,      STRING,   NULL),
   V(HTTPProxyAuthenticator,      STRING,   NULL),
   V(HTTPSProxy,                  STRING,   NULL),
   V(HTTPSProxy,                  STRING,   NULL),
@@ -434,7 +437,7 @@ static config_var_t option_vars_[] = {
   OBSOLETE("TunnelDirConns"),
   OBSOLETE("TunnelDirConns"),
   V(UpdateBridgesFromAuthority,  BOOL,     "0"),
   V(UpdateBridgesFromAuthority,  BOOL,     "0"),
   V(UseBridges,                  BOOL,     "0"),
   V(UseBridges,                  BOOL,     "0"),
-  V(UseEntryGuards,              BOOL,     "1"),
+  VAR("UseEntryGuards",          BOOL,     UseEntryGuards_option, "1"),
   V(UseEntryGuardsAsDirGuards,   BOOL,     "1"),
   V(UseEntryGuardsAsDirGuards,   BOOL,     "1"),
   V(UseGuardFraction,            AUTOBOOL, "auto"),
   V(UseGuardFraction,            AUTOBOOL, "auto"),
   V(UseMicrodescriptors,         AUTOBOOL, "auto"),
   V(UseMicrodescriptors,         AUTOBOOL, "auto"),
@@ -1558,10 +1561,10 @@ options_act(const or_options_t *old_options)
   if (consider_adding_dir_servers(options, old_options) < 0)
   if (consider_adding_dir_servers(options, old_options) < 0)
     return -1;
     return -1;
 
 
-#ifdef NON_ANONYMOUS_MODE_ENABLED
-  log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a "
-      "non-anonymous mode. It will provide NO ANONYMITY.");
-#endif
+  if (rend_non_anonymous_mode_enabled(options)) {
+    log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run "
+             "in a non-anonymous mode. It will provide NO ANONYMITY.");
+  }
 
 
 #ifdef ENABLE_TOR2WEB_MODE
 #ifdef ENABLE_TOR2WEB_MODE
 /* LCOV_EXCL_START */
 /* LCOV_EXCL_START */
@@ -1723,8 +1726,27 @@ options_act(const or_options_t *old_options)
 
 
   monitor_owning_controller_process(options->OwningControllerProcess);
   monitor_owning_controller_process(options->OwningControllerProcess);
 
 
+  /* We must create new keys after we poison the directories, because our
+   * poisoning code checks for existing keys, and refuses to modify their
+   * directories. */
+
+  /* If we use non-anonymous single onion services, make sure we poison any
+     new hidden service directories, so that we never accidentally launch the
+     non-anonymous hidden services thinking they are anonymous. */
+  if (running_tor && rend_service_non_anonymous_mode_enabled(options)) {
+    if (options->RendConfigLines && !num_rend_services()) {
+      log_warn(LD_BUG,"Error: hidden services configured, but not parsed.");
+      return -1;
+    }
+    if (rend_service_poison_new_single_onion_dirs(NULL) < 0) {
+      log_warn(LD_GENERAL,"Failed to mark new hidden services as non-anonymous"
+               ".");
+      return -1;
+    }
+  }
+
   /* reload keys as needed for rendezvous services. */
   /* reload keys as needed for rendezvous services. */
-  if (rend_service_load_all_keys()<0) {
+  if (rend_service_load_all_keys(NULL)<0) {
     log_warn(LD_GENERAL,"Error loading rendezvous service keys");
     log_warn(LD_GENERAL,"Error loading rendezvous service keys");
     return -1;
     return -1;
   }
   }
@@ -2796,6 +2818,86 @@ warn_about_relative_paths(or_options_t *options)
   }
   }
 }
 }
 
 
+/* Validate options related to single onion services.
+ * Modifies some options that are incompatible with single onion services.
+ * On failure returns -1, and sets *msg to an error string.
+ * Returns 0 on success. */
+STATIC int
+options_validate_single_onion(or_options_t *options, char **msg)
+{
+  /* The two single onion service options must have matching values. */
+  if (options->HiddenServiceSingleHopMode &&
+      !options->HiddenServiceNonAnonymousMode) {
+    REJECT("HiddenServiceSingleHopMode does not provide any server anonymity. "
+           "It must be used with HiddenServiceNonAnonymousMode set to 1.");
+  }
+  if (options->HiddenServiceNonAnonymousMode &&
+      !options->HiddenServiceSingleHopMode) {
+    REJECT("HiddenServiceNonAnonymousMode does not provide any server "
+           "anonymity. It must be used with HiddenServiceSingleHopMode set to "
+           "1.");
+  }
+
+  /* Now that we've checked that the two options are consistent, we can safely
+   * call the rend_service_* functions that abstract these options. */
+
+  /* If you run an anonymous client with an active Single Onion service, the
+   * client loses anonymity. */
+  const int client_port_set = (options->SocksPort_set ||
+                               options->TransPort_set ||
+                               options->NATDPort_set ||
+                               options->DNSPort_set);
+  if (rend_service_non_anonymous_mode_enabled(options) && client_port_set &&
+      !options->Tor2webMode) {
+    REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as "
+           "an anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or "
+           "HiddenServiceNonAnonymousMode to 0, or use the non-anonymous "
+           "Tor2webMode.");
+  }
+
+  /* If you run a hidden service in non-anonymous mode, the hidden service
+   * loses anonymity, even if SOCKSPort / Tor2web mode isn't used. */
+  if (!rend_service_non_anonymous_mode_enabled(options) &&
+      options->RendConfigLines && options->Tor2webMode) {
+    REJECT("Non-anonymous (Tor2web) mode is incompatible with using Tor as a "
+           "hidden service. Please remove all HiddenServiceDir lines, or use "
+           "a version of tor compiled without --enable-tor2web-mode, or use "
+           " HiddenServiceNonAnonymousMode.");
+  }
+
+  if (rend_service_allow_non_anonymous_connection(options)
+      && options->UseEntryGuards) {
+    /* Single Onion services only use entry guards when uploading descriptors,
+     * all other connections are one-hop. Further, Single Onions causes the
+     * hidden service code to do things which break the path bias
+     * detector, and it's far easier to turn off entry guards (and
+     * thus the path bias detector with it) than to figure out how to
+     * make path bias compatible with single onions.
+     */
+    log_notice(LD_CONFIG,
+               "HiddenServiceSingleHopMode is enabled; disabling "
+               "UseEntryGuards.");
+    options->UseEntryGuards = 0;
+  }
+
+  /* Check if existing hidden service keys were created in a different
+   * single onion service mode, and refuse to launch if they
+   * have. We'll poison new keys in options_act() just before we create them.
+   */
+  if (rend_service_list_verify_single_onion_poison(NULL, options) < 0) {
+    log_warn(LD_GENERAL, "We are configured with "
+             "HiddenServiceNonAnonymousMode %d, but one or more hidden "
+             "service keys were created in %s mode. This is not allowed.",
+             rend_service_non_anonymous_mode_enabled(options) ? 1 : 0,
+             rend_service_non_anonymous_mode_enabled(options) ?
+             "an anonymous" : "a non-anonymous"
+             );
+    return -1;
+  }
+
+  return 0;
+}
+
 /** Return 0 if every setting in <b>options</b> is reasonable, is a
 /** Return 0 if every setting in <b>options</b> is reasonable, is a
  * permissible transition from <b>old_options</b>, and none of the
  * permissible transition from <b>old_options</b>, and none of the
  * testing-only settings differ from <b>default_options</b> unless in
  * testing-only settings differ from <b>default_options</b> unless in
@@ -2822,6 +2924,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
   tor_assert(msg);
   tor_assert(msg);
   *msg = NULL;
   *msg = NULL;
 
 
+  /* Set UseEntryGuards from the configured value, before we check it below.
+   * We change UseEntryGuards whenn it's incompatible with other options,
+   * but leave UseEntryGuards_option with the original value.
+   * Always use the value of UseEntryGuards, not UseEntryGuards_option. */
+  options->UseEntryGuards = options->UseEntryGuards_option;
+
   warn_about_relative_paths(options);
   warn_about_relative_paths(options);
 
 
   if (server_mode(options) &&
   if (server_mode(options) &&
@@ -3197,10 +3305,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->UseBridges && options->EntryNodes)
   if (options->UseBridges && options->EntryNodes)
     REJECT("You cannot set both UseBridges and EntryNodes.");
     REJECT("You cannot set both UseBridges and EntryNodes.");
 
 
-  if (options->EntryNodes && !options->UseEntryGuards) {
-    REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
-  }
-
   options->MaxMemInQueues =
   options->MaxMemInQueues =
     compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
     compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
                                    server_mode(options));
                                    server_mode(options));
@@ -3291,25 +3395,11 @@ options_validate(or_options_t *old_options, or_options_t *options,
     options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE;
     options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE;
   }
   }
 
 
-#ifdef ENABLE_TOR2WEB_MODE
-  if (options->Tor2webMode && options->LearnCircuitBuildTimeout) {
-    /* LearnCircuitBuildTimeout and Tor2webMode are incompatible in
-     * two ways:
-     *
-     * - LearnCircuitBuildTimeout results in a low CBT, which
-     *   Tor2webMode's use of one-hop rendezvous circuits lowers
-     *   much further, producing *far* too many timeouts.
-     *
-     * - The adaptive CBT code does not update its timeout estimate
-     *   using build times for single-hop circuits.
-     *
-     * If we fix both of these issues someday, we should test
-     * Tor2webMode with LearnCircuitBuildTimeout on again. */
-    log_notice(LD_CONFIG,"Tor2webMode is enabled; turning "
-               "LearnCircuitBuildTimeout off.");
-    options->LearnCircuitBuildTimeout = 0;
-  }
+  /* Check the Single Onion Service options */
+  if (options_validate_single_onion(options, msg) < 0)
+    return -1;
 
 
+#ifdef ENABLE_TOR2WEB_MODE
   if (options->Tor2webMode && options->UseEntryGuards) {
   if (options->Tor2webMode && options->UseEntryGuards) {
     /* tor2web mode clients do not (and should not) use entry guards
     /* tor2web mode clients do not (and should not) use entry guards
      * in any meaningful way.  Further, tor2web mode causes the hidden
      * in any meaningful way.  Further, tor2web mode causes the hidden
@@ -3329,8 +3419,13 @@ options_validate(or_options_t *old_options, or_options_t *options,
     REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode.");
     REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode.");
   }
   }
 
 
+  if (options->EntryNodes && !options->UseEntryGuards) {
+    REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
+  }
+
   if (!(options->UseEntryGuards) &&
   if (!(options->UseEntryGuards) &&
-      (options->RendConfigLines != NULL)) {
+      (options->RendConfigLines != NULL) &&
+      !rend_service_allow_non_anonymous_connection(options)) {
     log_warn(LD_CONFIG,
     log_warn(LD_CONFIG,
              "UseEntryGuards is disabled, but you have configured one or more "
              "UseEntryGuards is disabled, but you have configured one or more "
              "hidden services on this Tor instance.  Your hidden services "
              "hidden services on this Tor instance.  Your hidden services "
@@ -3353,6 +3448,17 @@ options_validate(or_options_t *old_options, or_options_t *options,
     return -1;
     return -1;
   }
   }
 
 
+  /* Single Onion Services: non-anonymous hidden services */
+  if (rend_service_non_anonymous_mode_enabled(options)) {
+    log_warn(LD_CONFIG,
+             "HiddenServiceNonAnonymousMode is set. Every hidden service on "
+             "this tor instance is NON-ANONYMOUS. If "
+             "the HiddenServiceNonAnonymousMode option is changed, Tor will "
+             "refuse to launch hidden services from the same directories, to "
+             "protect your anonymity against config errors. This setting is "
+             "for experimental use only.");
+  }
+
   if (!options->LearnCircuitBuildTimeout && options->CircuitBuildTimeout &&
   if (!options->LearnCircuitBuildTimeout && options->CircuitBuildTimeout &&
       options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) {
       options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) {
     log_warn(LD_CONFIG,
     log_warn(LD_CONFIG,
@@ -3364,8 +3470,15 @@ options_validate(or_options_t *old_options, or_options_t *options,
         RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT );
         RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT );
   } else if (!options->LearnCircuitBuildTimeout &&
   } else if (!options->LearnCircuitBuildTimeout &&
              !options->CircuitBuildTimeout) {
              !options->CircuitBuildTimeout) {
-    log_notice(LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but didn't "
-               "a CircuitBuildTimeout. I'll pick a plausible default.");
+    int severity = LOG_NOTICE;
+    /* Be a little quieter if we've deliberately disabled
+     * LearnCircuitBuildTimeout. */
+    if (circuit_build_times_disabled()) {
+      severity = LOG_INFO;
+    }
+    log_fn(severity, LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but "
+           "didn't specify a CircuitBuildTimeout. I'll pick a plausible "
+           "default.");
   }
   }
 
 
   if (options->PathBiasNoticeRate > 1.0) {
   if (options->PathBiasNoticeRate > 1.0) {
@@ -4295,6 +4408,19 @@ options_transition_allowed(const or_options_t *old,
     return -1;
     return -1;
   }
   }
 
 
+  if (old->HiddenServiceSingleHopMode != new_val->HiddenServiceSingleHopMode) {
+    *msg = tor_strdup("While Tor is running, changing "
+                      "HiddenServiceSingleHopMode is not allowed.");
+    return -1;
+  }
+
+  if (old->HiddenServiceNonAnonymousMode !=
+      new_val->HiddenServiceNonAnonymousMode) {
+    *msg = tor_strdup("While Tor is running, changing "
+                      "HiddenServiceNonAnonymousMode is not allowed.");
+    return -1;
+  }
+
   if (old->DisableDebuggerAttachment &&
   if (old->DisableDebuggerAttachment &&
       !new_val->DisableDebuggerAttachment) {
       !new_val->DisableDebuggerAttachment) {
     *msg = tor_strdup("While Tor is running, disabling "
     *msg = tor_strdup("While Tor is running, disabling "
@@ -6777,14 +6903,17 @@ parse_port_config(smartlist_t *out,
 }
 }
 
 
 /** Return the number of ports which are actually going to listen with type
 /** Return the number of ports which are actually going to listen with type
- * <b>listenertype</b>.  Do not count no_listen ports.  Do not count unix
- * sockets. */
+ * <b>listenertype</b>.  Do not count no_listen ports.  Only count unix
+ * sockets if count_sockets is true. */
 static int
 static int
-count_real_listeners(const smartlist_t *ports, int listenertype)
+count_real_listeners(const smartlist_t *ports, int listenertype,
+                     int count_sockets)
 {
 {
   int n = 0;
   int n = 0;
   SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) {
   SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) {
-    if (port->server_cfg.no_listen || port->is_unix_addr)
+    if (port->server_cfg.no_listen)
+      continue;
+    if (!count_sockets && port->is_unix_addr)
       continue;
       continue;
     if (port->type != listenertype)
     if (port->type != listenertype)
       continue;
       continue;
@@ -6793,9 +6922,8 @@ count_real_listeners(const smartlist_t *ports, int listenertype)
   return n;
   return n;
 }
 }
 
 
-/** Parse all client port types (Socks, DNS, Trans, NATD) from
- * <b>options</b>. On success, set *<b>n_ports_out</b> to the number
- * of ports that are listed, update the *Port_set values in
+/** Parse all ports from <b>options</b>. On success, set *<b>n_ports_out</b>
+ * to the number of ports that are listed, update the *Port_set values in
  * <b>options</b>, and return 0.  On failure, set *<b>msg</b> to a
  * <b>options</b>, and return 0.  On failure, set *<b>msg</b> to a
  * description of the problem and return -1.
  * description of the problem and return -1.
  *
  *
@@ -6921,21 +7049,22 @@ parse_ports(or_options_t *options, int validate_only,
   /* Update the *Port_set options.  The !! here is to force a boolean out of
   /* Update the *Port_set options.  The !! here is to force a boolean out of
      an integer. */
      an integer. */
   options->ORPort_set =
   options->ORPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER, 0);
   options->SocksPort_set =
   options->SocksPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER, 1);
   options->TransPort_set =
   options->TransPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1);
   options->NATDPort_set =
   options->NATDPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1);
+  /* Use options->ControlSocket to test if a control socket is set */
   options->ControlPort_set =
   options->ControlPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0);
   options->DirPort_set =
   options->DirPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER, 0);
   options->DNSPort_set =
   options->DNSPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER, 1);
   options->ExtORPort_set =
   options->ExtORPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER, 0);
 
 
   if (world_writable_control_socket) {
   if (world_writable_control_socket) {
     SMARTLIST_FOREACH(ports, port_cfg_t *, p,
     SMARTLIST_FOREACH(ports, port_cfg_t *, p,

+ 2 - 0
src/or/config.h

@@ -166,6 +166,8 @@ extern struct config_format_t options_format;
 STATIC port_cfg_t *port_cfg_new(size_t namelen);
 STATIC port_cfg_t *port_cfg_new(size_t namelen);
 STATIC void port_cfg_free(port_cfg_t *port);
 STATIC void port_cfg_free(port_cfg_t *port);
 STATIC void or_options_free(or_options_t *options);
 STATIC void or_options_free(or_options_t *options);
+STATIC int options_validate_single_onion(or_options_t *options,
+                                         char **msg);
 STATIC int options_validate(or_options_t *old_options,
 STATIC int options_validate(or_options_t *old_options,
                             or_options_t *options,
                             or_options_t *options,
                             or_options_t *default_options,
                             or_options_t *default_options,

+ 26 - 3
src/or/connection_edge.c

@@ -27,6 +27,7 @@
 #include "control.h"
 #include "control.h"
 #include "dns.h"
 #include "dns.h"
 #include "dnsserv.h"
 #include "dnsserv.h"
+#include "directory.h"
 #include "dirserv.h"
 #include "dirserv.h"
 #include "hibernate.h"
 #include "hibernate.h"
 #include "main.h"
 #include "main.h"
@@ -2326,6 +2327,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
   char payload[CELL_PAYLOAD_SIZE];
   char payload[CELL_PAYLOAD_SIZE];
   int payload_len;
   int payload_len;
   int begin_type;
   int begin_type;
+  const or_options_t *options = get_options();
   origin_circuit_t *circ;
   origin_circuit_t *circ;
   edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn);
   edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn);
   connection_t *base_conn = TO_CONN(edge_conn);
   connection_t *base_conn = TO_CONN(edge_conn);
@@ -2369,10 +2371,31 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
 
 
   begin_type = ap_conn->use_begindir ?
   begin_type = ap_conn->use_begindir ?
                  RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN;
                  RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN;
+
+  /* Check that circuits are anonymised, based on their type. */
   if (begin_type == RELAY_COMMAND_BEGIN) {
   if (begin_type == RELAY_COMMAND_BEGIN) {
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-    tor_assert(circ->build_state->onehop_tunnel == 0);
-#endif
+    /* This connection is a standard OR connection.
+     * Make sure its path length is anonymous, or that we're in a
+     * non-anonymous mode. */
+    assert_circ_anonymity_ok(circ, options);
+  } else if (begin_type == RELAY_COMMAND_BEGIN_DIR) {
+    /* This connection is a begindir directory connection.
+     * Look at the linked directory connection to access the directory purpose.
+     * (This must be non-NULL, because we're doing begindir.) */
+    tor_assert(base_conn->linked);
+    connection_t *linked_dir_conn_base = base_conn->linked_conn;
+    tor_assert(linked_dir_conn_base);
+    /* Sensitive directory connections must have an anonymous path length.
+     * Otherwise, directory connections are typically one-hop.
+     * This matches the earlier check for directory connection path anonymity
+     * in directory_initiate_command_rend(). */
+    if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) {
+      assert_circ_anonymity_ok(circ, options);
+    }
+  } else {
+    /* This code was written for the two connection types BEGIN and BEGIN_DIR
+     */
+    tor_assert_unreached();
   }
   }
 
 
   if (connection_edge_send_command(edge_conn, begin_type,
   if (connection_edge_send_command(edge_conn, begin_type,

+ 22 - 0
src/or/control.c

@@ -4249,6 +4249,8 @@ handle_control_add_onion(control_connection_t *conn,
   int max_streams = 0;
   int max_streams = 0;
   int max_streams_close_circuit = 0;
   int max_streams_close_circuit = 0;
   rend_auth_type_t auth_type = REND_NO_AUTH;
   rend_auth_type_t auth_type = REND_NO_AUTH;
+  /* Default to adding an anonymous hidden service if no flag is given */
+  int non_anonymous = 0;
   for (size_t i = 1; i < arg_len; i++) {
   for (size_t i = 1; i < arg_len; i++) {
     static const char *port_prefix = "Port=";
     static const char *port_prefix = "Port=";
     static const char *flags_prefix = "Flags=";
     static const char *flags_prefix = "Flags=";
@@ -4285,11 +4287,16 @@ handle_control_add_onion(control_connection_t *conn,
        *   * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is
        *   * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is
        *                                exceeded.
        *                                exceeded.
        *   * 'BasicAuth' - Client authorization using the 'basic' method.
        *   * 'BasicAuth' - Client authorization using the 'basic' method.
+       *   * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this
+       *                      flag is present, tor must be in non-anonymous
+       *                      hidden service mode. If this flag is absent,
+       *                      tor must be in anonymous hidden service mode.
        */
        */
       static const char *discard_flag = "DiscardPK";
       static const char *discard_flag = "DiscardPK";
       static const char *detach_flag = "Detach";
       static const char *detach_flag = "Detach";
       static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
       static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
       static const char *basicauth_flag = "BasicAuth";
       static const char *basicauth_flag = "BasicAuth";
+      static const char *non_anonymous_flag = "NonAnonymous";
 
 
       smartlist_t *flags = smartlist_new();
       smartlist_t *flags = smartlist_new();
       int bad = 0;
       int bad = 0;
@@ -4310,6 +4317,8 @@ handle_control_add_onion(control_connection_t *conn,
           max_streams_close_circuit = 1;
           max_streams_close_circuit = 1;
         } else if (!strcasecmp(flag, basicauth_flag)) {
         } else if (!strcasecmp(flag, basicauth_flag)) {
           auth_type = REND_BASIC_AUTH;
           auth_type = REND_BASIC_AUTH;
+        } else if (!strcasecmp(flag, non_anonymous_flag)) {
+          non_anonymous = 1;
         } else {
         } else {
           connection_printf_to_buf(conn,
           connection_printf_to_buf(conn,
                                    "512 Invalid 'Flags' argument: %s\r\n",
                                    "512 Invalid 'Flags' argument: %s\r\n",
@@ -4378,6 +4387,19 @@ handle_control_add_onion(control_connection_t *conn,
               smartlist_len(auth_clients) > 16)) {
               smartlist_len(auth_clients) > 16)) {
     connection_printf_to_buf(conn, "512 Too many auth clients\r\n");
     connection_printf_to_buf(conn, "512 Too many auth clients\r\n");
     goto out;
     goto out;
+  } else if (non_anonymous != rend_service_non_anonymous_mode_enabled(
+                                                              get_options())) {
+    /* If we failed, and the non-anonymous flag is set, Tor must be in
+     * anonymous hidden service mode.
+     * The error message changes based on the current Tor config:
+     * 512 Tor is in anonymous hidden service mode
+     * 512 Tor is in non-anonymous hidden service mode
+     * (I've deliberately written them out in full here to aid searchability.)
+     */
+    connection_printf_to_buf(conn, "512 Tor is in %sanonymous hidden service "
+                             "mode\r\n",
+                             non_anonymous ? "" : "non-");
+    goto out;
   }
   }
 
 
   /* Parse the "keytype:keyblob" argument. */
   /* Parse the "keytype:keyblob" argument. */

+ 5 - 7
src/or/directory.c

@@ -1085,7 +1085,7 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
  * <b>dir_purpose</b> reveals sensitive information about a Tor
  * <b>dir_purpose</b> reveals sensitive information about a Tor
  * instance's client activities.  (Such connections must be performed
  * instance's client activities.  (Such connections must be performed
  * through normal three-hop Tor circuits.) */
  * through normal three-hop Tor circuits.) */
-static int
+int
 is_sensitive_dir_purpose(uint8_t dir_purpose)
 is_sensitive_dir_purpose(uint8_t dir_purpose)
 {
 {
   return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) ||
   return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) ||
@@ -1140,12 +1140,10 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
 
 
   log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
   log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
 
 
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(is_sensitive_dir_purpose(dir_purpose) &&
-               !anonymized_connection));
-#else
-  (void)is_sensitive_dir_purpose;
-#endif
+  if (is_sensitive_dir_purpose(dir_purpose)) {
+    tor_assert(anonymized_connection ||
+               rend_non_anonymous_mode_enabled(options));
+  }
 
 
   /* use encrypted begindir connections for everything except relays
   /* use encrypted begindir connections for everything except relays
    * this provides better protection for directory fetches */
    * this provides better protection for directory fetches */

+ 3 - 0
src/or/directory.h

@@ -132,7 +132,10 @@ int download_status_get_n_failures(const download_status_t *dls);
 int download_status_get_n_attempts(const download_status_t *dls);
 int download_status_get_n_attempts(const download_status_t *dls);
 time_t download_status_get_next_attempt_at(const download_status_t *dls);
 time_t download_status_get_next_attempt_at(const download_status_t *dls);
 
 
+/* Yes, these two functions are confusingly similar.
+ * Let's sort that out in #20077. */
 int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose);
 int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose);
+int is_sensitive_dir_purpose(uint8_t dir_purpose);
 
 
 #ifdef TOR_UNIT_TESTS
 #ifdef TOR_UNIT_TESTS
 /* Used only by directory.c and test_dir.c */
 /* Used only by directory.c and test_dir.c */

+ 6 - 8
src/or/main.c

@@ -2833,11 +2833,6 @@ tor_init(int argc, char *argv[])
                  "Expect more bugs than usual.");
                  "Expect more bugs than usual.");
   }
   }
 
 
-#ifdef NON_ANONYMOUS_MODE_ENABLED
-  log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a "
-      "non-anonymous mode. It will provide NO ANONYMITY.");
-#endif
-
   if (network_init()<0) {
   if (network_init()<0) {
     log_err(LD_BUG,"Error initializing network; exiting.");
     log_err(LD_BUG,"Error initializing network; exiting.");
     return -1;
     return -1;
@@ -2849,15 +2844,18 @@ tor_init(int argc, char *argv[])
     return -1;
     return -1;
   }
   }
 
 
+  /* The options are now initialised */
+  const or_options_t *options = get_options();
+
 #ifndef _WIN32
 #ifndef _WIN32
   if (geteuid()==0)
   if (geteuid()==0)
     log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, "
     log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, "
              "and you probably shouldn't.");
              "and you probably shouldn't.");
 #endif
 #endif
 
 
-  if (crypto_global_init(get_options()->HardwareAccel,
-                         get_options()->AccelName,
-                         get_options()->AccelDir)) {
+  if (crypto_global_init(options->HardwareAccel,
+                         options->AccelName,
+                         options->AccelDir)) {
     log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
     log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
     return -1;
     return -1;
   }
   }

+ 41 - 7
src/or/or.h

@@ -3606,9 +3606,13 @@ typedef struct {
 
 
   /** @name port booleans
   /** @name port booleans
    *
    *
-   * Derived booleans: True iff there is a non-listener port on an AF_INET or
-   * AF_INET6 address of the given type configured in one of the _lines
-   * options above.
+   * Derived booleans: For server ports and ControlPort, true iff there is a
+   * non-listener port on an AF_INET or AF_INET6 address of the given type
+   * configured in one of the _lines options above.
+   * For client ports, also true if there is a unix socket configured.
+   * If you are checking for client ports, you may want to use:
+   *   SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set
+   * rather than SocksPort_set.
    *
    *
    * @{
    * @{
    */
    */
@@ -3699,6 +3703,26 @@ typedef struct {
    * they reach the normal circuit-build timeout. */
    * they reach the normal circuit-build timeout. */
   int CloseHSServiceRendCircuitsImmediatelyOnTimeout;
   int CloseHSServiceRendCircuitsImmediatelyOnTimeout;
 
 
+  /** Onion Services in HiddenServiceSingleHopMode make one-hop (direct)
+   * circuits between the onion service server, and the introduction and
+   * rendezvous points. (Onion service descriptors are still posted using
+   * 3-hop paths, to avoid onion service directories blocking the service.)
+   * This option makes every hidden service instance hosted by
+   * this tor instance a Single Onion Service.
+   * HiddenServiceSingleHopMode requires HiddenServiceNonAnonymousMode to be
+   * set to 1.
+   * Use rend_service_allow_non_anonymous_connection() or
+   * rend_service_reveal_startup_time() instead of using this option directly.
+   */
+  int HiddenServiceSingleHopMode;
+  /* Makes hidden service clients and servers non-anonymous on this tor
+   * instance. Allows the non-anonymous HiddenServiceSingleHopMode. Enables
+   * non-anonymous behaviour in the hidden service protocol.
+   * Use rend_service_non_anonymous_mode_enabled() instead of using this option
+   * directly.
+   */
+  int HiddenServiceNonAnonymousMode;
+
   int ConnLimit; /**< Demanded minimum number of simultaneous connections. */
   int ConnLimit; /**< Demanded minimum number of simultaneous connections. */
   int ConnLimit_; /**< Maximum allowed number of simultaneous connections. */
   int ConnLimit_; /**< Maximum allowed number of simultaneous connections. */
   int ConnLimit_high_thresh; /**< start trying to lower socket usage if we
   int ConnLimit_high_thresh; /**< start trying to lower socket usage if we
@@ -3754,7 +3778,8 @@ typedef struct {
                      * unattached before we fail it? */
                      * unattached before we fail it? */
   int LearnCircuitBuildTimeout; /**< If non-zero, we attempt to learn a value
   int LearnCircuitBuildTimeout; /**< If non-zero, we attempt to learn a value
                                  * for CircuitBuildTimeout based on timeout
                                  * for CircuitBuildTimeout based on timeout
-                                 * history */
+                                 * history. Use circuit_build_times_disabled()
+                                 * rather than checking this value directly. */
   int CircuitBuildTimeout; /**< Cull non-open circuits that were born at
   int CircuitBuildTimeout; /**< Cull non-open circuits that were born at
                             * least this many seconds ago. Used until
                             * least this many seconds ago. Used until
                             * adaptive algorithm learns a new value. */
                             * adaptive algorithm learns a new value. */
@@ -3940,8 +3965,16 @@ typedef struct {
   int TokenBucketRefillInterval;
   int TokenBucketRefillInterval;
   char *AccelName; /**< Optional hardware acceleration engine name. */
   char *AccelName; /**< Optional hardware acceleration engine name. */
   char *AccelDir; /**< Optional hardware acceleration engine search dir. */
   char *AccelDir; /**< Optional hardware acceleration engine search dir. */
-  int UseEntryGuards; /**< Boolean: Do we try to enter from a smallish number
-                       * of fixed nodes? */
+
+  /** Boolean: Do we try to enter from a smallish number
+   * of fixed nodes? */
+  int UseEntryGuards_option;
+  /** Internal variable to remember whether we're actually acting on
+   * UseEntryGuards_option -- when we're a non-anonymous Tor2web client or
+   * Single Onion Service, it is alwasy false, otherwise we use the value of
+   * UseEntryGuards_option. */
+  int UseEntryGuards;
+
   int NumEntryGuards; /**< How many entry guards do we try to establish? */
   int NumEntryGuards; /**< How many entry guards do we try to establish? */
   int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
   int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
                                   * from a smallish number of fixed nodes? */
                                   * from a smallish number of fixed nodes? */
@@ -5054,7 +5087,8 @@ typedef struct rend_encoded_v2_service_descriptor_t {
  * the service side) and in rend_service_descriptor_t (on both the
  * the service side) and in rend_service_descriptor_t (on both the
  * client and service side). */
  * client and service side). */
 typedef struct rend_intro_point_t {
 typedef struct rend_intro_point_t {
-  extend_info_t *extend_info; /**< Extend info of this introduction point. */
+  extend_info_t *extend_info; /**< Extend info for connecting to this
+                               * introduction point via a multi-hop path. */
   crypto_pk_t *intro_key; /**< Introduction key that replaces the service
   crypto_pk_t *intro_key; /**< Introduction key that replaces the service
                                * key, if this descriptor is V2. */
                                * key, if this descriptor is V2. */
 
 

+ 39 - 10
src/or/rendclient.c

@@ -134,6 +134,7 @@ int
 rend_client_send_introduction(origin_circuit_t *introcirc,
 rend_client_send_introduction(origin_circuit_t *introcirc,
                               origin_circuit_t *rendcirc)
                               origin_circuit_t *rendcirc)
 {
 {
+  const or_options_t *options = get_options();
   size_t payload_len;
   size_t payload_len;
   int r, v3_shift = 0;
   int r, v3_shift = 0;
   char payload[RELAY_PAYLOAD_SIZE];
   char payload[RELAY_PAYLOAD_SIZE];
@@ -150,10 +151,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
   tor_assert(rendcirc->rend_data);
   tor_assert(rendcirc->rend_data);
   tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
   tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
                                    rendcirc->rend_data->onion_address));
                                    rendcirc->rend_data->onion_address));
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(introcirc->build_state->onehop_tunnel));
-  tor_assert(!(rendcirc->build_state->onehop_tunnel));
-#endif
+  assert_circ_anonymity_ok(introcirc, options);
+  assert_circ_anonymity_ok(rendcirc, options);
 
 
   r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
   r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
                               &entry);
                               &entry);
@@ -387,6 +386,7 @@ int
 rend_client_introduction_acked(origin_circuit_t *circ,
 rend_client_introduction_acked(origin_circuit_t *circ,
                                const uint8_t *request, size_t request_len)
                                const uint8_t *request, size_t request_len)
 {
 {
+  const or_options_t *options = get_options();
   origin_circuit_t *rendcirc;
   origin_circuit_t *rendcirc;
   (void) request; // XXXX Use this.
   (void) request; // XXXX Use this.
 
 
@@ -398,10 +398,9 @@ rend_client_introduction_acked(origin_circuit_t *circ,
     return -1;
     return -1;
   }
   }
 
 
+  tor_assert(circ->build_state);
   tor_assert(circ->build_state->chosen_exit);
   tor_assert(circ->build_state->chosen_exit);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(circ->build_state->onehop_tunnel));
-#endif
+  assert_circ_anonymity_ok(circ, options);
   tor_assert(circ->rend_data);
   tor_assert(circ->rend_data);
 
 
   /* For path bias: This circuit was used successfully. Valid
   /* For path bias: This circuit was used successfully. Valid
@@ -416,9 +415,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
     log_info(LD_REND,"Received ack. Telling rend circ...");
     log_info(LD_REND,"Received ack. Telling rend circ...");
     rendcirc = circuit_get_ready_rend_circ_by_rend_data(circ->rend_data);
     rendcirc = circuit_get_ready_rend_circ_by_rend_data(circ->rend_data);
     if (rendcirc) { /* remember the ack */
     if (rendcirc) { /* remember the ack */
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-      tor_assert(!(rendcirc->build_state->onehop_tunnel));
-#endif
+      assert_circ_anonymity_ok(rendcirc, options);
       circuit_change_purpose(TO_CIRCUIT(rendcirc),
       circuit_change_purpose(TO_CIRCUIT(rendcirc),
                              CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
                              CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
       /* Set timestamp_dirty, because circuit_expire_building expects
       /* Set timestamp_dirty, because circuit_expire_building expects
@@ -1527,3 +1524,35 @@ rend_parse_service_authorization(const or_options_t *options,
   return res;
   return res;
 }
 }
 
 
+/* Can Tor client code make direct (non-anonymous) connections to introduction
+ * or rendezvous points?
+ * Returns true if tor was compiled with NON_ANONYMOUS_MODE_ENABLED, and is
+ * configured in Tor2web mode. */
+int
+rend_client_allow_non_anonymous_connection(const or_options_t *options)
+{
+  /* Tor2web support needs to be compiled in to a tor binary. */
+#ifdef NON_ANONYMOUS_MODE_ENABLED
+  /* Tor2web */
+  return options->Tor2webMode ? 1 : 0;
+#else
+  (void)options;
+  return 0;
+#endif
+}
+
+/* At compile-time, was non-anonymous mode enabled via
+ * NON_ANONYMOUS_MODE_ENABLED ? */
+int
+rend_client_non_anonymous_mode_enabled(const or_options_t *options)
+{
+  (void)options;
+  /* Tor2web support needs to be compiled in to a tor binary. */
+#ifdef NON_ANONYMOUS_MODE_ENABLED
+  /* Tor2web */
+  return 1;
+#else
+  return 0;
+#endif
+}
+

+ 3 - 0
src/or/rendclient.h

@@ -51,5 +51,8 @@ rend_service_authorization_t *rend_client_lookup_service_authorization(
                                                 const char *onion_address);
                                                 const char *onion_address);
 void rend_service_authorization_free_all(void);
 void rend_service_authorization_free_all(void);
 
 
+int rend_client_allow_non_anonymous_connection(const or_options_t *options);
+int rend_client_non_anonymous_mode_enabled(const or_options_t *options);
+
 #endif
 #endif
 
 

+ 48 - 0
src/or/rendcommon.c

@@ -1067,3 +1067,51 @@ rend_auth_decode_cookie(const char *cookie_in, uint8_t *cookie_out,
   return res;
   return res;
 }
 }
 
 
+/* Is this a rend client or server that allows direct (non-anonymous)
+ * connections?
+ * Clients must be specifically compiled and configured in this mode.
+ * Onion services can be configured to start in this mode.
+ * Prefer rend_client_allow_non_anonymous_connection() or
+ * rend_service_allow_non_anonymous_connection() whenever possible, so that
+ * checks are specific to Single Onion Services or Tor2web. */
+int
+rend_allow_non_anonymous_connection(const or_options_t* options)
+{
+  return (rend_client_allow_non_anonymous_connection(options)
+          || rend_service_allow_non_anonymous_connection(options));
+}
+
+/* Is this a rend client or server in non-anonymous mode?
+ * Clients must be specifically compiled in this mode.
+ * Onion services can be configured to start in this mode.
+ * Prefer rend_client_non_anonymous_mode_enabled() or
+ * rend_service_non_anonymous_mode_enabled() whenever possible, so that checks
+ * are specific to Single Onion Services or Tor2web. */
+int
+rend_non_anonymous_mode_enabled(const or_options_t *options)
+{
+  return (rend_client_non_anonymous_mode_enabled(options)
+          || rend_service_non_anonymous_mode_enabled(options));
+}
+
+/* Make sure that tor only builds one-hop circuits when they would not
+ * compromise user anonymity.
+ *
+ * One-hop circuits are permitted in Tor2web or Single Onion modes.
+ *
+ * Tor2web or Single Onion modes are also allowed to make multi-hop circuits.
+ * For example, single onion HSDir circuits are 3-hop to prevent denial of
+ * service.
+ */
+void
+assert_circ_anonymity_ok(origin_circuit_t *circ,
+                         const or_options_t *options)
+{
+  tor_assert(options);
+  tor_assert(circ);
+  tor_assert(circ->build_state);
+
+  if (circ->build_state->onehop_tunnel) {
+    tor_assert(rend_allow_non_anonymous_connection(options));
+  }
+}

+ 6 - 0
src/or/rendcommon.h

@@ -77,5 +77,11 @@ int rend_auth_decode_cookie(const char *cookie_in,
                             rend_auth_type_t *auth_type_out,
                             rend_auth_type_t *auth_type_out,
                             char **err_msg_out);
                             char **err_msg_out);
 
 
+int rend_allow_non_anonymous_connection(const or_options_t* options);
+int rend_non_anonymous_mode_enabled(const or_options_t *options);
+
+void assert_circ_anonymity_ok(origin_circuit_t *circ,
+                              const or_options_t *options);
+
 #endif
 #endif
 
 

+ 448 - 126
src/or/rendservice.c

@@ -20,6 +20,7 @@
 #include "main.h"
 #include "main.h"
 #include "networkstatus.h"
 #include "networkstatus.h"
 #include "nodelist.h"
 #include "nodelist.h"
+#include "policies.h"
 #include "rendclient.h"
 #include "rendclient.h"
 #include "rendcommon.h"
 #include "rendcommon.h"
 #include "rendservice.h"
 #include "rendservice.h"
@@ -107,59 +108,13 @@ struct rend_service_port_config_s {
  * rendezvous point before giving up? */
  * rendezvous point before giving up? */
 #define MAX_REND_TIMEOUT 30
 #define MAX_REND_TIMEOUT 30
 
 
-/** Represents a single hidden service running at this OP. */
-typedef struct rend_service_t {
-  /* Fields specified in config file */
-  char *directory; /**< where in the filesystem it stores it. Will be NULL if
-                    * this service is ephemeral. */
-  int dir_group_readable; /**< if 1, allow group read
-                             permissions on directory */
-  smartlist_t *ports; /**< List of rend_service_port_config_t */
-  rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
-                               * authorization is performed. */
-  smartlist_t *clients; /**< List of rend_authorized_client_t's of
-                         * clients that may access our service. Can be NULL
-                         * if no client authorization is performed. */
-  /* Other fields */
-  crypto_pk_t *private_key; /**< Permanent hidden-service key. */
-  char service_id[REND_SERVICE_ID_LEN_BASE32+1]; /**< Onion address without
-                                                  * '.onion' */
-  char pk_digest[DIGEST_LEN]; /**< Hash of permanent hidden-service key. */
-  smartlist_t *intro_nodes; /**< List of rend_intro_point_t's we have,
-                             * or are trying to establish. */
-  /** List of rend_intro_point_t that are expiring. They are removed once
-   * the new descriptor is successfully uploaded. A node in this list CAN
-   * NOT appear in the intro_nodes list. */
-  smartlist_t *expiring_nodes;
-  time_t intro_period_started; /**< Start of the current period to build
-                                * introduction points. */
-  int n_intro_circuits_launched; /**< Count of intro circuits we have
-                                  * established in this period. */
-  unsigned int n_intro_points_wanted; /**< Number of intro points this
-                                       * service wants to have open. */
-  rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
-  time_t desc_is_dirty; /**< Time at which changes to the hidden service
-                         * descriptor content occurred, or 0 if it's
-                         * up-to-date. */
-  time_t next_upload_time; /**< Scheduled next hidden service descriptor
-                            * upload time. */
-  /** Replay cache for Diffie-Hellman values of INTRODUCE2 cells, to
-   * detect repeats.  Clients may send INTRODUCE1 cells for the same
-   * rendezvous point through two or more different introduction points;
-   * when they do, this keeps us from launching multiple simultaneous attempts
-   * to connect to the same rend point. */
-  replaycache_t *accepted_intro_dh_parts;
-  /** If true, we don't close circuits for making requests to unsupported
-   * ports. */
-  int allow_unknown_ports;
-  /** The maximum number of simultanious streams-per-circuit that are allowed
-   * to be established, or 0 if no limit is set.
-   */
-  int max_streams_per_circuit;
-  /** If true, we close circuits that exceed the max_streams_per_circuit
-   * limit.  */
-  int max_streams_close_circuit;
-} rend_service_t;
+/* Hidden service directory file names:
+ * new file names should be added to rend_service_add_filenames_to_list()
+ * for sandboxing purposes. */
+static const char *private_key_fname = "private_key";
+static const char *hostname_fname = "hostname";
+static const char *client_keys_fname = "client_keys";
+static const char *sos_poison_fname = "onion_service_non_anonymous";
 
 
 /** Returns a escaped string representation of the service, <b>s</b>.
 /** Returns a escaped string representation of the service, <b>s</b>.
  */
  */
@@ -206,16 +161,18 @@ rend_authorized_client_strmap_item_free(void *authorized_client)
 
 
 /** Release the storage held by <b>service</b>.
 /** Release the storage held by <b>service</b>.
  */
  */
-static void
+STATIC void
 rend_service_free(rend_service_t *service)
 rend_service_free(rend_service_t *service)
 {
 {
   if (!service)
   if (!service)
     return;
     return;
 
 
   tor_free(service->directory);
   tor_free(service->directory);
-  SMARTLIST_FOREACH(service->ports, rend_service_port_config_t*, p,
-                    rend_service_port_config_free(p));
-  smartlist_free(service->ports);
+  if (service->ports) {
+    SMARTLIST_FOREACH(service->ports, rend_service_port_config_t*, p,
+                      rend_service_port_config_free(p));
+    smartlist_free(service->ports);
+  }
   if (service->private_key)
   if (service->private_key)
     crypto_pk_free(service->private_key);
     crypto_pk_free(service->private_key);
   if (service->intro_nodes) {
   if (service->intro_nodes) {
@@ -1001,13 +958,235 @@ rend_service_update_descriptor(rend_service_t *service)
   }
   }
 }
 }
 
 
+/* Allocate and return a string containing the path to file_name in
+ * service->directory. Asserts that service has a directory.
+ * This function will never return NULL.
+ * The caller must free this path. */
+static char *
+rend_service_path(const rend_service_t *service, const char *file_name)
+{
+  char *file_path = NULL;
+
+  tor_assert(service->directory);
+
+  /* Can never fail: asserts rather than leaving file_path NULL. */
+  tor_asprintf(&file_path, "%s%s%s",
+               service->directory, PATH_SEPARATOR, file_name);
+
+  return file_path;
+}
+
+/* Allocate and return a string containing the path to the single onion
+ * service poison file in service->directory. Asserts that service has a
+ * directory.
+ * The caller must free this path. */
+STATIC char *
+rend_service_sos_poison_path(const rend_service_t *service)
+{
+  return rend_service_path(service, sos_poison_fname);
+}
+
+/** Return True if hidden services <b>service> has been poisoned by single
+ * onion mode. */
+static int
+service_is_single_onion_poisoned(const rend_service_t *service)
+{
+  char *poison_fname = NULL;
+  file_status_t fstatus;
+
+  if (!service->directory) {
+    return 0;
+  }
+
+  poison_fname = rend_service_sos_poison_path(service);
+
+  fstatus = file_status(poison_fname);
+  tor_free(poison_fname);
+
+  /* If this fname is occupied, the hidden service has been poisoned. */
+  if (fstatus == FN_FILE || fstatus == FN_EMPTY) {
+    return 1;
+  }
+
+  return 0;
+}
+
+/* Return 1 if the private key file for service exists and has a non-zero size,
+ * and 0 otherwise. */
+static int
+rend_service_private_key_exists(const rend_service_t *service)
+{
+  char *private_key_path = rend_service_path(service, private_key_fname);
+  const file_status_t private_key_status = file_status(private_key_path);
+  tor_free(private_key_path);
+  /* Only non-empty regular private key files could have been used before. */
+  return private_key_status == FN_FILE;
+}
+
+/** Check the single onion service poison state of all existing hidden service
+ * directories:
+ * - If each service is poisoned, and we are in Single Onion Mode,
+ *   return 0,
+ * - If each service is not poisoned, and we are not in Single Onion Mode,
+ *   return 0,
+ * - Otherwise, the poison state is invalid, and a service that was created in
+ *   one mode is being used in the other, return -1.
+ * Hidden service directories without keys are not checked for consistency.
+ * When their keys are created, they will be poisoned (if needed).
+ * If a <b>service_list</b> is provided, treat it
+ * as the list of hidden services (used in unittests). */
+int
+rend_service_list_verify_single_onion_poison(const smartlist_t *service_list,
+                                             const or_options_t *options)
+{
+  const smartlist_t *s_list;
+  /* If no special service list is provided, then just use the global one. */
+  if (!service_list) {
+    if (!rend_service_list) { /* No global HS list. Nothing to see here. */
+      return 0;
+    }
+
+    s_list = rend_service_list;
+  } else {
+    s_list = service_list;
+  }
+
+  int consistent = 1;
+  SMARTLIST_FOREACH_BEGIN(s_list, const rend_service_t *, s) {
+    if (service_is_single_onion_poisoned(s) !=
+        rend_service_non_anonymous_mode_enabled(options) &&
+        rend_service_private_key_exists(s)) {
+      consistent = 0;
+    }
+  } SMARTLIST_FOREACH_END(s);
+
+  return consistent ? 0 : -1;
+}
+
+/*** Helper for rend_service_poison_new_single_onion_dirs(). Add a file to
+ * this hidden service directory that marks it as a single onion service.
+ * Tor must be in single onion mode before calling this function.
+ * Returns 0 when a directory is successfully poisoned, or if it is already
+ * poisoned. Returns -1 on a failure to read the directory or write the poison
+ * file, or if there is an existing private key file in the directory. (The
+ * service should have been poisoned when the key was created.) */
+static int
+poison_new_single_onion_hidden_service_dir(const rend_service_t *service)
+{
+  /* We must only poison directories if we're in Single Onion mode */
+  tor_assert(rend_service_non_anonymous_mode_enabled(get_options()));
+
+  int fd;
+  int retval = -1;
+  char *poison_fname = NULL;
+
+  if (!service->directory) {
+    log_info(LD_REND, "Ephemeral HS started in non-anonymous mode.");
+    return 0;
+  }
+
+  /* Make sure we're only poisoning new hidden service directories */
+  if (rend_service_private_key_exists(service)) {
+    log_warn(LD_BUG, "Tried to single onion poison a service directory after "
+             "the private key was created.");
+    return -1;
+  }
+
+  poison_fname = rend_service_sos_poison_path(service);
+
+  switch (file_status(poison_fname)) {
+  case FN_DIR:
+  case FN_ERROR:
+    log_warn(LD_FS, "Can't read single onion poison file \"%s\"",
+             poison_fname);
+    goto done;
+  case FN_FILE: /* single onion poison file already exists. NOP. */
+  case FN_EMPTY: /* single onion poison file already exists. NOP. */
+    log_debug(LD_FS, "Tried to re-poison a single onion poisoned file \"%s\"",
+              poison_fname);
+    break;
+  case FN_NOENT:
+    fd = tor_open_cloexec(poison_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+    if (fd < 0) {
+      log_warn(LD_FS, "Could not create single onion poison file %s",
+               poison_fname);
+      goto done;
+    }
+    close(fd);
+    break;
+  default:
+    tor_assert(0);
+  }
+
+  retval = 0;
+
+ done:
+  tor_free(poison_fname);
+
+  return retval;
+}
+
+/** We just got launched in Single Onion Mode. That's a non-anoymous
+ * mode for hidden services; hence we should mark all new hidden service
+ * directories appropriately so that they are never launched as
+ * location-private hidden services again. (New directories don't have private
+ * key files.)
+ * If a <b>service_list</b> is provided, treat it as the list of hidden
+ * services (used in unittests).
+ * Return 0 on success, -1 on fail. */
+int
+rend_service_poison_new_single_onion_dirs(const smartlist_t *service_list)
+{
+  /* We must only poison directories if we're in Single Onion mode */
+  tor_assert(rend_service_non_anonymous_mode_enabled(get_options()));
+
+  const smartlist_t *s_list;
+  /* If no special service list is provided, then just use the global one. */
+  if (!service_list) {
+    if (!rend_service_list) { /* No global HS list. Nothing to see here. */
+      return 0;
+    }
+
+    s_list = rend_service_list;
+  } else {
+    s_list = service_list;
+  }
+
+  SMARTLIST_FOREACH_BEGIN(s_list, const rend_service_t *, s) {
+    if (!rend_service_private_key_exists(s)) {
+      if (poison_new_single_onion_hidden_service_dir(s) < 0) {
+        return -1;
+      }
+    }
+  } SMARTLIST_FOREACH_END(s);
+
+  /* The keys for these services are linked to the server IP address */
+  log_notice(LD_REND, "The configured onion service directories have been "
+             "used in single onion mode. They can not be used for anonymous "
+             "hidden services.");
+
+  return 0;
+}
+
 /** Load and/or generate private keys for all hidden services, possibly
 /** Load and/or generate private keys for all hidden services, possibly
- * including keys for client authorization.  Return 0 on success, -1 on
- * failure. */
+ * including keys for client authorization.
+ * If a <b>service_list</b> is provided, treat it as the list of hidden
+ * services (used in unittests). Otherwise, require that rend_service_list is
+ * not NULL.
+ * Return 0 on success, -1 on failure. */
 int
 int
-rend_service_load_all_keys(void)
+rend_service_load_all_keys(const smartlist_t *service_list)
 {
 {
-  SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) {
+  const smartlist_t *s_list;
+  /* If no special service list is provided, then just use the global one. */
+  if (!service_list) {
+    tor_assert(rend_service_list);
+    s_list = rend_service_list;
+  } else {
+    s_list = service_list;
+  }
+
+  SMARTLIST_FOREACH_BEGIN(s_list, rend_service_t *, s) {
     if (s->private_key)
     if (s->private_key)
       continue;
       continue;
     log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
     log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
@@ -1027,12 +1206,10 @@ rend_service_add_filenames_to_list(smartlist_t *lst, const rend_service_t *s)
   tor_assert(lst);
   tor_assert(lst);
   tor_assert(s);
   tor_assert(s);
   tor_assert(s->directory);
   tor_assert(s->directory);
-  smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"private_key",
-                         s->directory);
-  smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"hostname",
-                         s->directory);
-  smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"client_keys",
-                         s->directory);
+  smartlist_add(lst, rend_service_path(s, private_key_fname));
+  smartlist_add(lst, rend_service_path(s, hostname_fname));
+  smartlist_add(lst, rend_service_path(s, client_keys_fname));
+  smartlist_add(lst, rend_service_sos_poison_path(s));
 }
 }
 
 
 /** Add to <b>open_lst</b> every filename used by a configured hidden service,
 /** Add to <b>open_lst</b> every filename used by a configured hidden service,
@@ -1076,7 +1253,7 @@ rend_service_derive_key_digests(struct rend_service_t *s)
 static int
 static int
 rend_service_load_keys(rend_service_t *s)
 rend_service_load_keys(rend_service_t *s)
 {
 {
-  char fname[512];
+  char *fname = NULL;
   char buf[128];
   char buf[128];
   cpd_check_t  check_opts = CPD_CREATE;
   cpd_check_t  check_opts = CPD_CREATE;
 
 
@@ -1085,7 +1262,7 @@ rend_service_load_keys(rend_service_t *s)
   }
   }
   /* Check/create directory */
   /* Check/create directory */
   if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) {
   if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) {
-    return -1;
+    goto err;
   }
   }
 #ifndef _WIN32
 #ifndef _WIN32
   if (s->dir_group_readable) {
   if (s->dir_group_readable) {
@@ -1097,34 +1274,23 @@ rend_service_load_keys(rend_service_t *s)
 #endif
 #endif
 
 
   /* Load key */
   /* 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;
-  }
+  fname = rend_service_path(s, private_key_fname);
   s->private_key = init_key_from_file(fname, 1, LOG_ERR, 0);
   s->private_key = init_key_from_file(fname, 1, LOG_ERR, 0);
+
   if (!s->private_key)
   if (!s->private_key)
-    return -1;
+    goto err;
 
 
   if (rend_service_derive_key_digests(s) < 0)
   if (rend_service_derive_key_digests(s) < 0)
-    return -1;
+    goto err;
 
 
+  tor_free(fname);
   /* Create service file */
   /* Create service file */
-  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;
-  }
+  fname = rend_service_path(s, hostname_fname);
 
 
   tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id);
   tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id);
   if (write_str_to_file(fname,buf,0)<0) {
   if (write_str_to_file(fname,buf,0)<0) {
     log_warn(LD_CONFIG, "Could not write onion address to hostname file.");
     log_warn(LD_CONFIG, "Could not write onion address to hostname file.");
-    memwipe(buf, 0, sizeof(buf));
-    return -1;
+    goto err;
   }
   }
 #ifndef _WIN32
 #ifndef _WIN32
   if (s->dir_group_readable) {
   if (s->dir_group_readable) {
@@ -1135,15 +1301,21 @@ rend_service_load_keys(rend_service_t *s)
   }
   }
 #endif
 #endif
 
 
-  memwipe(buf, 0, sizeof(buf));
-
   /* If client authorization is configured, load or generate keys. */
   /* If client authorization is configured, load or generate keys. */
   if (s->auth_type != REND_NO_AUTH) {
   if (s->auth_type != REND_NO_AUTH) {
-    if (rend_service_load_auth_keys(s, fname) < 0)
-      return -1;
+    if (rend_service_load_auth_keys(s, fname) < 0) {
+      goto err;
+    }
   }
   }
 
 
-  return 0;
+  int r = 0;
+  goto done;
+ err:
+  r = -1;
+ done:
+  memwipe(buf, 0, sizeof(buf));
+  tor_free(fname);
+  return r;
 }
 }
 
 
 /** Load and/or generate client authorization keys for the hidden service
 /** Load and/or generate client authorization keys for the hidden service
@@ -1153,7 +1325,7 @@ static int
 rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
 rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
 {
 {
   int r = 0;
   int r = 0;
-  char cfname[512];
+  char *cfname = NULL;
   char *client_keys_str = NULL;
   char *client_keys_str = NULL;
   strmap_t *parsed_clients = strmap_new();
   strmap_t *parsed_clients = strmap_new();
   FILE *cfile, *hfile;
   FILE *cfile, *hfile;
@@ -1163,12 +1335,7 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
   char buf[1500];
   char buf[1500];
 
 
   /* Load client keys and descriptor cookies, if available. */
   /* 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;
-  }
+  cfname = rend_service_path(s, client_keys_fname);
   client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL);
   client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL);
   if (client_keys_str) {
   if (client_keys_str) {
     if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) {
     if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) {
@@ -1322,7 +1489,10 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
   }
   }
   strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
   strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
 
 
-  memwipe(cfname, 0, sizeof(cfname));
+  if (cfname) {
+    memwipe(cfname, 0, sizeof(cfname));
+    tor_free(cfname);
+  }
 
 
   /* Clear stack buffers that held key-derived material. */
   /* Clear stack buffers that held key-derived material. */
   memwipe(buf, 0, sizeof(buf));
   memwipe(buf, 0, sizeof(buf));
@@ -1424,6 +1594,31 @@ rend_check_authorization(rend_service_t *service,
   return 1;
   return 1;
 }
 }
 
 
+/* Can this service make a direct connection to ei?
+ * It must be a single onion service, and the firewall rules must allow ei. */
+static int
+rend_service_use_direct_connection(const or_options_t* options,
+                                   const extend_info_t* ei)
+{
+  /* We'll connect directly all reachable addresses, whether preferred or not.
+   * The prefer_ipv6 argument to fascist_firewall_allows_address_addr is
+   * ignored, because pref_only is 0. */
+  return (rend_service_allow_non_anonymous_connection(options) &&
+          fascist_firewall_allows_address_addr(&ei->addr, ei->port,
+                                               FIREWALL_OR_CONNECTION, 0, 0));
+}
+
+/* Like rend_service_use_direct_connection, but to a node. */
+static int
+rend_service_use_direct_connection_node(const or_options_t* options,
+                                        const node_t* node)
+{
+  /* We'll connect directly all reachable addresses, whether preferred or not.
+   */
+  return (rend_service_allow_non_anonymous_connection(options) &&
+          fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0));
+}
+
 /******
 /******
  * Handle cells
  * Handle cells
  ******/
  ******/
@@ -1473,9 +1668,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
     goto err;
     goto err;
   }
   }
 
 
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+  assert_circ_anonymity_ok(circuit, options);
   tor_assert(circuit->rend_data);
   tor_assert(circuit->rend_data);
 
 
   /* We'll use this in a bazillion log messages */
   /* We'll use this in a bazillion log messages */
@@ -1679,6 +1872,11 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
   for (i=0;i<MAX_REND_FAILURES;i++) {
   for (i=0;i<MAX_REND_FAILURES;i++) {
     int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
     int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
     if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
     if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
+    /* A Single Onion Service only uses a direct connection if its
+     * firewall rules permit direct connections to the address. */
+    if (rend_service_use_direct_connection(options, rp)) {
+      flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL;
+    }
     launched = circuit_launch_by_extend_info(
     launched = circuit_launch_by_extend_info(
                         CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
                         CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
 
 
@@ -1791,7 +1989,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
       goto err;
       goto err;
     }
     }
 
 
-    rp = extend_info_from_node(node, 0);
+    /* Are we in single onion mode? */
+    const int allow_direct = rend_service_allow_non_anonymous_connection(
+                                                                get_options());
+    rp = extend_info_from_node(node, allow_direct);
     if (!rp) {
     if (!rp) {
       if (err_msg_out) {
       if (err_msg_out) {
         tor_asprintf(&err_msg,
         tor_asprintf(&err_msg,
@@ -1816,6 +2017,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
     goto err;
     goto err;
   }
   }
 
 
+  /* rp is always set here: extend_info_dup guarantees a non-NULL result, and
+   * the other cases goto err. */
+  tor_assert(rp);
+
   /* Make sure the RP we are being asked to connect to is _not_ a private
   /* Make sure the RP we are being asked to connect to is _not_ a private
    * address unless it's allowed. Let's avoid to build a circuit to our
    * address unless it's allowed. Let's avoid to build a circuit to our
    * second middle node and fail right after when extending to the RP. */
    * second middle node and fail right after when extending to the RP. */
@@ -2590,6 +2795,10 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
   log_info(LD_REND,"Reattempting rendezvous circuit to '%s'",
   log_info(LD_REND,"Reattempting rendezvous circuit to '%s'",
            safe_str(extend_info_describe(oldstate->chosen_exit)));
            safe_str(extend_info_describe(oldstate->chosen_exit)));
 
 
+  /* You'd think Single Onion Services would want to retry the rendezvous
+   * using a direct connection. But if it's blocked by a firewall, or the
+   * service is IPv6-only, or the rend point avoiding becoming a one-hop
+   * proxy, we need a 3-hop connection. */
   newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
   newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
                             oldstate->chosen_exit,
                             oldstate->chosen_exit,
                             CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL);
                             CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL);
@@ -2618,26 +2827,72 @@ rend_service_launch_establish_intro(rend_service_t *service,
                                     rend_intro_point_t *intro)
                                     rend_intro_point_t *intro)
 {
 {
   origin_circuit_t *launched;
   origin_circuit_t *launched;
+  int flags = CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL;
+  const or_options_t *options = get_options();
+  extend_info_t *launch_ei = intro->extend_info;
+  extend_info_t *direct_ei = NULL;
+
+  /* Are we in single onion mode? */
+  if (rend_service_allow_non_anonymous_connection(options)) {
+    /* Do we have a descriptor for the node?
+     * We've either just chosen it from the consensus, or we've just reviewed
+     * our intro points to see which ones are still valid, and deleted the ones
+     * that aren't in the consensus any more. */
+    const node_t *node = node_get_by_id(launch_ei->identity_digest);
+    if (BUG(!node)) {
+      /* The service has kept an intro point after it went missing from the
+       * consensus. If we did anything else here, it would be a consensus
+       * distinguisher. Which are less of an issue for single onion services,
+       * but still a bug. */
+      return -1;
+    }
+    /* Can we connect to the node directly? If so, replace launch_ei
+     * (a multi-hop extend_info) with one suitable for direct connection. */
+    if (rend_service_use_direct_connection_node(options, node)) {
+      direct_ei = extend_info_from_node(node, 1);
+      if (BUG(!direct_ei)) {
+        /* rend_service_use_direct_connection_node and extend_info_from_node
+         * disagree about which addresses on this node are permitted. This
+         * should never happen. Avoiding the connection is a safe response. */
+        return -1;
+      }
+      flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL;
+      launch_ei = direct_ei;
+    }
+  }
+  /* launch_ei is either intro->extend_info, or has been replaced with a valid
+   * extend_info for single onion service direct connection. */
+  tor_assert(launch_ei);
+  /* We must have the same intro when making a direct connection. */
+  tor_assert(tor_memeq(intro->extend_info->identity_digest,
+                       launch_ei->identity_digest,
+                       DIGEST_LEN));
 
 
   log_info(LD_REND,
   log_info(LD_REND,
-           "Launching circuit to introduction point %s for service %s",
+           "Launching circuit to introduction point %s%s%s for service %s",
            safe_str_client(extend_info_describe(intro->extend_info)),
            safe_str_client(extend_info_describe(intro->extend_info)),
+           direct_ei ? " via direct address " : "",
+           direct_ei ? safe_str_client(extend_info_describe(direct_ei)) : "",
            service->service_id);
            service->service_id);
 
 
   rep_hist_note_used_internal(time(NULL), 1, 0);
   rep_hist_note_used_internal(time(NULL), 1, 0);
 
 
   ++service->n_intro_circuits_launched;
   ++service->n_intro_circuits_launched;
   launched = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
   launched = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
-                             intro->extend_info,
-                             CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL);
+                             launch_ei, flags);
 
 
   if (!launched) {
   if (!launched) {
     log_info(LD_REND,
     log_info(LD_REND,
-             "Can't launch circuit to establish introduction at %s.",
-             safe_str_client(extend_info_describe(intro->extend_info)));
+             "Can't launch circuit to establish introduction at %s%s%s.",
+             safe_str_client(extend_info_describe(intro->extend_info)),
+             direct_ei ? " via direct address " : "",
+             direct_ei ? safe_str_client(extend_info_describe(direct_ei)) : ""
+             );
+    extend_info_free(direct_ei);
     return -1;
     return -1;
   }
   }
-  /* We must have the same exit node even if cannibalized. */
+  /* We must have the same exit node even if cannibalized or direct connection.
+   */
   tor_assert(tor_memeq(intro->extend_info->identity_digest,
   tor_assert(tor_memeq(intro->extend_info->identity_digest,
                        launched->build_state->chosen_exit->identity_digest,
                        launched->build_state->chosen_exit->identity_digest,
                        DIGEST_LEN));
                        DIGEST_LEN));
@@ -2648,6 +2903,7 @@ rend_service_launch_establish_intro(rend_service_t *service,
   launched->intro_key = crypto_pk_dup_key(intro->intro_key);
   launched->intro_key = crypto_pk_dup_key(intro->intro_key);
   if (launched->base_.state == CIRCUIT_STATE_OPEN)
   if (launched->base_.state == CIRCUIT_STATE_OPEN)
     rend_service_intro_has_opened(launched);
     rend_service_intro_has_opened(launched);
+  extend_info_free(direct_ei);
   return 0;
   return 0;
 }
 }
 
 
@@ -2703,9 +2959,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   int reason = END_CIRC_REASON_TORPROTOCOL;
   int reason = END_CIRC_REASON_TORPROTOCOL;
 
 
   tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
   tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+  assert_circ_anonymity_ok(circuit, get_options());
   tor_assert(circuit->cpath);
   tor_assert(circuit->cpath);
   tor_assert(circuit->rend_data);
   tor_assert(circuit->rend_data);
 
 
@@ -2772,6 +3026,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   log_info(LD_REND,
   log_info(LD_REND,
            "Established circuit %u as introduction point for service %s",
            "Established circuit %u as introduction point for service %s",
            (unsigned)circuit->base_.n_circ_id, serviceid);
            (unsigned)circuit->base_.n_circ_id, serviceid);
+  circuit_log_path(LOG_INFO, LD_REND, circuit);
 
 
   /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
   /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
   crypto_pk_t *intro_key = circuit->intro_key;
   crypto_pk_t *intro_key = circuit->intro_key;
@@ -2901,9 +3156,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
   tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
   tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
   tor_assert(circuit->cpath);
   tor_assert(circuit->cpath);
   tor_assert(circuit->build_state);
   tor_assert(circuit->build_state);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
-  tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+  assert_circ_anonymity_ok(circuit, get_options());
   tor_assert(circuit->rend_data);
   tor_assert(circuit->rend_data);
 
 
   /* Declare the circuit dirty to avoid reuse, and for path-bias */
   /* Declare the circuit dirty to avoid reuse, and for path-bias */
@@ -2923,6 +3176,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
            "Done building circuit %u to rendezvous with "
            "Done building circuit %u to rendezvous with "
            "cookie %s for service %s",
            "cookie %s for service %s",
            (unsigned)circuit->base_.n_circ_id, hexcookie, serviceid);
            (unsigned)circuit->base_.n_circ_id, hexcookie, serviceid);
+  circuit_log_path(LOG_INFO, LD_REND, circuit);
 
 
   /* Clear the 'in-progress HS circ has timed out' flag for
   /* Clear the 'in-progress HS circ has timed out' flag for
    * consistency with what happens on the client side; this line has
    * consistency with what happens on the client side; this line has
@@ -3489,6 +3743,9 @@ rend_consider_services_intro_points(void)
   int i;
   int i;
   time_t now;
   time_t now;
   const or_options_t *options = get_options();
   const or_options_t *options = get_options();
+  /* Are we in single onion mode? */
+  const int allow_direct = rend_service_allow_non_anonymous_connection(
+                                                                get_options());
   /* List of nodes we need to _exclude_ when choosing a new node to
   /* List of nodes we need to _exclude_ when choosing a new node to
    * establish an intro point to. */
    * establish an intro point to. */
   smartlist_t *exclude_nodes;
   smartlist_t *exclude_nodes;
@@ -3584,8 +3841,24 @@ rend_consider_services_intro_points(void)
       router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC;
       router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC;
       if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION)
       if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION)
         flags |= CRN_ALLOW_INVALID;
         flags |= CRN_ALLOW_INVALID;
+      router_crn_flags_t direct_flags = flags;
+      direct_flags |= CRN_PREF_ADDR;
+      direct_flags |= CRN_DIRECT_CONN;
+
       node = router_choose_random_node(exclude_nodes,
       node = router_choose_random_node(exclude_nodes,
-                                       options->ExcludeNodes, flags);
+                                       options->ExcludeNodes,
+                                       allow_direct ? direct_flags : flags);
+      /* If we are in single onion mode, retry node selection for a 3-hop
+       * path */
+      if (allow_direct && !node) {
+        log_info(LD_REND,
+                 "Unable to find an intro point that we can connect to "
+                 "directly for %s, falling back to a 3-hop path.",
+                 safe_str_client(service->service_id));
+        node = router_choose_random_node(exclude_nodes,
+                                         options->ExcludeNodes, flags);
+      }
+
       if (!node) {
       if (!node) {
         log_warn(LD_REND,
         log_warn(LD_REND,
                  "We only have %d introduction points established for %s; "
                  "We only have %d introduction points established for %s; "
@@ -3595,10 +3868,13 @@ rend_consider_services_intro_points(void)
                  n_intro_points_to_open);
                  n_intro_points_to_open);
         break;
         break;
       }
       }
-      /* Add the choosen node to the exclusion list in order to avoid to
-       * pick it again in the next iteration. */
+      /* Add the choosen node to the exclusion list in order to avoid picking
+       * it again in the next iteration. */
       smartlist_add(exclude_nodes, (void*)node);
       smartlist_add(exclude_nodes, (void*)node);
       intro = tor_malloc_zero(sizeof(rend_intro_point_t));
       intro = tor_malloc_zero(sizeof(rend_intro_point_t));
+      /* extend_info is for clients, so we want the multi-hop primary ORPort,
+       * even if we are a single onion service and intend to connect to it
+       * directly ourselves. */
       intro->extend_info = extend_info_from_node(node, 0);
       intro->extend_info = extend_info_from_node(node, 0);
       intro->intro_key = crypto_pk_new();
       intro->intro_key = crypto_pk_new();
       const int fail = crypto_pk_generate_key(intro->intro_key);
       const int fail = crypto_pk_generate_key(intro->intro_key);
@@ -3644,8 +3920,9 @@ rend_consider_services_upload(time_t now)
 {
 {
   int i;
   int i;
   rend_service_t *service;
   rend_service_t *service;
-  int rendpostperiod = get_options()->RendPostPeriod;
-  int rendinitialpostdelay = (get_options()->TestingTorNetwork ?
+  const or_options_t *options = get_options();
+  int rendpostperiod = options->RendPostPeriod;
+  int rendinitialpostdelay = (options->TestingTorNetwork ?
                               MIN_REND_INITIAL_POST_DELAY_TESTING :
                               MIN_REND_INITIAL_POST_DELAY_TESTING :
                               MIN_REND_INITIAL_POST_DELAY);
                               MIN_REND_INITIAL_POST_DELAY);
 
 
@@ -3656,6 +3933,12 @@ rend_consider_services_upload(time_t now)
        * the descriptor is stable before being published. See comment below. */
        * the descriptor is stable before being published. See comment below. */
       service->next_upload_time =
       service->next_upload_time =
         now + rendinitialpostdelay + crypto_rand_int(2*rendpostperiod);
         now + rendinitialpostdelay + crypto_rand_int(2*rendpostperiod);
+      /* Single Onion Services prioritise availability over hiding their
+       * startup time, as their IP address is publicly discoverable anyway.
+       */
+      if (rend_service_reveal_startup_time(options)) {
+        service->next_upload_time = now + rendinitialpostdelay;
+      }
     }
     }
     /* Does every introduction points have been established? */
     /* Does every introduction points have been established? */
     unsigned int intro_points_ready =
     unsigned int intro_points_ready =
@@ -3896,12 +4179,51 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
     return -2;
     return -2;
 }
 }
 
 
-/* Stub that should be replaced with the #17178 version of the function
- * when merging. */
+/* Are HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode consistent?
+ */
+static int
+rend_service_non_anonymous_mode_consistent(const or_options_t *options)
+{
+  /* !! is used to make these options boolean */
+  return (!! options->HiddenServiceSingleHopMode ==
+          !! options->HiddenServiceNonAnonymousMode);
+}
+
+/* Do the options allow onion services to make direct (non-anonymous)
+ * connections to introduction or rendezvous points?
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ * Returns true if tor is in HiddenServiceSingleHopMode. */
 int
 int
-rend_service_allow_direct_connection(const or_options_t *options)
+rend_service_allow_non_anonymous_connection(const or_options_t *options)
 {
 {
-  (void)options;
-  return 0;
+  tor_assert(rend_service_non_anonymous_mode_consistent(options));
+  return options->HiddenServiceSingleHopMode ? 1 : 0;
+}
+
+/* Do the options allow us to reveal the exact startup time of the onion
+ * service?
+ * Single Onion Services prioritise availability over hiding their
+ * startup time, as their IP address is publicly discoverable anyway.
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ * Returns true if tor is in non-anonymous hidden service mode. */
+int
+rend_service_reveal_startup_time(const or_options_t *options)
+{
+  tor_assert(rend_service_non_anonymous_mode_consistent(options));
+  return rend_service_non_anonymous_mode_enabled(options);
+}
+
+/* Is non-anonymous mode enabled using the HiddenServiceNonAnonymousMode
+ * config option?
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ */
+int
+rend_service_non_anonymous_mode_enabled(const or_options_t *options)
+{
+  tor_assert(rend_service_non_anonymous_mode_consistent(options));
+  return options->HiddenServiceNonAnonymousMode ? 1 : 0;
 }
 }
 
 

+ 66 - 2
src/or/rendservice.h

@@ -63,11 +63,68 @@ struct rend_intro_cell_s {
   uint8_t dh[DH_KEY_LEN];
   uint8_t dh[DH_KEY_LEN];
 };
 };
 
 
+/** Represents a single hidden service running at this OP. */
+typedef struct rend_service_t {
+  /* Fields specified in config file */
+  char *directory; /**< where in the filesystem it stores it. Will be NULL if
+                    * this service is ephemeral. */
+  int dir_group_readable; /**< if 1, allow group read
+                             permissions on directory */
+  smartlist_t *ports; /**< List of rend_service_port_config_t */
+  rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
+                               * authorization is performed. */
+  smartlist_t *clients; /**< List of rend_authorized_client_t's of
+                         * clients that may access our service. Can be NULL
+                         * if no client authorization is performed. */
+  /* Other fields */
+  crypto_pk_t *private_key; /**< Permanent hidden-service key. */
+  char service_id[REND_SERVICE_ID_LEN_BASE32+1]; /**< Onion address without
+                                                  * '.onion' */
+  char pk_digest[DIGEST_LEN]; /**< Hash of permanent hidden-service key. */
+  smartlist_t *intro_nodes; /**< List of rend_intro_point_t's we have,
+                             * or are trying to establish. */
+  /** List of rend_intro_point_t that are expiring. They are removed once
+   * the new descriptor is successfully uploaded. A node in this list CAN
+   * NOT appear in the intro_nodes list. */
+  smartlist_t *expiring_nodes;
+  time_t intro_period_started; /**< Start of the current period to build
+                                * introduction points. */
+  int n_intro_circuits_launched; /**< Count of intro circuits we have
+                                  * established in this period. */
+  unsigned int n_intro_points_wanted; /**< Number of intro points this
+                                       * service wants to have open. */
+  rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
+  time_t desc_is_dirty; /**< Time at which changes to the hidden service
+                         * descriptor content occurred, or 0 if it's
+                         * up-to-date. */
+  time_t next_upload_time; /**< Scheduled next hidden service descriptor
+                            * upload time. */
+  /** Replay cache for Diffie-Hellman values of INTRODUCE2 cells, to
+   * detect repeats.  Clients may send INTRODUCE1 cells for the same
+   * rendezvous point through two or more different introduction points;
+   * when they do, this keeps us from launching multiple simultaneous attempts
+   * to connect to the same rend point. */
+  replaycache_t *accepted_intro_dh_parts;
+  /** If true, we don't close circuits for making requests to unsupported
+   * ports. */
+  int allow_unknown_ports;
+  /** The maximum number of simultanious streams-per-circuit that are allowed
+   * to be established, or 0 if no limit is set.
+   */
+  int max_streams_per_circuit;
+  /** If true, we close circuits that exceed the max_streams_per_circuit
+   * limit.  */
+  int max_streams_close_circuit;
+} rend_service_t;
+
+STATIC void rend_service_free(rend_service_t *service);
+STATIC char *rend_service_sos_poison_path(const rend_service_t *service);
+
 #endif
 #endif
 
 
 int num_rend_services(void);
 int num_rend_services(void);
 int rend_config_services(const or_options_t *options, int validate_only);
 int rend_config_services(const or_options_t *options, int validate_only);
-int rend_service_load_all_keys(void);
+int rend_service_load_all_keys(const smartlist_t *service_list);
 void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
 void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
                                           smartlist_t *stat_lst);
                                           smartlist_t *stat_lst);
 void rend_consider_services_intro_points(void);
 void rend_consider_services_intro_points(void);
@@ -108,6 +165,11 @@ void rend_service_port_config_free(rend_service_port_config_t *p);
 
 
 void rend_authorized_client_free(rend_authorized_client_t *client);
 void rend_authorized_client_free(rend_authorized_client_t *client);
 
 
+int rend_service_list_verify_single_onion_poison(
+                                              const smartlist_t *service_list,
+                                              const or_options_t *options);
+int rend_service_poison_new_single_onion_dirs(const smartlist_t *service_list);
+
 /** Return value from rend_service_add_ephemeral. */
 /** Return value from rend_service_add_ephemeral. */
 typedef enum {
 typedef enum {
   RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */
   RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */
@@ -131,7 +193,9 @@ void directory_post_to_hs_dir(rend_service_descriptor_t *renddesc,
                               const char *service_id, int seconds_valid);
                               const char *service_id, int seconds_valid);
 void rend_service_desc_has_uploaded(const rend_data_t *rend_data);
 void rend_service_desc_has_uploaded(const rend_data_t *rend_data);
 
 
-int rend_service_allow_direct_connection(const or_options_t *options);
+int rend_service_allow_non_anonymous_connection(const or_options_t *options);
+int rend_service_reveal_startup_time(const or_options_t *options);
+int rend_service_non_anonymous_mode_enabled(const or_options_t *options);
 
 
 #endif
 #endif
 
 

+ 207 - 0
src/test/test_hs.c

@@ -8,12 +8,14 @@
 
 
 #define CONTROL_PRIVATE
 #define CONTROL_PRIVATE
 #define CIRCUITBUILD_PRIVATE
 #define CIRCUITBUILD_PRIVATE
+#define RENDSERVICE_PRIVATE
 
 
 #include "or.h"
 #include "or.h"
 #include "test.h"
 #include "test.h"
 #include "control.h"
 #include "control.h"
 #include "config.h"
 #include "config.h"
 #include "rendcommon.h"
 #include "rendcommon.h"
+#include "rendservice.h"
 #include "routerset.h"
 #include "routerset.h"
 #include "circuitbuild.h"
 #include "circuitbuild.h"
 #include "test_helpers.h"
 #include "test_helpers.h"
@@ -496,6 +498,209 @@ test_hs_auth_cookies(void *arg)
   return;
   return;
 }
 }
 
 
+static int mock_get_options_calls = 0;
+static or_options_t *mock_options = NULL;
+
+static void
+reset_options(or_options_t *options, int *get_options_calls)
+{
+  memset(options, 0, sizeof(or_options_t));
+  options->TestingTorNetwork = 1;
+
+  *get_options_calls = 0;
+}
+
+static const or_options_t *
+mock_get_options(void)
+{
+  ++mock_get_options_calls;
+  tor_assert(mock_options);
+  return mock_options;
+}
+
+/* Test that single onion poisoning works. */
+static void
+test_single_onion_poisoning(void *arg)
+{
+  or_options_t opt;
+  mock_options = &opt;
+  reset_options(mock_options, &mock_get_options_calls);
+  MOCK(get_options, mock_get_options);
+
+  int ret = -1;
+  mock_options->DataDirectory = tor_strdup(get_fname("test_data_dir"));
+  rend_service_t *service_1 = tor_malloc_zero(sizeof(rend_service_t));
+  char *dir1 = tor_strdup(get_fname("test_hs_dir1"));
+  rend_service_t *service_2 = tor_malloc_zero(sizeof(rend_service_t));
+  char *dir2 = tor_strdup(get_fname("test_hs_dir2"));
+  smartlist_t *services = smartlist_new();
+
+  (void) arg;
+
+  /* No services, no problem! */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Either way, no problem. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Create directories for both services */
+
+#ifdef _WIN32
+  ret = mkdir(mock_options->DataDirectory);
+  tt_assert(ret == 0);
+  ret = mkdir(dir1);
+  tt_assert(ret == 0);
+  ret = mkdir(dir2);
+#else
+  ret = mkdir(mock_options->DataDirectory, 0700);
+  tt_assert(ret == 0);
+  ret = mkdir(dir1, 0700);
+  tt_assert(ret == 0);
+  ret = mkdir(dir2, 0700);
+#endif
+  tt_assert(ret == 0);
+
+  service_1->directory = dir1;
+  service_2->directory = dir2;
+  smartlist_add(services, service_1);
+  /* But don't add the second service yet. */
+
+  /* Service directories, but no previous keys, no problem! */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Either way, no problem. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Poison! Poison! Poison!
+   * This can only be done in HiddenServiceSingleHopMode. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_poison_new_single_onion_dirs(services);
+  tt_assert(ret == 0);
+  /* Poisoning twice is a no-op. */
+  ret = rend_service_poison_new_single_onion_dirs(services);
+  tt_assert(ret == 0);
+
+  /* Poisoned service directories, but no previous keys, no problem! */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Either way, no problem. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Now add some keys, and we'll have a problem. */
+  ret = rend_service_load_all_keys(services);
+  tt_assert(ret == 0);
+
+  /* Poisoned service directories with previous keys are not allowed. */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret < 0);
+
+  /* But they are allowed if we're in non-anonymous mode. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Re-poisoning directories with existing keys is a no-op, because
+   * directories with existing keys are ignored. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_poison_new_single_onion_dirs(services);
+  tt_assert(ret == 0);
+  /* And it keeps the poison. */
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Now add the second service: it has no key and no poison file */
+  smartlist_add(services, service_2);
+
+  /* A new service, and an existing poisoned service. Not ok. */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret < 0);
+
+  /* But ok to add in non-anonymous mode. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Now remove the poisoning from the first service, and we have the opposite
+   * problem. */
+  char *poison_path = rend_service_sos_poison_path(service_1);
+  ret = unlink(poison_path);
+  tor_free(poison_path);
+  tt_assert(ret == 0);
+
+  /* Unpoisoned service directories with previous keys are ok, as are empty
+   * directories. */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* But the existing unpoisoned key is not ok in non-anonymous mode, even if
+   * there is an empty service. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret < 0);
+
+  /* Poisoning directories with existing keys is a no-op, because directories
+   * with existing keys are ignored. But the new directory should poison. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_poison_new_single_onion_dirs(services);
+  tt_assert(ret == 0);
+  /* And the old directory remains unpoisoned. */
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret < 0);
+
+  /* And the new directory should be ignored, because it has no key. */
+  mock_options->HiddenServiceSingleHopMode = 0;
+  mock_options->HiddenServiceNonAnonymousMode = 0;
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret == 0);
+
+  /* Re-poisoning directories without existing keys is a no-op. */
+  mock_options->HiddenServiceSingleHopMode = 1;
+  mock_options->HiddenServiceNonAnonymousMode = 1;
+  ret = rend_service_poison_new_single_onion_dirs(services);
+  tt_assert(ret == 0);
+  /* And the old directory remains unpoisoned. */
+  ret = rend_service_list_verify_single_onion_poison(services, mock_options);
+  tt_assert(ret < 0);
+
+ done:
+  /* TODO: should we delete the directories here? */
+  rend_service_free(service_1);
+  rend_service_free(service_2);
+  smartlist_free(services);
+  UNMOCK(get_options);
+  tor_free(mock_options->DataDirectory);
+}
+
 struct testcase_t hs_tests[] = {
 struct testcase_t hs_tests[] = {
   { "hs_rend_data", test_hs_rend_data, TT_FORK,
   { "hs_rend_data", test_hs_rend_data, TT_FORK,
     NULL, NULL },
     NULL, NULL },
@@ -508,6 +713,8 @@ struct testcase_t hs_tests[] = {
     NULL, NULL },
     NULL, NULL },
   { "hs_auth_cookies", test_hs_auth_cookies, TT_FORK,
   { "hs_auth_cookies", test_hs_auth_cookies, TT_FORK,
     NULL, NULL },
     NULL, NULL },
+  { "single_onion_poisoning", test_single_onion_poisoning, TT_FORK,
+    NULL, NULL },
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 149 - 0
src/test/test_options.c

@@ -2749,6 +2749,154 @@ test_options_validate__rend(void *ignored)
   tor_free(msg);
   tor_free(msg);
 }
 }
 
 
+static void
+test_options_validate__single_onion(void *ignored)
+{
+  (void)ignored;
+  int ret;
+  char *msg;
+  options_test_data_t *tdata = NULL;
+  int previous_log = setup_capture_of_logs(LOG_WARN);
+
+  /* Test that HiddenServiceSingleHopMode must come with
+   * HiddenServiceNonAnonymousMode */
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 0\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, -1);
+  tt_str_op(msg, OP_EQ, "HiddenServiceSingleHopMode does not provide any "
+            "server anonymity. It must be used with "
+            "HiddenServiceNonAnonymousMode set to 1.");
+  tor_free(msg);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 0\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                "HiddenServiceNonAnonymousMode 0\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, -1);
+  tt_str_op(msg, OP_EQ, "HiddenServiceSingleHopMode does not provide any "
+            "server anonymity. It must be used with "
+            "HiddenServiceNonAnonymousMode set to 1.");
+  tor_free(msg);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 0\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                "HiddenServiceNonAnonymousMode 1\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+  free_options_test_data(tdata);
+
+  /* Test that SOCKSPort must come with Tor2webMode if
+   * HiddenServiceSingleHopMode is 1 */
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 5000\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                "HiddenServiceNonAnonymousMode 1\n"
+                                "Tor2webMode 0\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, -1);
+  tt_str_op(msg, OP_EQ, "HiddenServiceNonAnonymousMode is incompatible with "
+            "using Tor as an anonymous client. Please set "
+            "Socks/Trans/NATD/DNSPort to 0, or HiddenServiceNonAnonymousMode "
+            "to 0, or use the non-anonymous Tor2webMode.");
+  tor_free(msg);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 0\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                "HiddenServiceNonAnonymousMode 1\n"
+                                "Tor2webMode 0\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 5000\n"
+                                "HiddenServiceSingleHopMode 0\n"
+                                "Tor2webMode 0\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                                "SOCKSPort 5000\n"
+                                "HiddenServiceSingleHopMode 1\n"
+                                "HiddenServiceNonAnonymousMode 1\n"
+                                "Tor2webMode 1\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+  free_options_test_data(tdata);
+
+  /* Test that a hidden service can't be run with Tor2web
+   * Use HiddenServiceNonAnonymousMode instead of Tor2webMode, because
+   * Tor2webMode requires a compilation #define */
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                  "HiddenServiceNonAnonymousMode 1\n"
+                  "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n"
+                  "HiddenServicePort 80 127.0.0.1:8080\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, -1);
+  tt_str_op(msg, OP_EQ, "HiddenServiceNonAnonymousMode does not provide any "
+            "server anonymity. It must be used with "
+            "HiddenServiceSingleHopMode set to 1.");
+  tor_free(msg);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                  "HiddenServiceNonAnonymousMode 1\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, -1);
+  tt_str_op(msg, OP_EQ, "HiddenServiceNonAnonymousMode does not provide any "
+            "server anonymity. It must be used with "
+            "HiddenServiceSingleHopMode set to 1.");
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                  "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n"
+                  "HiddenServicePort 80 127.0.0.1:8080\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+  free_options_test_data(tdata);
+
+  tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+                  "HiddenServiceNonAnonymousMode 1\n"
+                  "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n"
+                  "HiddenServicePort 80 127.0.0.1:8080\n"
+                  "HiddenServiceSingleHopMode 1\n"
+                  "SOCKSPort 0\n"
+                                );
+  ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_ptr_op(msg, OP_EQ, NULL);
+
+ done:
+  policies_free_all();
+  teardown_capture_of_logs(previous_log);
+  free_options_test_data(tdata);
+  tor_free(msg);
+}
+
 static void
 static void
 test_options_validate__accounting(void *ignored)
 test_options_validate__accounting(void *ignored)
 {
 {
@@ -4371,6 +4519,7 @@ struct testcase_t options_tests[] = {
   LOCAL_VALIDATE_TEST(port_forwarding),
   LOCAL_VALIDATE_TEST(port_forwarding),
   LOCAL_VALIDATE_TEST(tor2web),
   LOCAL_VALIDATE_TEST(tor2web),
   LOCAL_VALIDATE_TEST(rend),
   LOCAL_VALIDATE_TEST(rend),
+  LOCAL_VALIDATE_TEST(single_onion),
   LOCAL_VALIDATE_TEST(accounting),
   LOCAL_VALIDATE_TEST(accounting),
   LOCAL_VALIDATE_TEST(proxy),
   LOCAL_VALIDATE_TEST(proxy),
   LOCAL_VALIDATE_TEST(control),
   LOCAL_VALIDATE_TEST(control),