Browse Source

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

Nick Mathewson 7 years ago
parent
commit
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
     to stick with them. This is desirable because constantly changing servers
     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**::
     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
     running a tor2web Hidden Service web proxy.
     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__,__...__::
     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
     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
 -----------------------
 

+ 27 - 5
src/or/circuitbuild.c

@@ -28,6 +28,7 @@
 #include "connection_edge.h"
 #include "connection_or.h"
 #include "control.h"
+#include "crypto.h"
 #include "directory.h"
 #include "entrynodes.h"
 #include "main.h"
@@ -38,14 +39,14 @@
 #include "onion_tap.h"
 #include "onion_fast.h"
 #include "policies.h"
-#include "transports.h"
 #include "relay.h"
+#include "rendcommon.h"
 #include "rephist.h"
 #include "router.h"
 #include "routerlist.h"
 #include "routerparse.h"
 #include "routerset.h"
-#include "crypto.h"
+#include "transports.h"
 
 static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
                                                uint16_t port,
@@ -1859,13 +1860,32 @@ pick_rendezvous_node(router_crn_flags_t flags)
     flags |= CRN_ALLOW_INVALID;
 
 #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. */
   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) {
       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
 
@@ -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;
 
   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;
   } else {
     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 "main.h"
 #include "networkstatus.h"
+#include "rendclient.h"
+#include "rendservice.h"
 #include "statefile.h"
 
 #undef log
@@ -81,12 +83,14 @@ get_circuit_build_timeout_ms(void)
 
 /**
  * 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.
  *  2. If the torrc option LearnCircuitBuildTimeout is false.
  *  3. If we are a directory authority
  *  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
 circuit_build_times_disabled(void)
@@ -94,14 +98,30 @@ circuit_build_times_disabled(void)
   if (unit_tests) {
     return 0;
   } else {
+    const or_options_t *options = get_options();
     int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
                                                      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;
+    /* 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 ||
-           state_disabled) {
+        state_disabled || tor2web_disabled || single_onion_disabled) {
 #if 0
       log_debug(LD_CIRC,
                "CircuitBuildTime learning is disabled. "
@@ -469,7 +489,7 @@ circuit_build_times_get_initial_timeout(void)
    */
   if (!unit_tests && get_options()->CircuitBuildTimeout) {
     timeout = get_options()->CircuitBuildTimeout*1000;
-    if (get_options()->LearnCircuitBuildTimeout &&
+    if (!circuit_build_times_disabled() &&
         timeout < circuit_build_times_min_timeout()) {
       log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
                circuit_build_times_min_timeout()/1000);

+ 175 - 46
src/or/config.c

@@ -18,6 +18,7 @@
 #include "circuitlist.h"
 #include "circuitmux.h"
 #include "circuitmux_ewma.h"
+#include "circuitstats.h"
 #include "config.h"
 #include "connection.h"
 #include "connection_edge.h"
@@ -297,6 +298,8 @@ static config_var_t option_vars_[] = {
   V(HidServAuth,                 LINELIST, NULL),
   V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
   V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
+  V(HiddenServiceSingleHopMode,  BOOL,     "0"),
+  V(HiddenServiceNonAnonymousMode,BOOL,    "0"),
   V(HTTPProxy,                   STRING,   NULL),
   V(HTTPProxyAuthenticator,      STRING,   NULL),
   V(HTTPSProxy,                  STRING,   NULL),
@@ -434,7 +437,7 @@ static config_var_t option_vars_[] = {
   OBSOLETE("TunnelDirConns"),
   V(UpdateBridgesFromAuthority,  BOOL,     "0"),
   V(UseBridges,                  BOOL,     "0"),
-  V(UseEntryGuards,              BOOL,     "1"),
+  VAR("UseEntryGuards",          BOOL,     UseEntryGuards_option, "1"),
   V(UseEntryGuardsAsDirGuards,   BOOL,     "1"),
   V(UseGuardFraction,            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)
     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
 /* LCOV_EXCL_START */
@@ -1723,8 +1726,27 @@ options_act(const or_options_t *old_options)
 
   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. */
-  if (rend_service_load_all_keys()<0) {
+  if (rend_service_load_all_keys(NULL)<0) {
     log_warn(LD_GENERAL,"Error loading rendezvous service keys");
     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
  * permissible transition from <b>old_options</b>, and none of the
  * 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);
   *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);
 
   if (server_mode(options) &&
@@ -3197,10 +3305,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->UseBridges && options->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 =
     compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
                                    server_mode(options));
@@ -3291,25 +3395,11 @@ options_validate(or_options_t *old_options, or_options_t *options,
     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) {
     /* tor2web mode clients do not (and should not) use entry guards
      * 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.");
   }
 
+  if (options->EntryNodes && !options->UseEntryGuards) {
+    REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
+  }
+
   if (!(options->UseEntryGuards) &&
-      (options->RendConfigLines != NULL)) {
+      (options->RendConfigLines != NULL) &&
+      !rend_service_allow_non_anonymous_connection(options)) {
     log_warn(LD_CONFIG,
              "UseEntryGuards is disabled, but you have configured one or more "
              "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;
   }
 
+  /* 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 &&
       options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) {
     log_warn(LD_CONFIG,
@@ -3364,8 +3470,15 @@ options_validate(or_options_t *old_options, or_options_t *options,
         RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT );
   } else if (!options->LearnCircuitBuildTimeout &&
              !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) {
@@ -4295,6 +4408,19 @@ options_transition_allowed(const or_options_t *old,
     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 &&
       !new_val->DisableDebuggerAttachment) {
     *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
- * <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
-count_real_listeners(const smartlist_t *ports, int listenertype)
+count_real_listeners(const smartlist_t *ports, int listenertype,
+                     int count_sockets)
 {
   int n = 0;
   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;
     if (port->type != listenertype)
       continue;
@@ -6793,9 +6922,8 @@ count_real_listeners(const smartlist_t *ports, int listenertype)
   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
  * 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
      an integer. */
   options->ORPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER, 0);
   options->SocksPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER, 1);
   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 =
-    !! 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 =
-    !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0);
   options->DirPort_set =
-    !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER, 0);
   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 =
-    !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER);
+    !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER, 0);
 
   if (world_writable_control_socket) {
     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 void port_cfg_free(port_cfg_t *port);
 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,
                             or_options_t *options,
                             or_options_t *default_options,

+ 26 - 3
src/or/connection_edge.c

@@ -27,6 +27,7 @@
 #include "control.h"
 #include "dns.h"
 #include "dnsserv.h"
+#include "directory.h"
 #include "dirserv.h"
 #include "hibernate.h"
 #include "main.h"
@@ -2326,6 +2327,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
   char payload[CELL_PAYLOAD_SIZE];
   int payload_len;
   int begin_type;
+  const or_options_t *options = get_options();
   origin_circuit_t *circ;
   edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_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 ?
                  RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN;
+
+  /* Check that circuits are anonymised, based on their type. */
   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,

+ 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_close_circuit = 0;
   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++) {
     static const char *port_prefix = "Port=";
     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
        *                                exceeded.
        *   * '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 *detach_flag = "Detach";
       static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
       static const char *basicauth_flag = "BasicAuth";
+      static const char *non_anonymous_flag = "NonAnonymous";
 
       smartlist_t *flags = smartlist_new();
       int bad = 0;
@@ -4310,6 +4317,8 @@ handle_control_add_onion(control_connection_t *conn,
           max_streams_close_circuit = 1;
         } else if (!strcasecmp(flag, basicauth_flag)) {
           auth_type = REND_BASIC_AUTH;
+        } else if (!strcasecmp(flag, non_anonymous_flag)) {
+          non_anonymous = 1;
         } else {
           connection_printf_to_buf(conn,
                                    "512 Invalid 'Flags' argument: %s\r\n",
@@ -4378,6 +4387,19 @@ handle_control_add_onion(control_connection_t *conn,
               smartlist_len(auth_clients) > 16)) {
     connection_printf_to_buf(conn, "512 Too many auth clients\r\n");
     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. */

+ 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
  * instance's client activities.  (Such connections must be performed
  * through normal three-hop Tor circuits.) */
-static int
+int
 is_sensitive_dir_purpose(uint8_t dir_purpose)
 {
   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));
 
-#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
    * 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);
 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 is_sensitive_dir_purpose(uint8_t dir_purpose);
 
 #ifdef TOR_UNIT_TESTS
 /* 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.");
   }
 
-#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) {
     log_err(LD_BUG,"Error initializing network; exiting.");
     return -1;
@@ -2849,15 +2844,18 @@ tor_init(int argc, char *argv[])
     return -1;
   }
 
+  /* The options are now initialised */
+  const or_options_t *options = get_options();
+
 #ifndef _WIN32
   if (geteuid()==0)
     log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, "
              "and you probably shouldn't.");
 #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.");
     return -1;
   }

