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

[or-cvs] r16955: {tor} Add patch 4 from Karsten for proposal 121, slightly modified (in tor/trunk: . doc src/or)



Author: nickm
Date: 2008-09-24 10:44:29 -0400 (Wed, 24 Sep 2008)
New Revision: 16955

Modified:
   tor/trunk/ChangeLog
   tor/trunk/doc/tor.1.in
   tor/trunk/src/or/circuitlist.c
   tor/trunk/src/or/circuituse.c
   tor/trunk/src/or/connection.c
   tor/trunk/src/or/connection_edge.c
   tor/trunk/src/or/directory.c
   tor/trunk/src/or/or.h
   tor/trunk/src/or/rendclient.c
   tor/trunk/src/or/rendcommon.c
   tor/trunk/src/or/rendservice.c
Log:
Add patch 4 from Karsten for proposal 121, slightly modified.  Karsten should definitely re-review the bits I changed.

Modified: tor/trunk/ChangeLog
===================================================================
--- tor/trunk/ChangeLog	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/ChangeLog	2008-09-24 14:44:29 UTC (rev 16955)
@@ -1,4 +1,13 @@
 Changes in version 0.2.1.6-alpha - 2008-09-xx
+  o Major features:
+    - Implement proposal 121: make it possible to build hidden services
+      that only certain clients are allowed to connect to.  This is
+      enforced at several points, so that unauthorized clients are
+      unable to send INTRODUCE cells to the service, or even (depending
+      on the type of authentication) to learn introduction points.  This
+      feature raises the bar for certain kinds of active attacks against
+      hidden services.
+
   o Major bugfixes:
     - Fix a bug when parsing ports in tor_addr_port_parse() that caused
       Tor to fail to start if you had it configured to use a bridge
@@ -63,6 +72,8 @@
       actual mistakes  we're making here.
     - Refactor unit testing logic so that dmalloc can be used sensibly with
       unit tests to check for memory leaks.
+    - Move all hidden-service related fields from connection and circuit
+      structure to substructures: this way they won't eat so much memory.
 
 
 Changes in version 0.2.0.31 - 2008-09-03

Modified: tor/trunk/doc/tor.1.in
===================================================================
--- tor/trunk/doc/tor.1.in	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/doc/tor.1.in	2008-09-24 14:44:29 UTC (rev 16955)
@@ -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
@@ -1269,6 +1278,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
@@ -1453,10 +1474,17 @@
 .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 
 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),

Modified: tor/trunk/src/or/circuitlist.c
===================================================================
--- tor/trunk/src/or/circuitlist.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/circuitlist.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -401,7 +401,8 @@
     circuit_free_cpath(ocirc->cpath);
     if (ocirc->intro_key)
       crypto_free_pk_env(ocirc->intro_key);
-
+    if (ocirc->rend_data)
+      rend_data_free(ocirc->rend_data);
   } else {
     or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
     mem = ocirc;
@@ -720,7 +721,7 @@
 }
 
 /** Return a circ such that:
- *  - circ-\>rend_query is equal to <b>rend_query</b>, and
+ *  - circ-\>rend_data-\>query is equal to <b>rend_query</b>, and
  *  - circ-\>purpose is equal to <b>purpose</b>.
  *
  * Return NULL if no such circuit exists.
@@ -734,9 +735,13 @@
 
   for (circ = global_circuitlist; circ; circ = circ->next) {
     if (!circ->marked_for_close &&
-        circ->purpose == purpose &&
-        !rend_cmp_service_ids(rend_query, TO_ORIGIN_CIRCUIT(circ)->rend_query))
-      return TO_ORIGIN_CIRCUIT(circ);
+        circ->purpose == purpose) {
+      origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+      if (ocirc->rend_data &&
+          !rend_cmp_service_ids(rend_query,
+                                ocirc->rend_data->onion_address))
+        return ocirc;
+    }
   }
   return NULL;
 }
@@ -764,7 +769,8 @@
       continue;
     if (!digest)
       return TO_ORIGIN_CIRCUIT(circ);
-    else if (!memcmp(TO_ORIGIN_CIRCUIT(circ)->rend_pk_digest,
+    else if (TO_ORIGIN_CIRCUIT(circ)->rend_data &&
+             !memcmp(TO_ORIGIN_CIRCUIT(circ)->rend_data->rend_pk_digest,
                      digest, DIGEST_LEN))
       return TO_ORIGIN_CIRCUIT(circ);
   }
@@ -1020,13 +1026,14 @@
     origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
     tor_assert(circ->state == CIRCUIT_STATE_OPEN);
     tor_assert(ocirc->build_state->chosen_exit);
+    tor_assert(ocirc->rend_data);
     /* treat this like getting a nack from it */
     log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). "
              "Removing from descriptor.",
-             safe_str(ocirc->rend_query),
+             safe_str(ocirc->rend_data->onion_address),
              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_data);
   }
   if (circ->n_conn)
     connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);

