[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Fourth patch for proposal 121



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Nick,

this is the fourth and final patch to implement the basic functionality
of proposal 121. There are going to be minor tweaks requiring future
patches, but after this one things will work:

4. This patch uses authorization data on client side (that was
introduced with patch 2) to download and decrypt hidden service
descriptors and establish connections to services that perform
client authorization.

Nick Mathewson wrote:
> BTW, you _have_ tested this with 0 clients configured, and with over
> 16 clients configured, yes?

Yes, I have tested these cases before splitting the code into patches
and once more for this fourth patch that puts things back together.

- --Karsten
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFIrVBd0M+WPffBEmURAiyQAKDV2VBvEVT/kawewfbDulcbBpjemgCeIJye
4I5hz/VgBWnNkvdbuuG0vsg=
=j/Us
-----END PGP SIGNATURE-----
Index: /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in	(working copy)
@@ -472,6 +472,15 @@
 ReachableAddresses instead. (Default: 80, 443)
 .LP
 .TP
+\fBHidServAuth \fR\fIonion-address\fR \fIauth-cookie\fP \fIservice-name\fR 
+Client authorization for a hidden service. Valid onion addresses contain 16
+characters in a-z2-7 plus ".onion", and valid auth cookies contain 22
+characters in A-Za-z0-9+/. The service name is only used for internal
+purposes, e.g., for Tor controllers. This option may be used multiple times
+for different hidden services. If a hidden service uses authorization and
+this option is not set, the hidden service is not accessible.
+.LP
+.TP
 \fBReachableAddresses \fR\fIADDR\fP[\fB/\fP\fIMASK\fP][:\fIPORT\fP]...\fP
 A comma-separated list of IP addresses and ports that your firewall allows you
 to connect to. The format is as
@@ -1268,6 +1277,18 @@
 service. Possible version numbers are 0 and 2. (Default: 0, 2)
 .LP
 .TP
+\fBHiddenServiceAuthorizeClient \fR\fIauth-type\fR \fR\fIclient-name\fR,\fIclient-name\fR,\fI...\fP
+If configured, the hidden service is accessible for authorized clients
+only. The auth-type can either be 'basic' for a general-purpose
+authorization protocol or 'stealth' for a less scalable protocol that also
+hides service activity from unauthorized clients. Only clients that are
+listed here are authorized to access the hidden service. Valid client names
+are 1 to 19 characters long and only use characters in A-Za-z0-9+-_
+(no spaces). If this option is set, the hidden service is not accessible
+for clients without authorization any more. Generated authorization data
+can be found in the hostname file.
+.LP
+.TP
 \fBRendPostPeriod \fR\fIN\fR \fBseconds\fR|\fBminutes\fR|\fBhours\fR|\fBdays\fR|\fBweeks\fP
 Every time the specified period elapses, Tor uploads any rendezvous
 service descriptors to the directory servers.  This information is also
@@ -1452,6 +1473,8 @@
 .TP
 .B \fIHiddenServiceDirectory\fP/hostname 
 The <base32-encoded-fingerprint>.onion domain name for this hidden service.
+If the hidden service is restricted to authorized clients only, this file
+also contains authorization data for all clients.
 .LP
 .TP
 .B \fIHiddenServiceDirectory\fP/private_key 
@@ -1456,6 +1479,11 @@
 .TP
 .B \fIHiddenServiceDirectory\fP/private_key 
 The private key for this hidden service.
+.LP
+.TP
+.B \fIHiddenServiceDirectory\fP/client_keys 
+Authorization data for a hidden service that is only accessible by authorized
+clients.
 .SH SEE ALSO
 .BR privoxy (1),
 .BR tsocks (1),
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c	(working copy)
@@ -2933,7 +2933,8 @@
                              1, bridge->identity,
                              DIR_PURPOSE_FETCH_SERVERDESC,
                              ROUTER_PURPOSE_BRIDGE,
-                             0, "authority.z", NULL, 0, 0);
+                             0, "authority.z", NULL, 0, 0,
+                             NULL, REND_NO_AUTH, NULL);
   tor_free(address);
 }
 
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c	(working copy)
@@ -1026,7 +1026,8 @@
              safe_str(ocirc->rend_query),
              safe_str(build_state_get_exit_nickname(ocirc->build_state)));
     rend_client_remove_intro_point(ocirc->build_state->chosen_exit,
-                                   ocirc->rend_query);
+                                   ocirc->rend_query, ocirc->rend_auth_type,
+                                   ocirc->rend_desc_cookie);
   }
   if (circ->n_conn)
     connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c	(working copy)
@@ -1056,8 +1056,14 @@
         log_info(LD_REND,
                  "No intro points for '%s': refetching service descriptor.",
                  safe_str(conn->rend_query));
-        rend_client_refetch_renddesc(conn->rend_query);
-        rend_client_refetch_v2_renddesc(conn->rend_query);
+        /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
+         * arrives first. Exception: When using client authorization, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_query,
+                                        conn->rend_auth_type,
+                                        conn->rend_desc_cookie);
+        if (conn->rend_auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_query);
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         return 0;
       }