+ 41 - 7
src/or/or.h

@@ -3606,9 +3606,13 @@ typedef struct {
 
   /** @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. */
   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_; /**< Maximum allowed number of simultaneous connections. */
   int ConnLimit_high_thresh; /**< start trying to lower socket usage if we
@@ -3754,7 +3778,8 @@ typedef struct {
                      * unattached before we fail it? */
   int LearnCircuitBuildTimeout; /**< If non-zero, we attempt to learn a value
                                  * 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
                             * least this many seconds ago. Used until
                             * adaptive algorithm learns a new value. */
@@ -3940,8 +3965,16 @@ typedef struct {
   int TokenBucketRefillInterval;
   char *AccelName; /**< Optional hardware acceleration engine name. */
   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 UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
                                   * 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
  * client and service side). */
 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
                                * 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,
                               origin_circuit_t *rendcirc)
 {
+  const or_options_t *options = get_options();
   size_t payload_len;
   int r, v3_shift = 0;
   char payload[RELAY_PAYLOAD_SIZE];
@@ -150,10 +151,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
   tor_assert(rendcirc->rend_data);
   tor_assert(!rend_cmp_service_ids(introcirc->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,
                               &entry);
@@ -387,6 +386,7 @@ int
 rend_client_introduction_acked(origin_circuit_t *circ,
                                const uint8_t *request, size_t request_len)
 {
+  const or_options_t *options = get_options();
   origin_circuit_t *rendcirc;
   (void) request; // XXXX Use this.
 
@@ -398,10 +398,9 @@ rend_client_introduction_acked(origin_circuit_t *circ,
     return -1;
   }
 
+  tor_assert(circ->build_state);
   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);
 
   /* 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...");
     rendcirc = circuit_get_ready_rend_circ_by_rend_data(circ->rend_data);
     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_PURPOSE_C_REND_READY_INTRO_ACKED);
       /* Set timestamp_dirty, because circuit_expire_building expects
@@ -1527,3 +1524,35 @@ rend_parse_service_authorization(const or_options_t *options,
   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);
 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
 

+ 48 - 0
src/or/rendcommon.c

@@ -1067,3 +1067,51 @@ rend_auth_decode_cookie(const char *cookie_in, uint8_t *cookie_out,
   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,
                             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
 

+ 448 - 126
src/or/rendservice.c

@@ -20,6 +20,7 @@
 #include "main.h"
 #include "networkstatus.h"
 #include "nodelist.h"
+#include "policies.h"
 #include "rendclient.h"
 #include "rendcommon.h"
 #include "rendservice.h"
@@ -107,59 +108,13 @@ struct rend_service_port_config_s {
  * rendezvous point before giving up? */
 #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>.
  */
@@ -206,16 +161,18 @@ rend_authorized_client_strmap_item_free(void *authorized_client)
 
 /** Release the storage held by <b>service</b>.
  */
-static void
+STATIC void
 rend_service_free(rend_service_t *service)
 {
   if (!service)
     return;
 
   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)
     crypto_pk_free(service->private_key);
   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
- * 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
-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)
       continue;
     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(s);
   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,
@@ -1076,7 +1253,7 @@ rend_service_derive_key_digests(struct rend_service_t *s)
 static int
 rend_service_load_keys(rend_service_t *s)
 {
-  char fname[512];
+  char *fname = NULL;
   char buf[128];
   cpd_check_t  check_opts = CPD_CREATE;
 
@@ -1085,7 +1262,7 @@ rend_service_load_keys(rend_service_t *s)
   }
   /* Check/create directory */
   if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) {
-    return -1;
+    goto err;
   }
 #ifndef _WIN32
   if (s->dir_group_readable) {
@@ -1097,34 +1274,23 @@ rend_service_load_keys(rend_service_t *s)
 #endif
 
   /* 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);
+
   if (!s->private_key)
-    return -1;
+    goto err;
 
   if (rend_service_derive_key_digests(s) < 0)
-    return -1;
+    goto err;
 
+  tor_free(fname);
   /* 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);
   if (write_str_to_file(fname,buf,0)<0) {
     log_warn(LD_CONFIG, "Could not write onion address to hostname file.");
-    memwipe(buf, 0, sizeof(buf));
-    return -1;
+    goto err;
   }
 #ifndef _WIN32
   if (s->dir_group_readable) {
@@ -1135,15 +1301,21 @@ rend_service_load_keys(rend_service_t *s)
   }
 #endif
 
-  memwipe(buf, 0, sizeof(buf));
-
   /* If client authorization is configured, load or generate keys. */
   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
@@ -1153,7 +1325,7 @@ static int
 rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
 {
   int r = 0;
-  char cfname[512];
+  char *cfname = NULL;
   char *client_keys_str = NULL;
   strmap_t *parsed_clients = strmap_new();
   FILE *cfile, *hfile;
@@ -1163,12 +1335,7 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
   char buf[1500];
 
   /* 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);
   if (client_keys_str) {
     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);
 
-  memwipe(cfname, 0, sizeof(cfname));
+  if (cfname) {
+    memwipe(cfname, 0, sizeof(cfname));
+    tor_free(cfname);
+  }
 
   /* Clear stack buffers that held key-derived material. */
   memwipe(buf, 0, sizeof(buf));
@@ -1424,6 +1594,31 @@ rend_check_authorization(rend_service_t *service,
   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
  ******/
@@ -1473,9 +1668,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
     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);
 
   /* 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++) {
     int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
     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(
                         CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
 
@@ -1791,7 +1989,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
       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 (err_msg_out) {
         tor_asprintf(&err_msg,
@@ -1816,6 +2017,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
     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
    * 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. */
@@ -2590,6 +2795,10 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
   log_info(LD_REND,"Reattempting rendezvous circuit to '%s'",
            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,
                             oldstate->chosen_exit,
                             CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL);
@@ -2618,26 +2827,72 @@ rend_service_launch_establish_intro(rend_service_t *service,
                                     rend_intro_point_t *intro)
 {
   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,
-           "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)),
+           direct_ei ? " via direct address " : "",
+           direct_ei ? safe_str_client(extend_info_describe(direct_ei)) : "",
            service->service_id);
 
   rep_hist_note_used_internal(time(NULL), 1, 0);
 
   ++service->n_intro_circuits_launched;
   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) {
     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;
   }
-  /* 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,
                        launched->build_state->chosen_exit->identity_digest,
                        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);
   if (launched->base_.state == CIRCUIT_STATE_OPEN)
     rend_service_intro_has_opened(launched);
+  extend_info_free(direct_ei);
   return 0;
 }
 
@@ -2703,9 +2959,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   int reason = END_CIRC_REASON_TORPROTOCOL;
 
   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->rend_data);
 
@@ -2772,6 +3026,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   log_info(LD_REND,
            "Established circuit %u as introduction point for service %s",
            (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. */
   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->cpath);
   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);
 
   /* 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 "
            "cookie %s for service %s",
            (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
    * consistency with what happens on the client side; this line has
@@ -3489,6 +3743,9 @@ rend_consider_services_intro_points(void)
   int i;
   time_t now;
   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
    * establish an intro point to. */
   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;
       if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION)
         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,