Modified: tor/trunk/src/or/circuituse.c
===================================================================
--- tor/trunk/src/or/circuituse.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/circuituse.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -121,8 +121,12 @@
       return 0;
     }
   } else { /* not general */
-    if (rend_cmp_service_ids(conn->rend_query,
-                             TO_ORIGIN_CIRCUIT(circ)->rend_query)) {
+    origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+    if ((conn->rend_data && !ocirc->rend_data) ||
+        (!conn->rend_data && ocirc->rend_data) ||
+        (conn->rend_data && ocirc->rend_data &&
+         rend_cmp_service_ids(conn->rend_data->onion_address,
+                              ocirc->rend_data->onion_address))) {
       /* this circ is not for this conn */
       return 0;
     }
@@ -300,7 +304,7 @@
           /* c_rend_ready circs measure age since timestamp_dirty,
            * because that's set when they switch purposes
            */
-          if (TO_ORIGIN_CIRCUIT(victim)->rend_query[0] ||
+          if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
               victim->timestamp_dirty > cutoff)
             continue;
           break;
@@ -1076,18 +1080,24 @@
 
     if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
       /* need to pick an intro point */
-      extend_info = rend_client_get_random_intro(conn->rend_query);
+      tor_assert(conn->rend_data);
+      extend_info = rend_client_get_random_intro(conn->rend_data);
       if (!extend_info) {
         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);
+                 safe_str(conn->rend_data->onion_address));
+        /* 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_data);
+        if (conn->rend_data->auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_data->onion_address);
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         return 0;
       }
       log_info(LD_REND,"Chose '%s' as intro point for '%s'.",
-               extend_info->nickname, safe_str(conn->rend_query));
+               extend_info->nickname,
+               safe_str(conn->rend_data->onion_address));
     }
 
     /* If we have specified a particular exit node for our
@@ -1163,7 +1173,7 @@
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       if (circ) {
         /* write the service_id into circ */
-        strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+        circ->rend_data = rend_data_dup(conn->rend_data);
         if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
             circ->_base.state == CIRCUIT_STATE_OPEN)
           rend_client_rendcirc_has_opened(circ);

Modified: tor/trunk/src/or/connection.c
===================================================================
--- tor/trunk/src/or/connection.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/connection.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -385,6 +385,8 @@
       memset(edge_conn->socks_request, 0xcc, sizeof(socks_request_t));
       tor_free(edge_conn->socks_request);
     }
+    if (edge_conn->rend_data)
+      rend_data_free(edge_conn->rend_data);
   }
   if (conn->type == CONN_TYPE_CONTROL) {
     control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -405,6 +407,8 @@
     }
     if (dir_conn->cached_dir)
       cached_dir_decref(dir_conn->cached_dir);
+    if (dir_conn->rend_data)
+      rend_data_free(dir_conn->rend_data);
   }
 
   if (conn->s >= 0) {
@@ -523,21 +527,22 @@
          * failed: forget about this router, and maybe try again. */
         connection_dir_request_failed(dir_conn);
       }
-      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC) {
+      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC && dir_conn->rend_data) {
         /* Give it a try. However, there is no re-fetching for v0 rend
          * descriptors; if the response is empty or the descriptor is
          * unusable, close pending connections (unless a v2 request is
          * still in progress). */
-        rend_client_desc_trynow(dir_conn->rend_query, 0);
+        rend_client_desc_trynow(dir_conn->rend_data->onion_address, 0);
       }
       /* If we were trying to fetch a v2 rend desc and did not succeed,
        * retry as needed. (If a fetch is successful, the connection state
        * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that
        * refetching is unnecessary.) */
       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);
+          dir_conn->rend_data &&
+          strlen(dir_conn->rend_data->onion_address) ==
+              REND_SERVICE_ID_LEN_BASE32)
+        rend_client_refetch_v2_renddesc(dir_conn->rend_data);
       break;
     case CONN_TYPE_OR:
       or_conn = TO_OR_CONN(conn);
@@ -2565,6 +2570,7 @@
 
   tor_assert(type == CONN_TYPE_DIR ||
              type == CONN_TYPE_AP || type == CONN_TYPE_EXIT);
+  tor_assert(rendquery);
 
   SMARTLIST_FOREACH(conns, connection_t *, conn,
   {
@@ -2572,12 +2578,16 @@
         !conn->marked_for_close &&
         (!state || state == conn->state)) {
       if (type == CONN_TYPE_DIR &&
+          TO_DIR_CONN(conn)->rend_data &&
           (rendversion < 0 ||
-           rendversion == TO_DIR_CONN(conn)->rend_version) &&
-          !rend_cmp_service_ids(rendquery, TO_DIR_CONN(conn)->rend_query))
+           rendversion == TO_DIR_CONN(conn)->rend_data->rend_desc_version) &&
+          !rend_cmp_service_ids(rendquery,
+                                TO_DIR_CONN(conn)->rend_data->onion_address))
         return conn;
       else if (CONN_IS_EDGE(conn) &&
-              !rend_cmp_service_ids(rendquery, TO_EDGE_CONN(conn)->rend_query))
+               TO_EDGE_CONN(conn)->rend_data &&
+               !rend_cmp_service_ids(rendquery,
+                            TO_EDGE_CONN(conn)->rend_data->onion_address))
         return conn;
     }
   });

Modified: tor/trunk/src/or/connection_edge.c
===================================================================
--- tor/trunk/src/or/connection_edge.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/connection_edge.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -1587,6 +1587,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
@@ -1608,14 +1609,16 @@
       return -1;
     }
 
-    strlcpy(conn->rend_query, socks->address, sizeof(conn->rend_query));
+    conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+    strlcpy(conn->rend_data->onion_address, socks->address,
+            sizeof(conn->rend_data->onion_address));
     log_info(LD_REND,"Got a hidden service request for ID '%s'",
-             safe_str(conn->rend_query));
+             safe_str(conn->rend_data->onion_address));
     /* see if we already have it cached */
-    r = rend_cache_lookup_entry(conn->rend_query, -1, &entry);
+    r = rend_cache_lookup_entry(conn->rend_data->onion_address, -1, &entry);
     if (r<0) {
       log_warn(LD_BUG,"Invalid service name '%s'",
-               safe_str(conn->rend_query));
+               safe_str(conn->rend_data->onion_address));
       connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
       return -1;
     }
@@ -1624,14 +1627,26 @@
      * 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_data->onion_address);
+    if (client_auth) {
+      log_info(LD_REND, "Using previously configured client authorization "
+                        "for hidden service request.");
+      memcpy(conn->rend_data->descriptor_cookie,
+             client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN);
+      conn->rend_data->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.",
-               safe_str(conn->rend_query));
+               safe_str(conn->rend_data->onion_address));
       /* 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_data);
+      if (conn->rend_data->auth_type == REND_NO_AUTH)
+        rend_client_refetch_renddesc(conn->rend_data->onion_address);
     } else { /* r > 0 */
 /** How long after we receive a hidden service descriptor do we consider
  * it valid? */