@@ -1139,6 +1145,11 @@
       if (circ) {
         /* write the service_id into circ */
         strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+        if (conn->rend_auth_type != REND_NO_AUTH) {
+          memcpy(circ->rend_desc_cookie, conn->rend_desc_cookie,
+                 REND_DESC_COOKIE_LEN);
+          circ->rend_auth_type = conn->rend_auth_type;
+        }
         if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
             circ->_base.state == CIRCUIT_STATE_OPEN)
           rend_client_rendcirc_has_opened(circ);
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c	(working copy)
@@ -487,7 +487,9 @@
        if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
            dir_conn->rend_query &&
            strlen(dir_conn->rend_query) == REND_SERVICE_ID_LEN_BASE32)
-        rend_client_refetch_v2_renddesc(dir_conn->rend_query);
+        rend_client_refetch_v2_renddesc(dir_conn->rend_query,
+                                        dir_conn->rend_auth_type,
+                                        dir_conn->rend_desc_cookie);
       break;
     case CONN_TYPE_OR:
       or_conn = TO_OR_CONN(conn);
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c	(working copy)
@@ -1582,6 +1582,7 @@
     /* it's a hidden-service request */
     rend_cache_entry_t *entry;
     int r;
+    rend_service_authorization_t *client_auth;
     tor_assert(!automap);
     if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
       /* if it's a resolve request, fail it right now, rather than
@@ -1619,6 +1620,15 @@
      * a stable circuit yet, but we know we'll need *something*. */
     rep_hist_note_used_internal(now, 0, 1);
 
+    /* Look up if we have client authorization for it. */
+    client_auth = rend_client_lookup_service_authorization(conn->rend_query);
+    if (client_auth) {
+      log_info(LD_REND, "Using previously configured client authorization "
+                        "for hidden service request.");
+      memcpy(conn->rend_desc_cookie, client_auth->descriptor_cookie,
+             REND_DESC_COOKIE_LEN);
+      conn->rend_auth_type = client_auth->auth_type;
+    }
     if (r==0) {
       conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
@@ -1624,9 +1634,13 @@
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
                safe_str(conn->rend_query));
       /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-       * arrives first. */
-      rend_client_refetch_v2_renddesc(conn->rend_query);
-      rend_client_refetch_renddesc(conn->rend_query);
+       * arrives first. Exception: When using client authorization, only
+       * fetch v2 descriptors.*/
+      rend_client_refetch_v2_renddesc(conn->rend_query,
+                                      conn->rend_auth_type,
+                                      conn->rend_desc_cookie);
+      if (conn->rend_auth_type == REND_NO_AUTH)
+        rend_client_refetch_renddesc(conn->rend_query);
     } else { /* r > 0 */
 /** How long after we receive a hidden service descriptor do we consider
  * it valid? */
@@ -1644,9 +1658,13 @@
         log_info(LD_REND, "Stale descriptor %s. Refetching.",
                  safe_str(conn->rend_query));
         /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-         * arrives first. */
-        rend_client_refetch_v2_renddesc(conn->rend_query);
-        rend_client_refetch_renddesc(conn->rend_query);
+         * arrives first. Exception: When using client authorization, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_query,
+                                        conn->rend_auth_type,
+                                        conn->rend_desc_cookie);
+        if (conn->rend_auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_query);
       }
     }
     return 0;
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c	(working copy)
@@ -266,7 +266,8 @@
       directory_initiate_command_routerstatus(rs, dir_purpose,
                                               router_purpose,
                                               post_via_tor,
-                                              NULL, payload, upload_len, 0);
+                                              NULL, payload, upload_len, 0,
+                                              NULL, REND_NO_AUTH, NULL);
   } SMARTLIST_FOREACH_END(ds);
   if (!found) {
     char *s = authority_type_to_string(type);
@@ -353,7 +354,8 @@
                                    1, ri->cache_info.identity_digest,
                                    dir_purpose,
                                    router_purpose,
-                                   0, resource, NULL, 0, if_modified_since);
+                                   0, resource, NULL, 0, if_modified_since,
+                                   NULL, REND_NO_AUTH, NULL);
       } else
         log_notice(LD_DIR, "Ignoring directory request, since no bridge "
                            "nodes are available yet.");
@@ -396,7 +398,8 @@
                                             router_purpose,
                                             get_via_tor,
                                             resource, NULL, 0,
-                                            if_modified_since);
+                                            if_modified_since,
+                                            NULL, REND_NO_AUTH, NULL);
   else {
     log_notice(LD_DIR,
                "While fetching directory info, "
@@ -430,7 +433,8 @@
         continue;
       rs = &ds->fake_status;
       directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose,
-                                              0, resource, NULL, 0, 0);
+                                              0, resource, NULL, 0, 0,
+                                              NULL, REND_NO_AUTH, NULL);
     });
 }
 
@@ -456,7 +460,10 @@
                                         const char *resource,
                                         const char *payload,
                                         size_t payload_len,
