Browse Source

Finish the client side of the PIR

Ian Goldberg 3 years ago
parent
commit
7082325f8f

+ 29 - 0
src/feature/dircache/dircache.c

@@ -1433,6 +1433,25 @@ dircache_pirserver_reply_params(dir_connection_t *conn,
     connection_buf_add(params, params_len, TO_CONN(conn));
 }
 
+/** Callback function when we have the PIR params to send back to the
+ *  client */
+void
+dircache_pirserver_reply_lookup(dir_connection_t *conn,
+    const char *reply, size_t reply_len)
+{
+    if (reply_len) {
+        log_info(LD_REND, "PIRSERVER Responding with PIR reply");
+        write_http_response_header_impl(conn, reply_len,
+                "application/octet-stream",
+                compression_method_get_name(NO_METHOD),
+                NULL, 0);
+        connection_buf_add(reply, reply_len, TO_CONN(conn));
+    } else {
+        log_info(LD_REND, "PIRSERVER Responding with unsuccessful PIR lookup");
+        write_short_http_response(conn, 404, "Not found");
+    }
+}
+
 /** Helper function for GET /tor/networkstatus-bridges
  */
 static int
@@ -1631,6 +1650,16 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers,
     goto done;
   }
 
+  /* Handle HS descriptor PIR query. */
+  if (connection_dir_is_encrypted(conn) && !strcmp(url, "/tor/pironion/query")) {
+    log_info(LD_REND, "PIRSERVER received PIR query");
+    if (hs_cache_pirserver_query(conn, body, body_len) < 0) {
+        write_short_http_response(conn, 404, "Not found");
+    }
+    goto done;
+  }
+
+
   if (!authdir_mode(options)) {
     /* we just provide cached directories; we don't want to
      * receive anything. */

+ 3 - 0
src/feature/dircache/dircache.h

@@ -43,4 +43,7 @@ STATIC unsigned parse_accept_encoding_header(const char *h);
 void dircache_pirserver_reply_params(dir_connection_t *conn,
     const char *params, size_t params_len);
 
+void dircache_pirserver_reply_lookup(dir_connection_t *conn,
+    const char *reply, size_t reply_len);
+
 #endif /* !defined(TOR_DIRCACHE_H) */

+ 88 - 34
src/feature/dirclient/dirclient.c

@@ -1055,7 +1055,8 @@ directory_request_set_payload(directory_request_t *req,
                               const char *payload,
                               size_t payload_len)
 {
-  tor_assert(DIR_PURPOSE_IS_UPLOAD(req->dir_purpose));
+  /* PIRONION: PIR fetches of hsdescs use POST */
+  tor_assert(DIR_PURPOSE_IS_UPLOAD(req->dir_purpose) || req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC);
 
   req->payload = payload;
   req->payload_len = payload_len;
@@ -1540,7 +1541,7 @@ directory_send_command(dir_connection_t *conn,
   /* NEEDS to be the same size hoststring.
    Will be decorated with brackets around it if it is ipv6. */
   char decorated_address[128];
-  smartlist_t *headers = smartlist_new();
+  smartlist_t *headers;
   char *url;
   char *accept_encoding;
   size_t url_len;
@@ -1555,6 +1556,21 @@ directory_send_command(dir_connection_t *conn,
   if (resource)
     conn->requested_resource = tor_strdup(resource);
 
+  /* If we are asked to fetch an hsdesc, and we have PIR parameters,
+   * ensure that the PIR query is constructed before continuing. */
+  if (purpose == DIR_PURPOSE_FETCH_HSDESC) {
+        HSClientPIRParams *pirparams =
+            hs_client_pirparams_lookup(req->routerstatus->identity_digest);
+        if (pirparams && pirparams->size > 0 && req->payload == NULL) {
+            /* This function will asynchronously construct the PIR
+             * request and call back to us when it's ready. */
+            hs_client_pir_create(directory_send_command, conn);
+            return;
+        }
+  }
+
+  headers = smartlist_new();
+
   /* decorate the ip address if it is ipv6 */
   if (strchr(conn->base_.address, ':')) {
     copy_ipv6_address(decorated_address, conn->base_.address,
@@ -1691,11 +1707,18 @@ directory_send_command(dir_connection_t *conn,
     case DIR_PURPOSE_FETCH_HSDESC:
       tor_assert(resource);
       tor_assert(strlen(resource) <= ED25519_BASE64_LEN);
-      tor_assert(!payload);
       if (getenv("PIR_CLIENT_PATH")) {
-        HSClientPIRParams *pirparams =
+        if (payload) {
+          conn->pirclient_state = PIRCLIENT_STATE_AWAITING_PIRREPLY;
+          httpcommand = "POST";
+          tor_asprintf(&url, "/tor/pironion/query");
+            log_info(LD_DIR, "PIRCLIENT Performing PIR query to %s",
+                safe_str_client(routerstatus_describe(req->routerstatus)));
+          break;
+        } else {
+          HSClientPIRParams *pirparams =
             hs_client_pirparams_lookup(req->routerstatus->identity_digest);
-        if (!pirparams) {
+          if (!pirparams) {
             /* First try to get the pironion params if we want them
              * but don't have them yet. */
             conn->pirclient_state = PIRCLIENT_STATE_AWAITING_PARAMS;
@@ -1704,16 +1727,7 @@ directory_send_command(dir_connection_t *conn,
             log_info(LD_DIR, "PIRCLIENT Fetching params from %s",
                 safe_str_client(routerstatus_describe(req->routerstatus)));
             break;
-        } else if (pirparams->size > 0) {
-            /* We have the params; privately fetch the resource */
-            /* PIRONION TODO: fill this in, but for now, fetch it not
-             * privately. */
-            // conn->pirclient_state = PIRCLIENT_STATE_AWAITING_PIRREPLY;
-            httpcommand = "GET";
-            tor_asprintf(&url, "/tor/hs/3/%s", resource);
-            log_info(LD_DIR, "PIRCLIENT PIR query (well, not yet) to %s",
-                safe_str_client(routerstatus_describe(req->routerstatus)));
-            break;
+          }
         }
       }
       /* Just fetch it normally, either because we're not configured to
@@ -2736,6 +2750,13 @@ handle_response_upload_signatures(dir_connection_t *conn,
   return 0;
 }
 
+struct connection_dir_client_conninfo_st {
+    char *address;
+    uint16_t port;
+    char identity_digest[DIGEST_LEN];
+    struct hs_ident_dir_conn_t *hs_ident;
+};
+
 /**
  * Handler function: processes a response to a request for a v3 hidden service
  * descriptor.
@@ -2788,38 +2809,67 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
             safe_str_client(routerstatus_describe(req->routerstatus)));
         directory_initiate_request(req);
         directory_request_free(req);
+    } else {
+        /* If something went wrong, returning -1 will signal that the
+         * hsdesc fetching process should be retried with another hsdir. */
+        return -1;
     }
 
     return 0;
   }
 
+  /* conn will get deallocated shortly after this function
+   * (handle_response_fetch_hsdesc_v3) completes, so we have to save
+   * anything in it we'll need in
+   * connection_dir_client_hsdesc_v3_received. */
+  connection_dir_client_conninfo_t conninfo =
+    tor_malloc_zero(sizeof(struct connection_dir_client_conninfo_st));
+  conninfo->address = tor_strdup(TO_CONN(conn)->address);
+  conninfo->port = TO_CONN(conn)->port;
+  memmove(conninfo->identity_digest, conn->identity_digest,
+        sizeof(conninfo->identity_digest));
+  conninfo->hs_ident = hs_ident_dir_conn_dup(conn->hs_ident);
+
   /* If we're waiting for a PIR response, decode it. */
-  if (conn->pirclient_state == PIRCLIENT_STATE_AWAITING_PIRREPLY) {
-    /* PIRONION TODO: Fill this in */
-        log_info(LD_REND,"PIRCLIENT received PIR reply from %s",
-            safe_str_client(routerstatus_describe(rstat)));
+  if (conn->pirclient_state == PIRCLIENT_STATE_AWAITING_PIRREPLY
+        && status_code == 200) {
+    log_info(LD_REND,"PIRCLIENT received PIR reply from %s statuscode %d",
+        safe_str_client(routerstatus_describe(rstat)), status_code);
+    hs_client_pir_extract(conninfo, conn->pirclient_queryid, body, body_len);
+    return 0;
   }
 
   log_info(LD_REND,"PIRCLIENT received hsdesc from %s",
       safe_str_client(routerstatus_describe(rstat)));
 
+  return connection_dir_client_hsdesc_v3_received(conninfo, body,
+            status_code, reason);
+}
+
+int
+connection_dir_client_hsdesc_v3_received(
+        connection_dir_client_conninfo_t conninfo,
+        const char *body, int status_code, const char *reason)
+{
   switch (status_code) {
   case 200:
     /* We got something: Try storing it in the cache. */
-    if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) {
+    if (hs_cache_store_as_client(body, &conninfo->hs_ident->identity_pk) < 0) {
       log_info(LD_REND, "Failed to store hidden service descriptor");
       /* Fire control port FAILED event. */
-      hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest,
+      hs_control_desc_event_failed(conninfo->hs_ident, conninfo->identity_digest,
                                    "BAD_DESC");
-      hs_control_desc_event_content(conn->hs_ident, conn->identity_digest,
+      hs_control_desc_event_content(conninfo->hs_ident, conninfo->identity_digest,
                                     NULL);
     } else {
       log_info(LD_REND, "Stored hidden service descriptor successfully.");
-      TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC;
-      hs_client_desc_has_arrived(conn->hs_ident);
+      /* Removed the next line, since conn will no longer be used after
+       * this function completes (or even before) */
+      /* TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; */
+      hs_client_desc_has_arrived(conninfo->hs_ident);
       /* Fire control port RECEIVED event. */
-      hs_control_desc_event_received(conn->hs_ident, conn->identity_digest);
-      hs_control_desc_event_content(conn->hs_ident, conn->identity_digest,
+      hs_control_desc_event_received(conninfo->hs_ident, conninfo->identity_digest);
+      hs_control_desc_event_content(conninfo->hs_ident, conninfo->identity_digest,
                                     body);
     }
     break;
@@ -2829,9 +2879,9 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
     log_info(LD_REND, "Fetching hidden service v3 descriptor not found: "
                       "Retrying at another directory.");
     /* Fire control port FAILED event. */
-    hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_failed(conninfo->hs_ident, conninfo->identity_digest,
                                  "NOT_FOUND");
-    hs_control_desc_event_content(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_content(conninfo->hs_ident, conninfo->identity_digest,
                                   NULL);
     break;
   case 400:
@@ -2840,25 +2890,29 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn,
                       "query? Retrying at another directory.",
              escaped(reason));
     /* Fire control port FAILED event. */
-    hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_failed(conninfo->hs_ident, conninfo->identity_digest,
                                  "QUERY_REJECTED");
-    hs_control_desc_event_content(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_content(conninfo->hs_ident, conninfo->identity_digest,
                                   NULL);
     break;
   default:
     log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
              "http status %d (%s) response unexpected from HSDir server "
              "'%s:%d'. Retrying at another directory.",
-             status_code, escaped(reason), TO_CONN(conn)->address,
-             TO_CONN(conn)->port);
+             status_code, escaped(reason), conninfo->address,
+             conninfo->port);
     /* Fire control port FAILED event. */
-    hs_control_desc_event_failed(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_failed(conninfo->hs_ident, conninfo->identity_digest,
                                  "UNEXPECTED");
-    hs_control_desc_event_content(conn->hs_ident, conn->identity_digest,
+    hs_control_desc_event_content(conninfo->hs_ident, conninfo->identity_digest,
                                   NULL);
     break;
   }
 
+  tor_free(conninfo->address);
+  hs_ident_dir_conn_free(conninfo->hs_ident);
+  tor_free(conninfo);
+
   return 0;
 }
 

+ 6 - 0
src/feature/dirclient/dirclient.h

@@ -94,6 +94,12 @@ void connection_dir_client_request_failed(dir_connection_t *conn);
 void connection_dir_client_refetch_hsdesc_if_needed(
                                           dir_connection_t *dir_conn);
 
+typedef struct connection_dir_client_conninfo_st *connection_dir_client_conninfo_t;
+
+int connection_dir_client_hsdesc_v3_received(
+    connection_dir_client_conninfo_t conninfo,
+    const char *body, int status_code, const char *reason);
+
 #ifdef DIRCLIENT_PRIVATE
 struct directory_request_t {
   /**

+ 2 - 0
src/feature/dircommon/dir_connection_st.h

@@ -63,6 +63,8 @@ struct dir_connection_t {
 
   dir_connection_pirclient_state_t pirclient_state;
 
+  char pirclient_queryid[8];
+
 #ifdef MEASUREMENTS_21206
   /** Number of RELAY_DATA cells received. */
   uint32_t data_cells_received;

+ 38 - 2
src/feature/hs/hs_cache.c

@@ -981,13 +981,26 @@ static void
 hs_cache_pirserver_received(const unsigned char *hdrbuf,
     const char *bodybuf, size_t bodylen)
 {
-    /* PIRONION TODO: actually deliver the received message */
-    log_info(LD_DIRSERV,"PIRSERVER response header %p type %02x body len %ld body %s", *(void**)hdrbuf, hdrbuf[8], bodylen, escaped(bodybuf));
+    /* PIRONION TODO: as elsewhere, conn may have gone away before we
+     * get here.  In the future, hook the function that closes a
+     * dir_connection_t. */
+    log_info(LD_DIRSERV,"PIRSERVER response header %p type %02x body len %ld",
+            *(void**)hdrbuf, hdrbuf[8], bodylen);
     if (hdrbuf[8] == PIRSERVER_RESPONSE_PARAMS) {
         /* Send the params back to the client */
         dir_connection_t *conn;
         memmove((char *)(&conn), hdrbuf, sizeof(conn));
         dircache_pirserver_reply_params(conn, bodybuf, bodylen);
+    } else if (hdrbuf[8] == PIRSERVER_RESPONSE_LOOKUP_SUCCESS) {
+        /* Send the PIR response back to the client */
+        dir_connection_t *conn;
+        memmove((char *)(&conn), hdrbuf, sizeof(conn));
+        dircache_pirserver_reply_lookup(conn, bodybuf, bodylen);
+    } else if (hdrbuf[8] == PIRSERVER_RESPONSE_LOOKUP_FAILURE) {
+        /* Send an error response back to the client */
+        dir_connection_t *conn;
+        memmove((char *)(&conn), hdrbuf, sizeof(conn));
+        dircache_pirserver_reply_lookup(conn, NULL, 0);
     }
 }
 
@@ -1077,6 +1090,29 @@ hs_cache_pirserver_get_params(dir_connection_t *conn)
     return 0;
 }
 
+/* Helper function for POST /tor/pironion/query */
+int
+hs_cache_pirserver_query(dir_connection_t *conn, const char *body,
+        size_t body_len)
+{
+    int res;
+    unsigned char hdr[PIRPROCESS_HDR_SIZE];
+
+    /* PIRSERVER TODO: as hs_cache_pirserver_get_params, above */
+    memmove(hdr, (const char *)(&conn), sizeof(conn));
+    hdr[8] = PIRSERVER_REQUEST_LOOKUP;
+    *(uint32_t*)(hdr+9) = htonl(body_len);
+    res = hs_cache_pirserver_send(hdr, PIRPROCESS_HDR_SIZE);
+    if (res < PIRPROCESS_HDR_SIZE) {
+        return -1;
+    }
+    res = hs_cache_pirserver_send((const unsigned char *)body, body_len);
+    if (res < (int)body_len) {
+        return -1;
+    }
+    return 0;
+}
+
 /* Initialize the hidden service cache subsystem. */
 void
 hs_cache_init(void)

+ 1 - 0
src/feature/hs/hs_cache.h

@@ -100,6 +100,7 @@ void hs_cache_client_intro_state_clean(time_t now);
 void hs_cache_client_intro_state_purge(void);
 
 int hs_cache_pirserver_get_params(dir_connection_t *conn);
+int hs_cache_pirserver_query(dir_connection_t *conn, const char *body, size_t body_len);
 
 #ifdef HS_CACHE_PRIVATE
 #include "lib/crypt_ops/crypto_ed25519.h"

+ 118 - 3
src/feature/hs/hs_client.c

@@ -45,6 +45,7 @@
 
 #include "feature/hs/hs_pirprocess.h"
 #include "lib/wallclock/approx_time.h"
+#include "feature/nodelist/node_st.h"
 
 /* Client-side authorizations for hidden services; map of service identity
  * public key to hs_client_service_authorization_t *. */
@@ -1351,6 +1352,9 @@ hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk)
 
   tor_assert(identity_pk);
 
+  log_info(LD_REND, "PIRCLIENT refetching %s",
+          safe_str_client(ed25519_fmt(identity_pk)));
+
   if (!can_client_refetch_desc(identity_pk, &status)) {
     return status;
   }
@@ -1962,6 +1966,11 @@ pirparams_clear(void)
     digestmap_free(hsdir_pir_params, pirparams_free);
 }
 
+typedef struct {
+    void (*callback)(dir_connection_t *, int, const directory_request_t *);
+    dir_connection_t *conn;
+} HSClientPIRCallback;
+
 static pir_process_t pirclient;
 
 #define PIRCLIENT_REQUEST_CREATE 0x41
@@ -1981,10 +1990,45 @@ static void
 hs_client_pirclient_received(const unsigned char *hdrbuf,
     const char *bodybuf, size_t bodylen)
 {
-    log_info(LD_REND, "PIRCLIENT response header %p type %02x body len %ld body %s",
-        (void *)hdrbuf, hdrbuf[8], bodylen, escaped(bodybuf));
-    if (hdrbuf[8] == PIRCLIENT_RESPONSE_CREATE) {
+    log_info(LD_REND, "PIRCLIENT response header %p type %02x body len %ld",
+        (void *)hdrbuf, hdrbuf[8], bodylen);
+    if (hdrbuf[8] == PIRCLIENT_RESPONSE_CREATE && bodylen > 8) {
+        HSClientPIRCallback *cb;
+        memmove(&cb, hdrbuf, sizeof(cb));
+        /* PIRONION TODO: As with the server, it's possible the conn
+         * contained in this structure might have closed on us.  In the
+         * future, hook the callback that closes a dir_connection_t. */
+        const node_t *node = node_get_by_id(cb->conn->identity_digest);
+        if (!node) goto createerr;
+        const routerstatus_t *rstat = node->rs;
+        if (!rstat) goto createerr;
+        directory_request_t *req =
+            directory_request_new(DIR_PURPOSE_FETCH_HSDESC);
+        directory_request_set_routerstatus(req, rstat);
+        directory_request_set_indirection(req, DIRIND_ANONYMOUS);
+        directory_request_set_resource(req, cb->conn->requested_resource);
+        directory_request_set_payload(req, bodybuf+8, bodylen-8);
+        directory_request_fetch_set_hs_ident(req, cb->conn->hs_ident);
+        memmove(cb->conn->pirclient_queryid, bodybuf, 8);
+        log_info(LD_REND, "PIRCLIENT Refetching %s from %s",
+            cb->conn->requested_resource,
+            safe_str_client(routerstatus_describe(rstat)));
+        (cb->callback)(cb->conn, 0, req);
+        directory_request_free(req);
+
+    createerr:
+        tor_free(cb);
+        return;
     } else if (hdrbuf[8] == PIRCLIENT_RESPONSE_EXTRACT) {
+        connection_dir_client_conninfo_t conninfo;
+        memmove(&conninfo, hdrbuf, sizeof(conninfo));
+        if (bodylen > 0) {
+            connection_dir_client_hsdesc_v3_received(conninfo,
+                bodybuf, 200, "OK");
+        } else {
+            connection_dir_client_hsdesc_v3_received(conninfo,
+                NULL, 404, "Not found");
+        }
     }
 }
 
@@ -2002,6 +2046,77 @@ hs_client_pirclient_send(const unsigned char *buf, size_t len)
     return hs_pirprocess_send(pirclient, buf, len);
 }
 
+/* Initiate the creation of a PIR request by the pirclient process.
+ * When it is done, call back the callback function with the given
+ * parameters, and a new req having req->payload filled in with the
+ * PIR request. */
+void
+hs_client_pir_create(
+    void (*callback)(dir_connection_t *, int, const directory_request_t *),
+    dir_connection_t *conn)
+{
+    HSClientPIRParams *pirparams =
+        hs_client_pirparams_lookup(conn->identity_digest);
+    HSClientPIRCallback *cb;
+    unsigned char hdr[PIRPROCESS_HDR_SIZE];
+    size_t bodylen;
+    char resource[32];
+
+    if (pirparams == NULL) return;
+    cb = tor_malloc_zero(sizeof(HSClientPIRCallback));
+    if (cb == NULL) return;
+    cb->callback = callback;
+    cb->conn = conn;
+    memmove(hdr, (const char *)(&cb), sizeof(cb));
+    hdr[8] = PIRCLIENT_REQUEST_CREATE;
+    bodylen = 32 + pirparams->size;
+    *(uint32_t*)(hdr+9) = htonl(bodylen);
+
+    digest256_from_base64(resource, conn->requested_resource);
+    log_info(LD_REND, "PIRCLIENT creating PIR request for %s", escaped(resource));
+
+    if (hs_client_pirclient_send(hdr, PIRPROCESS_HDR_SIZE) <
+            PIRPROCESS_HDR_SIZE) {
+        return;
+    }
+    if (hs_client_pirclient_send((const unsigned char*)resource, 32) < 32) {
+        return;
+    }
+    if (hs_client_pirclient_send((const unsigned char *)pirparams->params,
+            pirparams->size) < (int)pirparams->size) {
+        return;
+    }
+}
+
+/* Initiate the extraction of a PIR reply by the pirclient process. */
+void
+hs_client_pir_extract(connection_dir_client_conninfo_t conninfo,
+    const char queryid[8], const char *body, size_t body_len)
+{
+    unsigned char hdr[PIRPROCESS_HDR_SIZE];
+    size_t totlen;
+
+    memmove(hdr, &conninfo, sizeof(conninfo));
+    hdr[8] = PIRCLIENT_REQUEST_EXTRACT;
+    totlen = 8 + body_len;
+    *(uint32_t*)(hdr+9) = htonl(totlen);
+
+    log_info(LD_REND, "PIRCLIENT extracting PIR reply");
+
+    if (hs_client_pirclient_send(hdr, PIRPROCESS_HDR_SIZE) <
+            PIRPROCESS_HDR_SIZE) {
+        return;
+    }
+    if (hs_client_pirclient_send(
+            (const unsigned char*)(queryid), 8) < 8) {
+        return;
+    }
+    if (hs_client_pirclient_send((const unsigned char *)body, body_len)
+            < (int)body_len) {
+        return;
+    }
+}
+
 /* Release all the storage held by the client subsystem. */
 void
 hs_client_free_all(void)

+ 8 - 1
src/feature/hs/hs_client.h

@@ -12,6 +12,7 @@
 #include "lib/crypt_ops/crypto_ed25519.h"
 #include "feature/hs/hs_descriptor.h"
 #include "feature/hs/hs_ident.h"
+#include "feature/dirclient/dirclient.h"
 
 /* Status code of a descriptor fetch request. */
 typedef enum {
@@ -91,8 +92,14 @@ typedef struct {
 
 void hs_client_pir_init(void);
 
-void hs_client_pirparams_insert(const char *digest, size_t size, const char *params);
+void hs_client_pirparams_insert(const char *digest, size_t size,
+    const char *params);
 HSClientPIRParams* hs_client_pirparams_lookup(const char *digest);
+void hs_client_pir_create(
+    void (*callback)(dir_connection_t *, int, const directory_request_t *),
+    dir_connection_t *conn);
+void hs_client_pir_extract(connection_dir_client_conninfo_t conninfo,
+    const char queryid[8], const char *body, size_t body_len);
 
 #ifdef HS_CLIENT_PRIVATE
 

+ 2 - 1
src/feature/hs/hs_pirprocess.c

@@ -58,7 +58,8 @@ pirprocess_stdoutcb(evutil_socket_t fd, short what, void *arg)
                     (handle->hdrbuf+PIRPROCESS_HDR_SIZE-4));
             tor_free(handle->bodybuf);
             if (handle->readleft > 0) {
-                handle->bodybuf = tor_malloc(handle->readleft);
+                handle->bodybuf = tor_malloc(handle->readleft+1);
+                handle->bodybuf[handle->readleft] = '\0';
                 handle->readoff = 0;
                 handle->readstate = PIRPROCESS_READSTATE_BODY;
             } else {