@@ -1647,11 +1662,13 @@
       } else {
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         log_info(LD_REND, "Stale descriptor %s. Refetching.",
-                 safe_str(conn->rend_query));
+                 safe_str(conn->rend_data->onion_address));
         /* 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_data);
+        if (conn->rend_data->auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_data->onion_address);
       }
     }
     return 0;
@@ -2531,8 +2548,7 @@
     log_info(LD_REND,"begin is for rendezvous. configuring stream.");
     n_stream->_base.address = tor_strdup("(rendezvous)");
     n_stream->_base.state = EXIT_CONN_STATE_CONNECTING;
-    strlcpy(n_stream->rend_query, origin_circ->rend_query,
-            sizeof(n_stream->rend_query));
+    n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
     tor_assert(connection_edge_is_rendezvous_stream(n_stream));
     assert_circuit_ok(circ);
     if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) {
@@ -2815,7 +2831,7 @@
 connection_edge_is_rendezvous_stream(edge_connection_t *conn)
 {
   tor_assert(conn);
-  if (*conn->rend_query) /* XXX */ /* XXXX Why is this XXX? -NM */
+  if (conn->rend_data) /* XXX */ /* XXXX Why is this XXX? -NM */
     return 1;
   return 0;
 }

Modified: tor/trunk/src/or/directory.c
===================================================================
--- tor/trunk/src/or/directory.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/directory.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -60,6 +60,22 @@
 static void note_client_request(int purpose, int compressed, size_t bytes);
 static int client_likes_consensus(networkstatus_t *v, const char *want_url);
 
+static void directory_initiate_command_rend(const char *address,
+                                            const tor_addr_t *addr,
+                                            uint16_t or_port,
+                                            uint16_t dir_port,
+                                            int supports_conditional_consensus,
+                                            int supports_begindir,
+                                            const char *digest,
+                                            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,
+                                            const rend_data_t *rend_query);
+
 /********* START VARIABLES **********/
 
 /** How far in the future do we allow a directory server to tell us it is
@@ -434,29 +450,18 @@
     });
 }
 
-/** Launch a new connection to the directory server <b>status</b> to
- * upload or download a server or rendezvous
- * descriptor. <b>dir_purpose</b> determines what
- * kind of directory connection we're launching, and must be one of
- * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC|RENDDESC_V2}. <b>router_purpose</b>
- * specifies the descriptor purposes we have in mind (currently only
- * used for FETCH_DIR).
- *
- * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
- * of the HTTP post.  Otherwise, <b>payload</b> should be NULL.
- *
- * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we
- * want to fetch.
- */
+/** Same as directory_initiate_command_routerstatus(), but accepts
+ * rendezvous data to fetch a hidden service descriptor. */
 void
-directory_initiate_command_routerstatus(routerstatus_t *status,
-                                        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)
+directory_initiate_command_routerstatus_rend(routerstatus_t *status,
+                                             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,
+                                             const rend_data_t *rend_query)
 {
   routerinfo_t *router;
   char address_buf[INET_NTOA_BUF_LEN+1];
@@ -476,16 +481,48 @@
     address = address_buf;
   }
   tor_addr_from_ipv4h(&addr, status->addr);
-  directory_initiate_command(address, &addr,
+  directory_initiate_command_rend(address, &addr,
                              status->or_port, status->dir_port,
                              status->version_supports_conditional_consensus,
                              status->version_supports_begindir,
                              status->identity_digest,
                              dir_purpose, router_purpose,
                              anonymized_connection, resource,
-                             payload, payload_len, if_modified_since);
+                             payload, payload_len, if_modified_since,
+                             rend_query);
 }
 
+/** Launch a new connection to the directory server <b>status</b> to
+ * upload or download a server or rendezvous
+ * descriptor. <b>dir_purpose</b> determines what
+ * kind of directory connection we're launching, and must be one of
+ * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC|RENDDESC_V2}. <b>router_purpose</b>
+ * specifies the descriptor purposes we have in mind (currently only
+ * used for FETCH_DIR).
+ *
+ * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
+ * of the HTTP post.  Otherwise, <b>payload</b> should be NULL.
+ *
+ * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we
+ * want to fetch.
+ */
+void
+directory_initiate_command_routerstatus(routerstatus_t *status,
+                                        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)
+{
+  directory_initiate_command_routerstatus_rend(status, dir_purpose,
+                                          router_purpose,
+                                          anonymized_connection, resource,
+                                          payload, payload_len,
+                                          if_modified_since, NULL);
+}
+
 /** Return true iff <b>conn</b> is the client side of a directory connection
  * we launched to ourself in order to determine the reachability of our
  * dir_port. */
@@ -668,6 +705,28 @@
                            const char *payload, size_t payload_len,
                            time_t if_modified_since)
 {
+  directory_initiate_command_rend(address, _addr, or_port, dir_port,
+                             supports_conditional_consensus,
+                             supports_begindir, digest, dir_purpose,
+                             router_purpose, anonymized_connection,
+                             resource, payload, payload_len,
+                             if_modified_since, NULL);
+}
+
+/** Same as directory_initiate_command(), but accepts rendezvous data to
+ * fetch a hidden service descriptor. */
+static void
+directory_initiate_command_rend(const char *address, const tor_addr_t *_addr,
+                                uint16_t or_port, uint16_t dir_port,
+                                int supports_conditional_consensus,
+                                int supports_begindir, const char *digest,
+                                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,
+                                const rend_data_t *rend_query)
+{
   dir_connection_t *conn;
   or_options_t *options = get_options();
   int socket_error = 0;
@@ -705,6 +764,10 @@
   /* decide whether we can learn our IP address from this conn */
   conn->dirconn_direct = !anonymized_connection;
 
+  /* copy rendezvous data, if any */
+  if (rend_query)
+    conn->rend_data = rend_data_dup(rend_query);
+
   if (!anonymized_connection && !use_begindir) {
     /* then we want to connect to dirport directly */
 
@@ -1005,8 +1068,10 @@
       /* this must be true or we wouldn't be doing the lookup */
       tor_assert(strlen(resource) <= REND_SERVICE_ID_LEN_BASE32);
       /* This breaks the function abstraction. */
-      strlcpy(conn->rend_query, resource, sizeof(conn->rend_query));
-      conn->rend_version = 0;
+      conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+      strlcpy(conn->rend_data->onion_address, resource,
+              sizeof(conn->rend_data->onion_address));
+      conn->rend_data->rend_desc_version = 0;
 
       httpcommand = "GET";
       /* Request the most recent versioned descriptor. */
@@ -1019,10 +1084,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));
-      conn->rend_version = 2;
-      payload = NULL;
+      tor_assert(!payload);
+      conn->rend_data->rend_desc_version = 2;
       httpcommand = "GET";
       len = strlen(resource) + 32;
       url = tor_malloc(len);