-                                        time_t if_modified_since)
+                                        time_t if_modified_since,
+                                        const char *rend_query,
+                                        rend_auth_type_t auth_type,
+                                        const char *rend_desc_cookie)
 {
   routerinfo_t *router;
   char address_buf[INET_NTOA_BUF_LEN+1];
@@ -478,7 +485,8 @@
                              status->identity_digest,
                              dir_purpose, router_purpose,
                              anonymized_connection, resource,
-                             payload, payload_len, if_modified_since);
+                             payload, payload_len, if_modified_since,
+                             rend_query, auth_type, rend_desc_cookie);
 }
 
 /** Return true iff <b>conn</b> is the client side of a directory connection
@@ -661,7 +669,10 @@
                            uint8_t dir_purpose, uint8_t router_purpose,
                            int anonymized_connection, const char *resource,
                            const char *payload, size_t payload_len,
-                           time_t if_modified_since)
+                           time_t if_modified_since,
+                           const char *rend_query,
+                           rend_auth_type_t auth_type,
+                           const char *rend_desc_cookie)
 {
   dir_connection_t *conn;
   or_options_t *options = get_options();
@@ -700,6 +711,13 @@
   /* decide whether we can learn our IP address from this conn */
   conn->dirconn_direct = !anonymized_connection;
 
+  /* copy rendezvous data, if any */
+  if (rend_query)
+    strlcpy(conn->rend_query, rend_query, sizeof(conn->rend_query));
+  conn->rend_auth_type = auth_type;
+  if (rend_desc_cookie)
+    memcpy(conn->rend_desc_cookie, rend_desc_cookie, REND_DESC_COOKIE_LEN);
+
   if (!anonymized_connection && !use_begindir) {
     /* then we want to connect to dirport directly */
 
@@ -1012,10 +1030,8 @@
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
       tor_assert(resource);
       tor_assert(strlen(resource) <= REND_DESC_ID_V2_LEN_BASE32);
-      /* Remember the query to refer to it when a response arrives. */
-      strlcpy(conn->rend_query, payload, sizeof(conn->rend_query));
+      tor_assert(!payload);
       conn->rend_version = 2;
-      payload = NULL;
       httpcommand = "GET";
       len = strlen(resource) + 32;
       url = tor_malloc(len);
@@ -1912,7 +1928,8 @@
              (int)body_len, status_code, escaped(reason));
     switch (status_code) {
       case 200:
-        switch (rend_cache_store_v2_desc_as_client(body, NULL)) {
+        switch (rend_cache_store_v2_desc_as_client(body,
+                    conn->rend_auth_type, conn->rend_desc_cookie)) {
           case -2:
             log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
                      "Retrying at another directory.");
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c	(working copy)
@@ -1033,7 +1033,8 @@
                0, /* Not private */
                resource,
                NULL, 0 /* No payload. */,
-               0 /* No I-M-S. */);
+               0 /* No I-M-S. */,
+               NULL, REND_NO_AUTH, NULL);
       }
     SMARTLIST_FOREACH_END(ds);
   } else {
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/or.h
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/or.h	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/or.h	(working copy)
@@ -676,6 +676,18 @@
 /** Maximum length of authorized client names for a hidden service. */
 #define REND_CLIENTNAME_MAX_LEN 16
 
+/** Client authorization type that a hidden service performs. */
+typedef enum rend_auth_type_t {
+  REND_NO_AUTH      = 0,
+  REND_BASIC_AUTH   = 1,
+  REND_STEALTH_AUTH = 2,
+} rend_auth_type_t;
+
+/** Time interval for tracking possible replays of INTRODUCE2 cells.
+ * Incoming cells with timestamps half of this interval in the past or
+ * future are dropped immediately. */
+#define REND_REPLAY_TIME_INTERVAL (60 * 60)
+
 #define CELL_DIRECTION_IN 1
 #define CELL_DIRECTION_OUT 2
 
@@ -1043,6 +1055,15 @@
   /** What rendezvous service are we querying for? (AP only) */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** Client authorization type that is used for this hidden-service
+   * request, if any. */
+  rend_auth_type_t rend_auth_type;
+
   /** Number of times we've reassigned this application connection to
    * a new circuit. We keep track because the timeout is longer if we've
    * already retried several times. */
@@ -1100,6 +1121,15 @@
   /** What rendezvous service are we querying for? */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** Client authorization type that is used for this hidden-service
+   * request, if any. */
+  rend_auth_type_t rend_auth_type;
+
   char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
                                      * the directory server's signing key. */
 
@@ -1916,6 +1946,15 @@
    */
   char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
 
+  /** (Optional) descriptor cookie for requesting v2 hidden service
+   * descriptors, decrypting introduction points, and authenticating at
+   * hidden service. */
+  char rend_desc_cookie[REND_DESC_COOKIE_LEN];
+
+  /** Client authorization type that is used for this hidden-service
+   * request, if any. */
+  rend_auth_type_t rend_auth_type;
+
   /** Stores the rendezvous descriptor version if purpose is S_*. Used to
    * distinguish introduction and rendezvous points belonging to the same
    * rendezvous service ID, but different descriptor versions.
@@ -3181,7 +3220,10 @@
                                              const char *resource,
                                              const char *payload,
                                              size_t payload_len,
-                                             time_t if_modified_since);
+                                             time_t if_modified_since,
+                                             const char *rend_query,
+                                             rend_auth_type_t auth_type,
+                                             const char *rend_desc_cookie);
 
 int parse_http_response(const char *headers, int *code, time_t *date,
                         compress_method_t *compression, char **response);
@@ -3200,7 +3242,10 @@
                                 int anonymized_connection,
                                 const char *resource,
                                 const char *payload, size_t payload_len,
-                                time_t if_modified_since);
+                                time_t if_modified_since,
+                                const char *rend_query,
+                                rend_auth_type_t auth_type,
+                                const char *rend_desc_cookie);
 
 int dir_split_resource_into_fingerprints(const char *resource,
                                     smartlist_t *fp_out, int *compresseed_out,
@@ -3833,9 +3878,13 @@
 int rend_client_introduction_acked(origin_circuit_t *circ, const char *request,
                                    size_t request_len);
 void rend_client_refetch_renddesc(const char *query);
-void rend_client_refetch_v2_renddesc(const char *query);
+void rend_client_refetch_v2_renddesc(const char *query,
+                                     rend_auth_type_t auth_type,
+                                     const char *descriptor_cookie);
 int rend_client_remove_intro_point(extend_info_t *failed_intro,
-                                   const char *query);
+                                   const char *query,
+                                   rend_auth_type_t auth_type,
+                                   const char *descriptor_cookie);
 int rend_client_rendezvous_acked(origin_circuit_t *circ, const char *request,
                                  size_t request_len);
 int rend_client_receive_rendezvous(origin_circuit_t *circ, const char *request,
@@ -3847,13 +3896,6 @@
 int rend_client_send_introduction(origin_circuit_t *introcirc,
                                   origin_circuit_t *rendcirc);
 
-/** Client authorization type that a hidden service performs. */
-typedef enum rend_auth_type_t {
-  REND_NO_AUTH      = 0,
-  REND_BASIC_AUTH   = 1,
-  REND_STEALTH_AUTH = 2,
-} rend_auth_type_t;
-
 /** Client-side configuration of authorization for a hidden service. */
 typedef struct rend_service_authorization_t {
   char descriptor_cookie[REND_DESC_COOKIE_LEN];
@@ -3938,7 +3980,8 @@
 int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc);
 int rend_cache_store(const char *desc, size_t desc_len, int published);
 int rend_cache_store_v2_desc_as_client(const char *desc,
-                               const char *descriptor_cookie);
+                                       rend_auth_type_t auth_type,
+                                       const char *descriptor_cookie);
 int rend_cache_store_v2_desc_as_dir(const char *desc);
 int rend_cache_size(void);
 int rend_encode_v2_descriptors(smartlist_t *descs_out,
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c	(working copy)
@@ -58,7 +58,7 @@
                               origin_circuit_t *rendcirc)
 {
   size_t payload_len;
-  int r;
+  int r, v3_shift = 0;
   char payload[RELAY_PAYLOAD_SIZE];
   char tmp[RELAY_PAYLOAD_SIZE];
   rend_cache_entry_t *entry;
@@ -117,22 +117,39 @@
     }
   }
 