-                                       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) {
         log_warn(LD_REND,
                  "We only have %d introduction points established for %s; "
@@ -3595,10 +3868,13 @@ rend_consider_services_intro_points(void)
                  n_intro_points_to_open);
         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);
       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->intro_key = crypto_pk_new();
       const int fail = crypto_pk_generate_key(intro->intro_key);
@@ -3644,8 +3920,9 @@ rend_consider_services_upload(time_t now)
 {
   int i;
   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);
 
@@ -3656,6 +3933,12 @@ rend_consider_services_upload(time_t now)
        * the descriptor is stable before being published. See comment below. */
       service->next_upload_time =
         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? */
     unsigned int intro_points_ready =
@@ -3896,12 +4179,51 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
     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
-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];
 };
 
+/** 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
 
 int num_rend_services(void);
 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,
                                           smartlist_t *stat_lst);
 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);
 
+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. */
 typedef enum {
   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);
 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
 

+ 207 - 0
src/test/test_hs.c

@@ -8,12 +8,14 @@
 
 #define CONTROL_PRIVATE
 #define CIRCUITBUILD_PRIVATE
+#define RENDSERVICE_PRIVATE
 
 #include "or.h"
 #include "test.h"
 #include "control.h"
 #include "config.h"
 #include "rendcommon.h"