@@ -1877,6 +1940,7 @@
   }
 
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC) {
+    tor_assert(conn->rend_data);
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
              "(%s))",
              (int)body_len, status_code, escaped(reason));
@@ -1892,7 +1956,7 @@
         } else {
           /* success. notify pending connections about this. */
           conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
-          rend_client_desc_trynow(conn->rend_query, -1);
+          rend_client_desc_trynow(conn->rend_data->onion_address, -1);
         }
         break;
       case 404:
@@ -1914,12 +1978,13 @@
   }
 
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) {
+    tor_assert(conn->rend_data);
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
              "(%s))",
              (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_data)) {
           case -2:
             log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
                      "Retrying at another directory.");
@@ -1938,7 +2003,7 @@
             log_info(LD_REND, "Successfully fetched v2 rendezvous "
                      "descriptor.");
             conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
-            rend_client_desc_trynow(conn->rend_query, -1);
+            rend_client_desc_trynow(conn->rend_data->onion_address, -1);
             break;
         }
         break;

Modified: tor/trunk/src/or/or.h
===================================================================
--- tor/trunk/src/or/or.h	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/or.h	2008-09-24 14:44:29 UTC (rev 16955)
@@ -676,6 +676,55 @@
 /** Maximum length of authorized client names for a hidden service. */
 #define REND_CLIENTNAME_MAX_LEN 16
 
+/** Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define REND_COOKIE_LEN DIGEST_LEN
+
+/** 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];
+  char onion_address[REND_SERVICE_ADDRESS_LEN+1];
+  rend_auth_type_t auth_type;
+} rend_service_authorization_t;
+
+/** Client- and server-side data that is used for hidden service connection
+ * establishment. Not all fields contain data depending on where this struct
+ * is used. */
+typedef struct rend_data_t {
+  /** Onion address (without the .onion part) that a client requests. */
+  char onion_address[REND_SERVICE_ID_LEN_BASE32+1];
+
+  /** (Optional) descriptor cookie that is used by a client. */
+  char descriptor_cookie[REND_DESC_COOKIE_LEN];
+
+  /** Authorization type for accessing a service used by a client. */
+  rend_auth_type_t auth_type;
+
+  /** Hash of the hidden service's PK used by a service. */
+  char rend_pk_digest[DIGEST_LEN];
+
+  /** Rendezvous cookie used by both, client and service. */
+  char rend_cookie[REND_COOKIE_LEN];
+
+  /** Rendezvous descriptor version that is used by a service. Used to
+   * distinguish introduction and rendezvous points belonging to the same
+   * rendezvous service ID, but different descriptor versions.
+   */
+  uint8_t rend_desc_version;
+} rend_data_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
 
@@ -1025,7 +1074,7 @@
   uint32_t n_written;
 
   /** What rendezvous service are we querying for? (AP only) */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
+  rend_data_t *rend_data;
 
   /** Number of times we've reassigned this application connection to
    * a new circuit. We keep track because the timeout is longer if we've
@@ -1078,11 +1127,8 @@
   /** The zlib object doing on-the-fly compression for spooled data. */
   tor_zlib_state_t *zlib_state;
 
-  /** What hidden service descriptor are we fetching, if any? */
-  int rend_version;
-
   /** What rendezvous service are we querying for? */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
+  rend_data_t *rend_data;
 
   char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
                                      * the directory server's signing key. */
@@ -1747,7 +1793,6 @@
                                  CIPHER_KEY_LEN+\
                                  DH_KEY_LEN)
 #define ONIONSKIN_REPLY_LEN (DH_KEY_LEN+DIGEST_LEN)
-#define REND_COOKIE_LEN DIGEST_LEN
 
 /** Information used to build a circuit. */
 typedef struct {
@@ -1883,29 +1928,9 @@
    */
   crypt_path_t *cpath;
 
-  /** The rend_pk_digest field holds a hash of location-hidden service's
-   * PK if purpose is S_ESTABLISH_INTRO or S_RENDEZVOUSING.
-   */
-  char rend_pk_digest[DIGEST_LEN];
+  /** Holds all rendezvous data on either client or service side. */
+  rend_data_t *rend_data;
 
-  /** Holds rendezvous cookie if purpose is C_ESTABLISH_REND. Filled with
-   * zeroes otherwise.
-   */
-  char rend_cookie[REND_COOKIE_LEN];
-
-  /**
-   * The rend_query field holds the y portion of y.onion (nul-terminated)
-   * if purpose is C_INTRODUCING or C_ESTABLISH_REND, or is a C_GENERAL
-   * for a hidden service, or is S_*.
-   */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
-
-  /** 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.
-   */
-  uint8_t rend_desc_version;
-
   /** How many more relay_early cells can we send on this circuit, according
    * to the specification? */
   unsigned int remaining_relay_early_cells : 4;
@@ -3179,6 +3204,15 @@
                                              const char *payload,
                                              size_t payload_len,
                                              time_t if_modified_since);
+void directory_initiate_command_routerstatus_rend(routerstatus_t *status,
+                                                  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,
+                                                const rend_data_t *rend_query);
 
 int parse_http_response(const char *headers, int *code, time_t *date,
                         compress_method_t *compression, char **response);