+  /* If version is 3, write (optional) auth data and timestamp. */
+  if (entry->parsed->protocols & (1<<3)) {
+    tmp[0] = 3; /* version 3 of the cell format */
+    tmp[1] = (uint8_t)introcirc->rend_auth_type; /* auth type, if any */
+    v3_shift = 1;
+    if (introcirc->rend_auth_type != REND_NO_AUTH) {
+      set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
+      memcpy(tmp+4, introcirc->rend_desc_cookie, REND_DESC_COOKIE_LEN);
+      v3_shift += 2+REND_DESC_COOKIE_LEN;
+    }
+    set_uint32(tmp+v3_shift+1, htonl(time(NULL)));
+    v3_shift += 4;
+  } /* if version 2 only write version number */
+  else if (entry->parsed->protocols & (1<<2)) {
+    tmp[0] = 2; /* version 2 of the cell format */
+  }
+
   /* write the remaining items into tmp */
-  if (entry->parsed->protocols & (1<<2)) {
+  if (entry->parsed->protocols & (1<<3) || entry->parsed->protocols & (1<<2)) {
     /* version 2 format */
     extend_info_t *extend_info = rendcirc->build_state->chosen_exit;
     int klen;
-    tmp[0] = 2; /* version 2 of the cell format */
     /* nul pads */
-    set_uint32(tmp+1, tor_addr_to_ipv4h(&extend_info->addr));
-    set_uint16(tmp+5, htons(extend_info->port));
-    memcpy(tmp+7, extend_info->identity_digest, DIGEST_LEN);
-    klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+7+DIGEST_LEN+2,
-                                 sizeof(tmp)-(7+DIGEST_LEN+2));
-    set_uint16(tmp+7+DIGEST_LEN, htons(klen));
-    memcpy(tmp+7+DIGEST_LEN+2+klen, rendcirc->rend_cookie,
+    set_uint32(tmp+v3_shift+1, tor_addr_to_ipv4h(&extend_info->addr));
+    set_uint16(tmp+v3_shift+5, htons(extend_info->port));
+    memcpy(tmp+v3_shift+7, extend_info->identity_digest, DIGEST_LEN);
+    klen = crypto_pk_asn1_encode(extend_info->onion_key,
+                                 tmp+v3_shift+7+DIGEST_LEN+2,
+                                 sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2));
+    set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen));
+    memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_cookie,
            REND_COOKIE_LEN);