+#include "rendservice.h"
 #include "routerset.h"
 #include "circuitbuild.h"
 #include "test_helpers.h"
@@ -496,6 +498,209 @@ test_hs_auth_cookies(void *arg)
   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[] = {
   { "hs_rend_data", test_hs_rend_data, TT_FORK,
     NULL, NULL },
@@ -508,6 +713,8 @@ struct testcase_t hs_tests[] = {
     NULL, NULL },
   { "hs_auth_cookies", test_hs_auth_cookies, TT_FORK,
     NULL, NULL },
+  { "single_onion_poisoning", test_single_onion_poisoning, TT_FORK,
+    NULL, NULL },
   END_OF_TESTCASES
 };
 

+ 149 - 0
src/test/test_options.c

@@ -2749,6 +2749,154 @@ test_options_validate__rend(void *ignored)
   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
 test_options_validate__accounting(void *ignored)
 {
@@ -4371,6 +4519,7 @@ struct testcase_t options_tests[] = {
   LOCAL_VALIDATE_TEST(port_forwarding),
   LOCAL_VALIDATE_TEST(tor2web),
   LOCAL_VALIDATE_TEST(rend),
+  LOCAL_VALIDATE_TEST(single_onion),
   LOCAL_VALIDATE_TEST(accounting),
   LOCAL_VALIDATE_TEST(proxy),
   LOCAL_VALIDATE_TEST(control),