@@ -3835,39 +3869,25 @@
 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 rend_data_t *rend_query);
 int rend_client_remove_intro_point(extend_info_t *failed_intro,
-                                   const char *query);
+                                   const rend_data_t *rend_query);
 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,
                                    size_t request_len);
 void rend_client_desc_trynow(const char *query, int rend_version);
 
-extend_info_t *rend_client_get_random_intro(const char *query);
+extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query);
 
 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];
-  char onion_address[REND_SERVICE_ADDRESS_LEN+1];
-  rend_auth_type_t auth_type;
-} rend_service_authorization_t;
-
 int rend_parse_service_authorization(or_options_t *options,
                                      int validate_only);
 rend_service_authorization_t *rend_client_lookup_service_authorization(
                                                 const char *onion_address);
 void rend_service_authorization_free_all(void);
+rend_data_t *rend_data_dup(const rend_data_t *request);
 
 /********************************* rendcommon.c ***************************/
 
@@ -3910,6 +3930,13 @@
   smartlist_t *successful_uploads;
 } rend_service_descriptor_t;
 
+/** Free all storage associated with <b>data</b> */
+static INLINE void
+rend_data_free(rend_data_t *data)
+{
+  tor_free(data);
+}
+
 int rend_cmp_service_ids(const char *one, const char *two);
 
 void rend_process_relay_cell(circuit_t *circ, int command, size_t length,
@@ -3947,7 +3974,7 @@
 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);
+                                       const rend_data_t *rend_query);
 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,

Modified: tor/trunk/src/or/rendclient.c
===================================================================
--- tor/trunk/src/or/rendclient.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/rendclient.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -31,16 +31,18 @@
 rend_client_send_establish_rendezvous(origin_circuit_t *circ)
 {
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+  tor_assert(circ->rend_data);
   log_info(LD_REND, "Sending an ESTABLISH_RENDEZVOUS cell");
 
-  if (crypto_rand(circ->rend_cookie, REND_COOKIE_LEN) < 0) {
+  if (crypto_rand(circ->rend_data->rend_cookie, REND_COOKIE_LEN) < 0) {
     log_warn(LD_BUG, "Internal error: Couldn't produce random cookie.");
     circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
     return -1;
   }
   if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
                                    RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
-                                   circ->rend_cookie, REND_COOKIE_LEN,
+                                   circ->rend_data->rend_cookie,
+                                   REND_COOKIE_LEN,
                                    circ->cpath->prev)<0) {
     /* circ is already marked for close */
     log_warn(LD_GENERAL, "Couldn't send ESTABLISH_RENDEZVOUS cell");
@@ -58,7 +60,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;
@@ -68,13 +70,16 @@
 
   tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
   tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY);
-  tor_assert(!rend_cmp_service_ids(introcirc->rend_query,
-                                   rendcirc->rend_query));
+  tor_assert(introcirc->rend_data);
+  tor_assert(rendcirc->rend_data);
+  tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
+                                   rendcirc->rend_data->onion_address));
 
-  if (rend_cache_lookup_entry(introcirc->rend_query, -1, &entry) < 1) {
+  if (rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
+                              &entry) < 1) {
     log_warn(LD_REND,
              "query %s didn't have valid rend desc in cache. Failing.",
-             escaped_safe_str(introcirc->rend_query));
+             escaped_safe_str(introcirc->rend_data->onion_address));
     goto err;
   }
 
@@ -117,27 +122,45 @@
     }
   }
 
+  /* 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_data->auth_type; /* auth type, if any */
+    v3_shift = 1;
+    if (introcirc->rend_data->auth_type != REND_NO_AUTH) {
+      set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
+      memcpy(tmp+4, introcirc->rend_data->descriptor_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_data->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,
             (MAX_NICKNAME_LEN+1)); /* nul pads */
-    memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_cookie,
+    memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_data->rend_cookie,
            REND_COOKIE_LEN);
     dh_offset = MAX_NICKNAME_LEN+1+REND_COOKIE_LEN;
   }
@@ -216,6 +239,7 @@
   }
 
   tor_assert(circ->build_state->chosen_exit);
+  tor_assert(circ->rend_data);
 
   if (request_len == 0) {
     /* It's an ACK; the introduction point relayed our introduction request. */
@@ -224,7 +248,7 @@
      */
     log_info(LD_REND,"Received ack. Telling rend circ...");
     rendcirc = circuit_get_by_rend_query_and_purpose(
-               circ->rend_query, CIRCUIT_PURPOSE_C_REND_READY);
+               circ->rend_data->onion_address, CIRCUIT_PURPOSE_C_REND_READY);
     if (rendcirc) { /* remember the ack */
       rendcirc->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
     } else {
@@ -241,22 +265,22 @@
      * If none remain, refetch the service descriptor.
      */
     if (rend_client_remove_intro_point(circ->build_state->chosen_exit,
-                                       circ->rend_query) > 0) {
+                                       circ->rend_data) > 0) {
       /* There are introduction points left. Re-extend the circuit to
        * another intro point and try again. */
       extend_info_t *extend_info;
       int result;
-      extend_info = rend_client_get_random_intro(circ->rend_query);
+      extend_info = rend_client_get_random_intro(circ->rend_data);
       if (!extend_info) {
         log_warn(LD_REND, "No introduction points left for %s. Closing.",
-                 escaped_safe_str(circ->rend_query));
+                 escaped_safe_str(circ->rend_data->onion_address));
         circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
         return -1;
       }
       log_info(LD_REND,
                "Got nack for %s from %s. Re-extending circ %d, "
                "this time to %s.",
-               escaped_safe_str(circ->rend_query),
+               escaped_safe_str(circ->rend_data->onion_address),
                circ->build_state->chosen_exit->nickname, circ->_base.n_circ_id,
                extend_info->nickname);
       result = circuit_extend_to_new_exit(circ, extend_info);