-    dh_offset = 7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
+    dh_offset = v3_shift+7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
   } else {
     /* Version 0. */
     strncpy(tmp, rendcirc->build_state->chosen_exit->nickname,
@@ -241,7 +258,9 @@
      * If none remain, refetch the service descriptor.
      */
     if (rend_client_remove_intro_point(circ->build_state->chosen_exit,
-                                       circ->rend_query) > 0) {
+                                       circ->rend_query,
+                                       circ->rend_auth_type,
+                                       circ->rend_desc_cookie) > 0) {
       /* There are introduction points left. Re-extend the circuit to
        * another intro point and try again. */
       extend_info_t *extend_info;
@@ -337,7 +356,9 @@
  * descriptor, return 0, and in case of a failure -1. <b>query</b> is only
  * passed for pretty log statements. */
 static int
-directory_get_from_hs_dir(const char *desc_id, const char *query)
+directory_get_from_hs_dir(const char *desc_id, const char *query,
+                          rend_auth_type_t auth_type,
+                          const char *descriptor_cookie)
 {
   smartlist_t *responsible_dirs = smartlist_create();
   routerstatus_t *hs_dir;
@@ -343,6 +364,7 @@
   routerstatus_t *hs_dir;
   char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
   time_t now = time(NULL);
+  char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
   tor_assert(desc_id);
   tor_assert(query);
   tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32);
@@ -376,17 +398,33 @@
    * directory now. */
   lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
 
-  /* Send fetch request. (Pass query as payload to write it to the directory
-   * connection so that it can be referred to when the response arrives.) */
+  /* Encode descriptor cookie for logging purposes. */
+  if (descriptor_cookie &&
+      base64_encode(descriptor_cookie_base64, 3*REND_DESC_COOKIE_LEN_BASE64,
+                    descriptor_cookie, REND_DESC_COOKIE_LEN) < 0) {
+    log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
+    return 0;
+  }
+  /* Remove == signs and newline. */
+  descriptor_cookie_base64[strlen(descriptor_cookie_base64)-3] = '\0';
+
+  /* Send fetch request. (Pass query and possibly descriptor cookie so that
+   * they can be written to the directory connection and be referred to when
+   * the response arrives. */
   directory_initiate_command_routerstatus(hs_dir,
                                           DIR_PURPOSE_FETCH_RENDDESC_V2,
                                           ROUTER_PURPOSE_GENERAL,
-                                          1, desc_id_base32, query, 0, 0);
+                                          1, desc_id_base32, NULL, 0, 0,
+                                          query, auth_type,
+                                          descriptor_cookie);
   log_info(LD_REND, "Sending fetch request for v2 descriptor for "
-                    "service '%s' with descriptor ID '%s' to hidden "
-                    "service directory '%s' on port %d.",
-           safe_str(query), safe_str(desc_id_base32), hs_dir->nickname,
-           hs_dir->dir_port);
+                    "service '%s' with descriptor ID '%s', auth type %d, "
+                    "and descriptor cookie '%s' to hidden service "
+                    "directory '%s' on port %d.",
+           query, desc_id_base32, auth_type,
+           (descriptor_cookie == NULL ? "NULL" :
+           escaped_safe_str(descriptor_cookie_base64)),
+           hs_dir->nickname, hs_dir->dir_port);
   return 1;
 }
 
@@ -416,7 +454,9 @@
  * <b>query</b>.
  */
 void
-rend_client_refetch_v2_renddesc(const char *query)
+rend_client_refetch_v2_renddesc(const char *query,
+                                rend_auth_type_t auth_type,
+                                const char *descriptor_cookie)
 {
   char descriptor_id[DIGEST_LEN];
   int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
@@ -448,8 +488,10 @@
     int chosen_replica = replicas_left_to_try[rand];
     replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
 
-    if (rend_compute_v2_desc_id(descriptor_id, query, NULL, time(NULL),
-                                chosen_replica) < 0) {
+    if (rend_compute_v2_desc_id(descriptor_id, query,
+                                auth_type == REND_STEALTH_AUTH ?
+                                    descriptor_cookie : NULL,
+                                time(NULL), chosen_replica) < 0) {
       log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
                         "descriptor ID did not succeed.");
       return;
@@ -454,7 +496,8 @@
                         "descriptor ID did not succeed.");
       return;
     }
-    if (directory_get_from_hs_dir(descriptor_id, query) != 0)
+    if (directory_get_from_hs_dir(descriptor_id, query, auth_type,
+                                  descriptor_cookie) != 0)
       return; /* either success or failure, but we're done */
   }
   /* If we come here, there are no hidden service directories left. */
@@ -471,7 +514,9 @@
  * unrecognized, 1 if recognized and some intro points remain.
  */
 int
-rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query)
+rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query,
+                               rend_auth_type_t auth_type,
+                               const char *descriptor_cookie)
 {
   int i, r;
   rend_cache_entry_t *ent;
@@ -486,9 +531,11 @@
     log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
              escaped_safe_str(query));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using client authorization, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(query, auth_type, descriptor_cookie);
+    if (auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(query);
     return 0;
   }
 
@@ -507,9 +554,11 @@
              "No more intro points remain for %s. Re-fetching descriptor.",
              escaped_safe_str(query));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using client authorization, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(query, auth_type, descriptor_cookie);
+    if (auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(query);
 
     /* move all pending streams back to renddesc_wait */
     while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c	(working copy)
@@ -1236,6 +1236,7 @@
  */
 int
 rend_cache_store_v2_desc_as_client(const char *desc,
+                                   rend_auth_type_t auth_type,
                                    const char *descriptor_cookie)
 {
   /*XXXX this seems to have a bit of duplicate code with
@@ -1265,7 +1266,6 @@
   rend_cache_entry_t *e;
   tor_assert(rend_cache);
   tor_assert(desc);
-  (void) descriptor_cookie; /* We don't use it, yet. */
   /* Parse the descriptor. */
   if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content,
                                        &intro_size, &encoded_size,
@@ -1284,10 +1284,31 @@
   }
   /* Decode/decrypt introduction points. */
   if (intro_content) {
+    if (auth_type != REND_NO_AUTH && descriptor_cookie) {
+      char *ipos_decrypted;
+      size_t ipos_decrypted_size;
+      if (rend_decrypt_introduction_points(&ipos_decrypted,
+                                           &ipos_decrypted_size,
+                                           descriptor_cookie,
+                                           intro_content,
+                                           intro_size) < 0) {
+        log_warn(LD_REND, "Failed to decrypt introduction points. We are "
+                 "probably unable to parse the encoded introduction points.");
+      } else {
+        /* Replace encrypted with decrypted introduction points. */
+        log_info(LD_REND, "Successfully decrypted introduction points.");
+        tor_free(intro_content);
+        intro_content = ipos_decrypted;
+        intro_size = ipos_decrypted_size;
+      }
+    }
     if (rend_parse_introduction_points(parsed, intro_content,
-                                       intro_size) < 0) {
-      log_warn(LD_PROTOCOL,"Couldn't decode/decrypt introduction points.");
-      rend_service_descriptor_free(parsed);
+                                       intro_size) <= 0) {
+      log_warn(LD_REND, "Failed to parse introduction points. Either the "
+               "service has published a corrupt descriptor or you have "
+               "provided invalid authorization data.");
+      if (parsed)
+        rend_service_descriptor_free(parsed);
       tor_free(intro_content);
       return -2;
     }
@@ -1292,6 +1313,7 @@
       return -2;
     }
   } else {
+    log_info(LD_REND, "Descriptor does not contain any introduction points.");
     parsed->intro_nodes = smartlist_create();
   }
   /* We don't need the encoded/encrypted introduction points any longer. */
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c	(working copy)
@@ -69,8 +69,16 @@
                          * up-to-date. */
   time_t next_upload_time; /**< Scheduled next hidden service descriptor
                             * upload time. */
+  smartlist_t *accepted_intros; /**< List of client_access_event_t's for
+                                 * accepted and answered INTRODUCE2 cells. */
 } rend_service_t;
 
+/** The event of a client accessing our hidden service. */
+typedef struct client_access_event_t {
+  time_t access_time;
+  char diffie_hellman_hash[DIGEST_LEN];
+} client_access_event_t;
+
 /** A list of rend_service_t's for services run on this OP.
  */
 static smartlist_t *rend_service_list = NULL;
@@ -360,7 +368,7 @@
       if (smartlist_len(type_names_split) < 2) {
         log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains "
                             "auth-type '%s', but no client names.",
-                 service->auth_type == 1 ? "basic" : "stealth");
+                 service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
         SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
         smartlist_free(type_names_split);
         continue;
@@ -423,7 +431,7 @@
                             "authorization type '%s'.",
                  smartlist_len(service->clients),
                  service->auth_type == REND_BASIC_AUTH ? 512 : 16,
-                 service->auth_type == 1 ? "basic" : "stealth");
+                 service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
         rend_service_free(service);
         return -1;
       }
@@ -717,8 +725,10 @@
       tor_free(client_keys_str);
       strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
       if (r<0) {
-        abort_writing_to_file(open_cfile);
-        abort_writing_to_file(open_hfile);
+        if (open_cfile)
+          abort_writing_to_file(open_cfile);
+        if (open_hfile)
+          abort_writing_to_file(open_hfile);
         return r;
       } else {
         finish_writing_to_file(open_cfile);
@@ -761,6 +771,45 @@
   return 0;
 }
 