@@ -337,15 +361,15 @@
  * 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 rend_data_t *rend_query)
 {
   smartlist_t *responsible_dirs = smartlist_create();
   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);
+  tor_assert(rend_query);
   /* Determine responsible dirs. Even if we can't get all we want,
    * work with the ones we have. If it's empty, we'll notice below. */
   (int) hid_serv_get_responsible_directories(responsible_dirs, desc_id);
@@ -377,17 +401,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.) */
-  directory_initiate_command_routerstatus(hs_dir,
+  /* Encode descriptor cookie for logging purposes. */
+  if (rend_query->auth_type != REND_NO_AUTH &&
+      base64_encode(descriptor_cookie_base64, 3*REND_DESC_COOKIE_LEN_BASE64,
+                    rend_query->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_rend(hs_dir,
                                           DIR_PURPOSE_FETCH_RENDDESC_V2,
                                           ROUTER_PURPOSE_GENERAL,
-                                          1, desc_id_base32, query, 0, 0);
+                                          1, desc_id_base32, NULL, 0, 0,
+                                          rend_query);
   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.",
+           rend_query->onion_address, desc_id_base32,
+           rend_query->auth_type,
+           (rend_query->auth_type == REND_NO_AUTH ? "NULL" :
+           escaped_safe_str(descriptor_cookie_base64)),
+           hs_dir->nickname, hs_dir->dir_port);
   return 1;
 }
 
@@ -417,14 +457,13 @@
  * <b>query</b>.
  */
 void
-rend_client_refetch_v2_renddesc(const char *query)
+rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
 {
   char descriptor_id[DIGEST_LEN];
   int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
   int i, tries_left;
   rend_cache_entry_t *e = NULL;
-  tor_assert(query);
-  tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32);
+  tor_assert(rend_query);
   /* Are we configured to fetch descriptors? */
   if (!get_options()->FetchHidServDescriptors) {
     log_warn(LD_REND, "We received an onion address for a v2 rendezvous "
@@ -432,13 +471,13 @@
     return;
   }
   /* Before fetching, check if we already have the descriptor here. */
-  if (rend_cache_lookup_entry(query, -1, &e) > 0) {
+  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0) {
     log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
                       "already have that descriptor here. Not fetching.");
     return;
   }
   log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
-            safe_str(query));
+            safe_str(rend_query->onion_address));
   /* Randomly iterate over the replicas until a descriptor can be fetched
    * from one of the consecutive nodes, or no options are left. */
   tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
@@ -449,13 +488,15 @@
     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, rend_query->onion_address,
+                                rend_query->auth_type == REND_STEALTH_AUTH ?
+                                    rend_query->descriptor_cookie : NULL,
+                                time(NULL), chosen_replica) < 0) {
       log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
                         "descriptor ID did not succeed.");
       return;
     }
-    if (directory_get_from_hs_dir(descriptor_id, query) != 0)
+    if (directory_get_from_hs_dir(descriptor_id, rend_query) != 0)
       return; /* either success or failure, but we're done */
   }
   /* If we come here, there are no hidden service directories left. */
@@ -463,7 +504,7 @@
                     "service directories to fetch descriptors, because "
                     "we already tried them all unsuccessfully.");
   /* Close pending connections (unless a v0 request is still going on). */
-  rend_client_desc_trynow(query, 2);
+  rend_client_desc_trynow(rend_query->onion_address, 2);
   return;
 }
 
@@ -474,24 +515,28 @@
  * 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 rend_data_t *rend_query)
 {
   int i, r;
   rend_cache_entry_t *ent;
   connection_t *conn;
 
-  r = rend_cache_lookup_entry(query, -1, &ent);
+  r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
   if (r<0) {
-    log_warn(LD_BUG, "Malformed service ID %s.", escaped_safe_str(query));
+    log_warn(LD_BUG, "Malformed service ID %s.",
+             escaped_safe_str(rend_query->onion_address));
     return -1;
   }
   if (r==0) {
     log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
-             escaped_safe_str(query));
+             escaped_safe_str(rend_query->onion_address));
     /* 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(rend_query);
+    if (rend_query->auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(rend_query->onion_address);
     return 0;
   }
 
@@ -508,22 +553,26 @@
   if (smartlist_len(ent->parsed->intro_nodes) == 0) {
     log_info(LD_REND,
              "No more intro points remain for %s. Re-fetching descriptor.",
-             escaped_safe_str(query));
+             escaped_safe_str(rend_query->onion_address));
     /* 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(rend_query);
+    if (rend_query->auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(rend_query->onion_address);
 
     /* move all pending streams back to renddesc_wait */
     while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
-                                   AP_CONN_STATE_CIRCUIT_WAIT, query, -1))) {
+                                   AP_CONN_STATE_CIRCUIT_WAIT,
+                                   rend_query->onion_address, -1))) {
       conn->state = AP_CONN_STATE_RENDDESC_WAIT;
     }
 
     return 0;
   }
   log_info(LD_REND,"%d options left for %s.",
-           smartlist_len(ent->parsed->intro_nodes), escaped_safe_str(query));
+           smartlist_len(ent->parsed->intro_nodes),
+           escaped_safe_str(rend_query->onion_address));
   return 1;
 }
 
@@ -648,10 +697,13 @@
         _conn->marked_for_close)
       continue;
     conn = TO_EDGE_CONN(_conn);
-    if (rend_cmp_service_ids(query, conn->rend_query))
+    if (!conn->rend_data)
       continue;
+    if (rend_cmp_service_ids(query, conn->rend_data->onion_address))
+      continue;
     assert_connection_ok(TO_CONN(conn), now);
-    if (rend_cache_lookup_entry(conn->rend_query, -1, &entry) == 1 &&
+    if (rend_cache_lookup_entry(conn->rend_data->onion_address, -1,
+                                &entry) == 1 &&
         smartlist_len(entry->parsed->intro_nodes) > 0) {
       /* either this fetch worked, or it failed but there was a
        * valid entry from before which we should reuse */
@@ -689,17 +741,17 @@
  * have been tried and failed.
  */
 extend_info_t *
-rend_client_get_random_intro(const char *query)
+rend_client_get_random_intro(const rend_data_t *rend_query)
 {
   int i;
   rend_cache_entry_t *entry;
   rend_intro_point_t *intro;
   routerinfo_t *router;
 
-  if (rend_cache_lookup_entry(query, -1, &entry) < 1) {
+  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) {
     log_warn(LD_REND,
              "Query '%s' didn't have valid rend desc in cache. Failing.",
-             safe_str(query));
+             safe_str(rend_query->onion_address));
     return NULL;
   }
 

Modified: tor/trunk/src/or/rendcommon.c
===================================================================
--- tor/trunk/src/or/rendcommon.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/rendcommon.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -1243,7 +1243,7 @@
  */
 int
 rend_cache_store_v2_desc_as_client(const char *desc,
-                                   const char *descriptor_cookie)
+                                   const rend_data_t *rend_query)
 {
   /*XXXX this seems to have a bit of duplicate code with
    * rend_cache_store_v2_desc_as_dir().  Fix that. */
@@ -1272,7 +1272,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,
@@ -1291,14 +1290,37 @@
   }
   /* Decode/decrypt introduction points. */
   if (intro_content) {
+    if (rend_query->auth_type != REND_NO_AUTH &&
+        rend_query->descriptor_cookie) {
+      char *ipos_decrypted;
+      size_t ipos_decrypted_size;
+      if (rend_decrypt_introduction_points(&ipos_decrypted,
+                                           &ipos_decrypted_size,
+                                           rend_query->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;
     }
   } 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. */
@@ -1426,3 +1448,12 @@
   return strmap_size(rend_cache);
 }
 
+/** Allocate and return a new rend_data_t with the same
+ * contents as <b>query</b>. */
+rend_data_t *
+rend_data_dup(const rend_data_t *data)
+{
+  tor_assert(data);
+  return tor_memdup(data, sizeof(rend_data_t));
+}
+

Modified: tor/trunk/src/or/rendservice.c
===================================================================
--- tor/trunk/src/or/rendservice.c	2008-09-24 05:53:37 UTC (rev 16954)
+++ tor/trunk/src/or/rendservice.c	2008-09-24 14:44:29 UTC (rev 16955)
@@ -69,6 +69,11 @@
                          * up-to-date. */
   time_t next_upload_time; /**< Scheduled next hidden service descriptor
                             * upload time. */
+  /** Map from digests of diffie-hellman values INTRODUCE2 to time_t of when
+   * they were received; used to prevent replays. */
+  digestmap_t *accepted_intros;
+  /** Time at which we last removed expired values from accepted_intros. */
+  time_t last_cleaned_accepted_intros;
 } rend_service_t;
 
 /** A list of rend_service_t's for services run on this OP.
@@ -125,6 +130,8 @@
       rend_authorized_client_free(c););
     smartlist_free(service->clients);
   }
+  if (service->accepted_intros)
+    digestmap_free(service->accepted_intros, _tor_free);
   tor_free(service);
 }
 
@@ -360,7 +367,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 +430,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;
       }
@@ -720,8 +727,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);
@@ -764,6 +773,64 @@
   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_debug(LD_REND, "Client %s authorized for service %s.",
+            auth_client->client_name, service->service_id);
+  return 1;
+}
+
+/** Remove elements from <b>service</b>'s replay cache that are old enough to
+ * be noticed by timestamp checking. */
+static void
+clean_accepted_intros(rend_service_t *service, time_t now)
+{
+  const time_t cutoff = now - REND_REPLAY_TIME_INTERVAL;
+
+  service->last_cleaned_accepted_intros = now;
+  if (!service->accepted_intros)
+    return;
+
+  DIGESTMAP_FOREACH_MODIFY(service->accepted_intros, digest, time_t *, t) {
+    if (*t < cutoff) {
+      tor_free(t);
+      MAP_DEL_CURRENT(digest);
+    }
+  } DIGESTMAP_FOREACH_END;
+}
+
 /******
  * Handle cells
  ******/
@@ -780,7 +847,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;
@@ -791,9 +858,17 @@
   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];
+  time_t *access_time;
+  tor_assert(circuit->rend_data);
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
   log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
            escaped(serviceid), circuit->_base.n_circ_id);
 
@@ -814,7 +889,8 @@
 
   /* look up service depending on circuit. */
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
     log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.",
              escaped(serviceid));
@@ -822,7 +898,7 @@
   }
 
   /* if descriptor version is 2, use intro key instead of service key. */
-  if (circuit->rend_desc_version == 0) {
+  if (circuit->rend_data->rend_desc_version == 0) {
     intro_key = service->private_key;
   } else {
     intro_key = circuit->intro_key;
@@ -854,33 +930,70 @@
     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);
 
-    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;
@@ -932,6 +1045,54 @@
   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 = digestmap_new();
+
+  access_time = digestmap_get(service->accepted_intros, diffie_hellman_hash);
+  if (access_time != NULL) {
+    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.",
+             (int) (now - *access_time));
+    goto err;
+  }
+
+  /* Add request to access history, including time and hash of
+   * Diffie-Hellman, part 1. */
+  access_time = tor_malloc(sizeof(time_t));
+  *access_time = now;
+  digestmap_set(service->accepted_intros, diffie_hellman_hash, access_time);
+  if (service->last_cleaned_accepted_intros + REND_REPLAY_TIME_INTERVAL < now)
+    clean_accepted_intros(service, now);
+
+  /* If the service performs client authorization, check included auth data. */
+  if (service->clients) {
+    if (auth_len > 0) {
+      if (rend_check_authorization(service, auth_data)) {
+        log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid.");
+      } else {
+        log_info(LD_REND, "The authorization data that are contained in "
+                 "the INTRODUCE2 cell are invalid. Dropping cell.");
+        reason = END_CIRC_REASON_CONNECTFAILED;
+        goto err;
+      }
+    } else {
+      log_info(LD_REND, "INTRODUCE2 cell does not contain authentication "
+               "data, but we require client authorization. Dropping cell.");
+      reason = END_CIRC_REASON_CONNECTFAILED;
+      goto err;
+    }
+  }
+
   /* Try DH handshake... */
   dh = crypto_dh_new();
   if (!dh || crypto_dh_generate_public(dh)<0) {
@@ -976,12 +1137,14 @@
            escaped_safe_str(extend_info->nickname), hexcookie, serviceid);
   tor_assert(launched->build_state);
   /* Fill in the circuit's state. */