+/** Check client authorization of a given <b>descriptor_cookie</b> for
+ * <b>service</b>. Return 1 for success and 0 for failure. */
+static int
+rend_check_authorization(rend_service_t *service,
+                         const char *descriptor_cookie)
+{
+  rend_authorized_client_t *auth_client = NULL;
+  tor_assert(service);
+  tor_assert(descriptor_cookie);
+  if (!service->clients) {
+    log_warn(LD_BUG, "Can't check authorization for a service that has no "
+                     "authorized clients configured.");
+    return 0;
+  }
+
+  /* Look up client authorization by descriptor cookie. */
+  SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, client, {
+    if (!memcmp(client->descriptor_cookie, descriptor_cookie,
+                REND_DESC_COOKIE_LEN)) {
+      auth_client = client;
+      break;
+    }
+  });
+  if (!auth_client) {
+    char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+    base64_encode(descriptor_cookie_base64, sizeof(descriptor_cookie_base64),
+                  descriptor_cookie, REND_DESC_COOKIE_LEN);
+    log_info(LD_REND, "No authorization found for descriptor cookie '%s'! "
+                      "Dropping cell!",
+             descriptor_cookie_base64);
+    return 0;
+  }
+
+  /* Allow the request. */
+  log_info(LD_REND, "Client %s could be identified for service %s.",
+           auth_client->client_name, service->service_id);
+  return 1;
+}
+
 /******
  * Handle cells
  ******/
@@ -777,7 +826,7 @@
   char buf[RELAY_PAYLOAD_SIZE];
   char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
   rend_service_t *service;
-  int r, i;
+  int r, i, v3_shift = 0;
   size_t len, keylen;
   crypto_dh_env_t *dh = NULL;
   origin_circuit_t *launched = NULL;
@@ -788,6 +837,13 @@
   int reason = END_CIRC_REASON_TORPROTOCOL;
   crypto_pk_env_t *intro_key;
   char intro_key_digest[DIGEST_LEN];
+  int auth_type;
+  size_t auth_len = 0;
+  char auth_data[REND_DESC_COOKIE_LEN];
+  crypto_digest_env_t *digest = NULL;
+  time_t now = time(NULL);
+  char diffie_hellman_hash[DIGEST_LEN];
+  client_access_event_t *event = NULL;
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                 circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
@@ -851,13 +907,49 @@
     return -1;
   }
   len = r;
-  if (*buf == 2) {
+  if (*buf == 3) {
+    /* Version 3 INTRODUCE2 cell. */
+    time_t ts = 0, now = time(NULL);
+    v3_shift = 1;
+    auth_type = buf[1];
+    switch (auth_type) {
+      case REND_BASIC_AUTH:
+        /* fall through */
+      case REND_STEALTH_AUTH:
+        auth_len = ntohs(get_uint16(buf+2));
+        if (auth_len != REND_DESC_COOKIE_LEN) {
+          log_info(LD_REND, "Wrong auth data size %d, should be %d.",
+                   (int)auth_len, REND_DESC_COOKIE_LEN);
+          return -1;
+        }
+        memcpy(auth_data, buf+4, sizeof(auth_data));
+        v3_shift += 2+REND_DESC_COOKIE_LEN;
+        break;
+      case REND_NO_AUTH:
+        break;
+      default:
+        log_info(LD_REND, "Unknown authorization type '%d'", auth_type);
+    }
+
+    /* Check timestamp. */
+    memcpy((char*)&ts, buf+1+v3_shift, sizeof(uint32_t));
+    v3_shift += 4;
+    ts = ntohl(ts);
+    if ((now - ts) < -1 * REND_REPLAY_TIME_INTERVAL / 2 ||
+        (now - ts) > REND_REPLAY_TIME_INTERVAL / 2) {
+      log_warn(LD_REND, "INTRODUCE2 cell is too %s. Discarding.",
+          (now - ts) < 0 ? "old" : "new");
+      return -1;
+    }
+  }
+  if (*buf == 2 || *buf == 3) {
     /* Version 2 INTRODUCE2 cell. */
     int klen;
     extend_info = tor_malloc_zero(sizeof(extend_info_t));
-    tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+1));
-    extend_info->port = ntohs(get_uint16(buf+5));
-    memcpy(extend_info->identity_digest, buf+7, DIGEST_LEN);
+    tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+v3_shift+1));
+    extend_info->port = ntohs(get_uint16(buf+v3_shift+5));
+    memcpy(extend_info->identity_digest, buf+v3_shift+7,
+           DIGEST_LEN);
     extend_info->nickname[0] = '$';
     base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
                   extend_info->identity_digest, DIGEST_LEN);
@@ -862,22 +954,23 @@
     base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
                   extend_info->identity_digest, DIGEST_LEN);
 
-    klen = ntohs(get_uint16(buf+7+DIGEST_LEN));
-    if ((int)len != 7+DIGEST_LEN+2+klen+20+128) {
-      log_warn(LD_PROTOCOL, "Bad length %u for version 2 INTRODUCE2 cell.",
-               (int)len);
+    klen = ntohs(get_uint16(buf+v3_shift+7+DIGEST_LEN));
+    if ((int)len != v3_shift+7+DIGEST_LEN+2+klen+20+128) {
+      log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.",
+               (int)len, *buf);
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
     }