-  memcpy(launched->rend_pk_digest, circuit->rend_pk_digest,
+  launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+  memcpy(launched->rend_data->rend_pk_digest,
+         circuit->rend_data->rend_pk_digest,
          DIGEST_LEN);
-  memcpy(launched->rend_cookie, r_cookie, REND_COOKIE_LEN);
-  strlcpy(launched->rend_query, service->service_id,
-          sizeof(launched->rend_query));
-  launched->rend_desc_version = service->descriptor_version;
+  memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
+  strlcpy(launched->rend_data->onion_address, service->service_id,
+          sizeof(launched->rend_data->onion_address));
+  launched->rend_data->rend_desc_version = service->descriptor_version;
   launched->build_state->pending_final_cpath = cpath =
     tor_malloc_zero(sizeof(crypt_path_t));
   cpath->magic = CRYPT_PATH_MAGIC;
@@ -1053,13 +1216,7 @@
   newstate->pending_final_cpath = oldstate->pending_final_cpath;
   oldstate->pending_final_cpath = NULL;
 
-  memcpy(newcirc->rend_query, oldcirc->rend_query,
-         REND_SERVICE_ID_LEN_BASE32+1);
-  memcpy(newcirc->rend_pk_digest, oldcirc->rend_pk_digest,
-         DIGEST_LEN);
-  memcpy(newcirc->rend_cookie, oldcirc->rend_cookie,
-         REND_COOKIE_LEN);
-  newcirc->rend_desc_version = oldcirc->rend_desc_version;
+  newcirc->rend_data = rend_data_dup(oldcirc->rend_data);
 }
 
 /** Launch a circuit to serve as an introduction point for the service
@@ -1105,10 +1262,11 @@
     intro->extend_info = extend_info_dup(launched->build_state->chosen_exit);
   }
 
-  strlcpy(launched->rend_query, service->service_id,
-          sizeof(launched->rend_query));
-  memcpy(launched->rend_pk_digest, service->pk_digest, DIGEST_LEN);
-  launched->rend_desc_version = service->descriptor_version;
+  launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+  strlcpy(launched->rend_data->onion_address, service->service_id,
+          sizeof(launched->rend_data->onion_address));
+  memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN);
+  launched->rend_data->rend_desc_version = service->descriptor_version;
   if (service->descriptor_version == 2)
     launched->intro_key = crypto_pk_dup_key(intro->intro_key);
   if (launched->_base.state == CIRCUIT_STATE_OPEN)
@@ -1133,12 +1291,14 @@
 
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
   tor_assert(circuit->cpath);
+  tor_assert(circuit->rend_data);
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
     log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
              serviceid, circuit->_base.n_circ_id);
@@ -1214,8 +1374,10 @@
              "received INTRO_ESTABLISHED cell on non-intro circuit.");
     goto err;
   }
+  tor_assert(circuit->rend_data);
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
     log_warn(LD_REND, "Unknown service on introduction circuit %d.",
              circuit->_base.n_circ_id);
@@ -1225,7 +1387,7 @@
   circuit->_base.purpose = CIRCUIT_PURPOSE_S_INTRO;
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
   log_info(LD_REND,
            "Received INTRO_ESTABLISHED cell on circuit %d for service %s",
            circuit->_base.n_circ_id, serviceid);
@@ -1252,12 +1414,13 @@
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
   tor_assert(circuit->cpath);
   tor_assert(circuit->build_state);
+  tor_assert(circuit->rend_data);
   hop = circuit->build_state->pending_final_cpath;
   tor_assert(hop);
 
-  base16_encode(hexcookie,9,circuit->rend_cookie,4);
+  base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4);
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 
   log_info(LD_REND,
            "Done building circuit %d to rendezvous with "
@@ -1265,7 +1428,8 @@
            circuit->_base.n_circ_id, hexcookie, serviceid);
 
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
     log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
              "introduction circuit.");
@@ -1274,7 +1438,7 @@
   }
 
   /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
-  memcpy(buf, circuit->rend_cookie, REND_COOKIE_LEN);
+  memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN);
   if (crypto_dh_get_public(hop->dh_handshake_state,
                            buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
     log_warn(LD_GENERAL,"Couldn't get DH public key.");
@@ -1336,7 +1500,8 @@
                                                   CIRCUIT_PURPOSE_S_INTRO))) {
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
-        circ->rend_desc_version == desc_version) {
+        circ->rend_data &&
+        circ->rend_data->rend_desc_version == desc_version) {
       return circ;
     }
   }
@@ -1346,7 +1511,8 @@
                                         CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
-        circ->rend_desc_version == desc_version) {
+        circ->rend_data &&
+        circ->rend_data->rend_desc_version == desc_version) {
       return circ;
     }
   }
@@ -1827,11 +1993,13 @@
   rend_service_port_config_t *chosen_port;
 
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+  tor_assert(circ->rend_data);
   log_debug(LD_REND,"beginning to hunt for addr/port");
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circ->rend_pk_digest, REND_SERVICE_ID_LEN);
-  service = rend_service_get_by_pk_digest_and_version(circ->rend_pk_digest,
-                                                      circ->rend_desc_version);
+                circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+  service = rend_service_get_by_pk_digest_and_version(
+                circ->rend_data->rend_pk_digest,
+                circ->rend_data->rend_desc_version);
   if (!service) {
     log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
              "rendezvous circuit %d; closing.",