-    extend_info->onion_key = crypto_pk_asn1_decode(buf+7+DIGEST_LEN+2, klen);
+    extend_info->onion_key =
+        crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen);
     if (!extend_info->onion_key) {
-      log_warn(LD_PROTOCOL,
-               "Error decoding onion key in version 2 INTRODUCE2 cell.");
+      log_warn(LD_PROTOCOL, "Error decoding onion key in version %d "
+                            "INTRODUCE2 cell.", *buf);
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
     }
-    ptr = buf+7+DIGEST_LEN+2+klen;
-    len -= 7+DIGEST_LEN+2+klen;
+    ptr = buf+v3_shift+7+DIGEST_LEN+2+klen;
+    len -= v3_shift+7+DIGEST_LEN+2+klen;
   } else {
     char *rp_nickname;
     size_t nickname_field_len;
@@ -929,6 +1022,58 @@
   r_cookie = ptr;
   base16_encode(hexcookie,9,r_cookie,4);
 
+  /* Determine hash of Diffie-Hellman, part 1 to detect replays. */
+  digest = crypto_new_digest_env();
+  crypto_digest_add_bytes(digest, ptr+REND_COOKIE_LEN, DH_KEY_LEN);
+  crypto_digest_get_digest(digest, diffie_hellman_hash, DIGEST_LEN);
+  crypto_free_digest_env(digest);
+
+  /* Iterate over past requests, remove those which are older than one hour,
+   * and check whether there is one with same Diffie-Hellman, part 1. */
+  if (!service->accepted_intros)
+    service->accepted_intros = smartlist_create();
+  SMARTLIST_FOREACH(service->accepted_intros, client_access_event_t *,
+                    access, {
+    if (access->access_time + REND_REPLAY_TIME_INTERVAL < now) {
+      tor_free(access);
+      SMARTLIST_DEL_CURRENT(service->accepted_intros, access);
+    } else if (!memcmp(access->diffie_hellman_hash, diffie_hellman_hash,
+                       DIGEST_LEN)) {
+      log_warn(LD_REND, "Possible replay detected! We received an "
+                        "INTRODUCE2 cell with same first part of "
+                        "Diffie-Hellman handshake %d seconds ago. Dropping "
+                        "cell.",
+               (uint32_t) (now - access->access_time));
+      return 0;
+    }
+  });
+
+  /* Add request to access history, including time and hash of
+   * Diffie-Hellman, part 1. */
+  event = tor_malloc_zero(sizeof(client_access_event_t));
+  event->access_time = now;
+  memcpy(event->diffie_hellman_hash, diffie_hellman_hash, DIGEST_LEN);
+  smartlist_add(service->accepted_intros, event);
+
+  /* If the service performs client authorization, check included auth data. */
+  if (service->clients) {
+    if (auth_len > 0) {
+      if (rend_check_authorization(service, auth_data)) {
+        log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid.");
+      } else {
+        log_info(LD_REND, "The authorization data that are contained in "
+                 "the INTRODUCE2 cell are invalid. Dropping cell.");
+        reason = END_CIRC_REASON_CONNECTFAILED;
+        goto err;
+      }
+    } else {
+      log_info(LD_REND, "INTRODUCE2 cell does not contain authentication "
+               "data, but we require client authorization. Dropping cell.");
+      reason = END_CIRC_REASON_CONNECTFAILED;
+      goto err;
+    }
+  }
+
   /* Try DH handshake... */
   dh = crypto_dh_new();
   if (!dh || crypto_dh_generate_public(dh)<0) {
@@ -1379,7 +1524,8 @@
                                               DIR_PURPOSE_UPLOAD_RENDDESC_V2,
                                               ROUTER_PURPOSE_GENERAL,
                                               1, NULL, desc->desc_str,
-                                              strlen(desc->desc_str), 0);
+                                              strlen(desc->desc_str), 0,
+                                              NULL, REND_NO_AUTH, NULL);
       base32_encode(desc_id_base32, sizeof(desc_id_base32),
                     desc->desc_id, DIGEST_LEN);
       log_info(LD_REND, "Sending publish request for v2 descriptor for "
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/router.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/router.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/router.c	(working copy)
@@ -763,7 +763,8 @@
                                0, me->cache_info.identity_digest,
                                DIR_PURPOSE_FETCH_SERVERDESC,
                                ROUTER_PURPOSE_GENERAL,
-                               1, "authority.z", NULL, 0, 0);
+                               1, "authority.z", NULL, 0, 0,
+                               NULL, REND_NO_AUTH, NULL);
 
     control_event_server_status(LOG_NOTICE,
                                 "CHECKING_REACHABILITY DIRADDRESS=%s:%d",
Index: /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c
===================================================================
--- /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c	(revision 16614)
+++ /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c	(working copy)
@@ -3757,7 +3757,8 @@
     directory_initiate_command_routerstatus(source, purpose,
                                             ROUTER_PURPOSE_GENERAL,
                                             0, /* not private */
-                                            resource, NULL, 0, 0);
+                                            resource, NULL, 0, 0,
+                                            NULL, REND_NO_AUTH, NULL);
   } else {
     directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, 1);
   }

Attachment: patch-121-4.txt.sig
Description: Binary data