[tor-commits] [tor/master] Split getinfo handling into a new control_getinfo.c

asn at torproject.org asn at torproject.org
Tue Mar 26 14:41:25 UTC 2019


commit 4754e9058b7521967dd92092e54483cec456bb2f
Author: Nick Mathewson <nickm at torproject.org>
Date:   Mon Mar 25 12:48:52 2019 -0400

    Split getinfo handling into a new control_getinfo.c
---
 scripts/maint/practracker/exceptions.txt |    7 +-
 src/core/include.am                      |    4 +-
 src/feature/control/control.c            | 1661 +-----------------------------
 src/feature/control/control.h            |   44 +-
 src/feature/control/control_events.c     |   37 +
 src/feature/control/control_events.h     |    8 +
 src/feature/control/control_fmt.c        |   10 +
 src/feature/control/control_fmt.h        |    1 +
 src/feature/control/control_getinfo.c    | 1661 ++++++++++++++++++++++++++++++
 src/feature/control/control_getinfo.h    |   57 +
 src/test/test_controller.c               |    2 +
 src/test/test_dir.c                      |    4 +-
 12 files changed, 1803 insertions(+), 1693 deletions(-)

diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt
index 15f16fdb4..142fda580 100644
--- a/scripts/maint/practracker/exceptions.txt
+++ b/scripts/maint/practracker/exceptions.txt
@@ -112,9 +112,9 @@ problem function-size /src/feature/dircommon/consdiff.c:apply_ed_diff() 159
 problem file-size /src/feature/control/control.c 7592
 problem include-count /src/feature/control/control.c 90
 problem function-size /src/feature/control/control.c:handle_control_authenticate() 188
-problem function-size /src/feature/control/control.c:getinfo_helper_misc() 109
-problem function-size /src/feature/control/control.c:getinfo_helper_dir() 304
-problem function-size /src/feature/control/control.c:getinfo_helper_events() 236
+problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_misc() 109
+problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_dir() 304
+problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_events() 236
 problem function-size /src/feature/control/control.c:handle_control_extendcircuit() 151
 problem function-size /src/feature/control/control.c:handle_control_authchallenge() 115
 problem function-size /src/feature/control/control.c:handle_control_hsfetch() 114
@@ -123,6 +123,7 @@ problem function-size /src/feature/control/control.c:handle_control_add_onion()
 problem function-size /src/feature/control/control.c:add_onion_helper_keyarg() 125
 problem function-size /src/feature/control/control.c:connection_control_process_inbuf() 239
 problem function-size /src/feature/control/control_events.c:control_event_stream_status() 119
+problem include-count /src/feature/control/control_getinfo.c 51
 problem function-size /src/feature/stats/rephist.c:rep_hist_load_mtbf_data() 185
 problem function-size /src/feature/stats/rephist.c:rep_hist_format_exit_stats() 148
 problem function-size /src/feature/dircache/consdiffmgr.c:consdiffmgr_cleanup() 115
diff --git a/src/core/include.am b/src/core/include.am
index 58fcb784f..a8ec36059 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -73,6 +73,7 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/control/control_bootstrap.c	\
 	src/feature/control/control_events.c	\
 	src/feature/control/control_fmt.c	\
+	src/feature/control/control_getinfo.c	\
 	src/feature/control/fmt_serverstatus.c  \
 	src/feature/control/getinfo_geoip.c	\
 	src/feature/dirauth/keypin.c		\
@@ -291,7 +292,8 @@ noinst_HEADERS +=					\
 	src/feature/control/control.h			\
 	src/feature/control/control_connection_st.h	\
 	src/feature/control/control_events.h	        \
-	src/feature/control/control_fmt.h                \
+	src/feature/control/control_fmt.h		\
+	src/feature/control/control_getinfo.h		\
 	src/feature/control/fmt_serverstatus.h		\
 	src/feature/control/getinfo_geoip.h		\
 	src/feature/dirauth/authmode.h			\
diff --git a/src/feature/control/control.c b/src/feature/control/control.c
index d56ff41b0..94643efde 100644
--- a/src/feature/control/control.c
+++ b/src/feature/control/control.c
@@ -65,6 +65,7 @@
 #include "feature/control/control.h"
 #include "feature/control/control_events.h"
 #include "feature/control/control_fmt.h"
+#include "feature/control/control_getinfo.h"
 #include "feature/control/fmt_serverstatus.h"
 #include "feature/control/getinfo_geoip.h"
 #include "feature/dircache/dirserv.h"
@@ -178,9 +179,6 @@ static int handle_control_signal(control_connection_t *conn, uint32_t len,
                                  const char *body);
 static int handle_control_mapaddress(control_connection_t *conn, uint32_t len,
                                      const char *body);
-static char *list_getinfo_options(void);
-static int handle_control_getinfo(control_connection_t *conn, uint32_t len,
-                                  const char *body);
 static int handle_control_extendcircuit(control_connection_t *conn,
                                         uint32_t len,
                                         const char *body);
@@ -214,8 +212,6 @@ static int handle_control_add_onion(control_connection_t *conn, uint32_t len,
 static int handle_control_del_onion(control_connection_t *conn, uint32_t len,
                                     const char *body);
 
-static char * download_status_to_string(const download_status_t *dl);
-
 /** Convert a connection_t* to an control_connection_t*; assert if the cast is
  * invalid. */
 control_connection_t *
@@ -225,16 +221,6 @@ TO_CONTROL_CONN(connection_t *c)
   return DOWNCAST(control_connection_t, c);
 }
 
-/** Append a NUL-terminated string <b>s</b> to the end of
- * <b>conn</b>-\>outbuf.
- */
-static inline void
-connection_write_str_to_buf(const char *s, control_connection_t *conn)
-{
-  size_t len = strlen(s);
-  connection_buf_add(s, len, TO_CONN(conn));
-}
-
 /** If the first <b>in_len_max</b> characters in <b>start</b> contain a
  * double-quoted string with escaped characters, return the length of that
  * string (as encoded, including quotes).  Otherwise return -1. */
@@ -684,48 +670,6 @@ handle_control_loadconf(control_connection_t *conn, uint32_t len,
   return 0;
 }
 
-/** Helper structure: maps event values to their names. */
-struct control_event_t {
-  uint16_t event_code;
-  const char *event_name;
-};
-/** Table mapping event values to their names.  Used to implement SETEVENTS
- * and GETINFO events/names, and to keep they in sync. */
-static const struct control_event_t control_event_table[] = {
-  { EVENT_CIRCUIT_STATUS, "CIRC" },
-  { EVENT_CIRCUIT_STATUS_MINOR, "CIRC_MINOR" },
-  { EVENT_STREAM_STATUS, "STREAM" },
-  { EVENT_OR_CONN_STATUS, "ORCONN" },
-  { EVENT_BANDWIDTH_USED, "BW" },
-  { EVENT_DEBUG_MSG, "DEBUG" },
-  { EVENT_INFO_MSG, "INFO" },
-  { EVENT_NOTICE_MSG, "NOTICE" },
-  { EVENT_WARN_MSG, "WARN" },
-  { EVENT_ERR_MSG, "ERR" },
-  { EVENT_NEW_DESC, "NEWDESC" },
-  { EVENT_ADDRMAP, "ADDRMAP" },
-  { EVENT_DESCCHANGED, "DESCCHANGED" },
-  { EVENT_NS, "NS" },
-  { EVENT_STATUS_GENERAL, "STATUS_GENERAL" },
-  { EVENT_STATUS_CLIENT, "STATUS_CLIENT" },
-  { EVENT_STATUS_SERVER, "STATUS_SERVER" },
-  { EVENT_GUARD, "GUARD" },
-  { EVENT_STREAM_BANDWIDTH_USED, "STREAM_BW" },
-  { EVENT_CLIENTS_SEEN, "CLIENTS_SEEN" },
-  { EVENT_NEWCONSENSUS, "NEWCONSENSUS" },
-  { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" },
-  { EVENT_GOT_SIGNAL, "SIGNAL" },
-  { EVENT_CONF_CHANGED, "CONF_CHANGED"},
-  { EVENT_CONN_BW, "CONN_BW" },
-  { EVENT_CELL_STATS, "CELL_STATS" },
-  { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" },
-  { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" },
-  { EVENT_HS_DESC, "HS_DESC" },
-  { EVENT_HS_DESC_CONTENT, "HS_DESC_CONTENT" },
-  { EVENT_NETWORK_LIVENESS, "NETWORK_LIVENESS" },
-  { 0, NULL },
-};
-
 /** Called when we get a SETEVENTS message: update conn->event_mask,
  * and reply with DONE or ERROR. */
 static int
@@ -1034,12 +978,7 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len,
   return 0;
 }
 
-struct signal_t {
-  int sig;
-  const char *signal_name;
-};
-
-static const struct signal_t signal_table[] = {
+const struct signal_name_t signal_table[] = {
   { SIGHUP, "RELOAD" },
   { SIGHUP, "HUP" },
   { SIGINT, "SHUTDOWN" },
@@ -1240,1593 +1179,6 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len,
   return 0;
 }
 
-/** Implementation helper for GETINFO: knows the answers for various
- * trivial-to-implement questions. */
-static int
-getinfo_helper_misc(control_connection_t *conn, const char *question,
-                    char **answer, const char **errmsg)
-{
-  (void) conn;
-  if (!strcmp(question, "version")) {
-    *answer = tor_strdup(get_version());
-  } else if (!strcmp(question, "bw-event-cache")) {
-    *answer = get_bw_samples();
-  } else if (!strcmp(question, "config-file")) {
-    const char *a = get_torrc_fname(0);
-    if (a)
-      *answer = tor_strdup(a);
-  } else if (!strcmp(question, "config-defaults-file")) {
-    const char *a = get_torrc_fname(1);
-    if (a)
-      *answer = tor_strdup(a);
-  } else if (!strcmp(question, "config-text")) {
-    *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
-  } else if (!strcmp(question, "config-can-saveconf")) {
-    *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1");
-  } else if (!strcmp(question, "info/names")) {
-    *answer = list_getinfo_options();
-  } else if (!strcmp(question, "dormant")) {
-    int dormant = rep_hist_circbuilding_dormant(time(NULL));
-    *answer = tor_strdup(dormant ? "1" : "0");
-  } else if (!strcmp(question, "events/names")) {
-    int i;
-    smartlist_t *event_names = smartlist_new();
-
-    for (i = 0; control_event_table[i].event_name != NULL; ++i) {
-      smartlist_add(event_names, (char *)control_event_table[i].event_name);
-    }
-
-    *answer = smartlist_join_strings(event_names, " ", 0, NULL);
-
-    smartlist_free(event_names);
-  } else if (!strcmp(question, "signal/names")) {
-    smartlist_t *signal_names = smartlist_new();
-    int j;
-    for (j = 0; signal_table[j].signal_name != NULL; ++j) {
-      smartlist_add(signal_names, (char*)signal_table[j].signal_name);
-    }
-
-    *answer = smartlist_join_strings(signal_names, " ", 0, NULL);
-
-    smartlist_free(signal_names);
-  } else if (!strcmp(question, "features/names")) {
-    *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS");
-  } else if (!strcmp(question, "address")) {
-    uint32_t addr;
-    if (router_pick_published_address(get_options(), &addr, 0) < 0) {
-      *errmsg = "Address unknown";
-      return -1;
-    }
-    *answer = tor_dup_ip(addr);
-  } else if (!strcmp(question, "traffic/read")) {
-    tor_asprintf(answer, "%"PRIu64, (get_bytes_read()));
-  } else if (!strcmp(question, "traffic/written")) {
-    tor_asprintf(answer, "%"PRIu64, (get_bytes_written()));
-  } else if (!strcmp(question, "uptime")) {
-    long uptime_secs = get_uptime();
-    tor_asprintf(answer, "%ld", uptime_secs);
-  } else if (!strcmp(question, "process/pid")) {
-    int myPid = -1;
-
-#ifdef _WIN32
-      myPid = _getpid();
-#else
-      myPid = getpid();
-#endif
-
-    tor_asprintf(answer, "%d", myPid);
-  } else if (!strcmp(question, "process/uid")) {
-#ifdef _WIN32
-      *answer = tor_strdup("-1");
-#else
-      int myUid = geteuid();
-      tor_asprintf(answer, "%d", myUid);
-#endif /* defined(_WIN32) */
-  } else if (!strcmp(question, "process/user")) {
-#ifdef _WIN32
-      *answer = tor_strdup("");
-#else
-      int myUid = geteuid();
-      const struct passwd *myPwEntry = tor_getpwuid(myUid);
-
-      if (myPwEntry) {
-        *answer = tor_strdup(myPwEntry->pw_name);
-      } else {
-        *answer = tor_strdup("");
-      }
-#endif /* defined(_WIN32) */
-  } else if (!strcmp(question, "process/descriptor-limit")) {
-    int max_fds = get_max_sockets();
-    tor_asprintf(answer, "%d", max_fds);
-  } else if (!strcmp(question, "limits/max-mem-in-queues")) {
-    tor_asprintf(answer, "%"PRIu64,
-                 (get_options()->MaxMemInQueues));
-  } else if (!strcmp(question, "fingerprint")) {
-    crypto_pk_t *server_key;
-    if (!server_mode(get_options())) {
-      *errmsg = "Not running in server mode";
-      return -1;
-    }
-    server_key = get_server_identity_key();
-    *answer = tor_malloc(HEX_DIGEST_LEN+1);
-    crypto_pk_get_fingerprint(server_key, *answer, 0);
-  }
-  return 0;
-}
-
-/** Awful hack: return a newly allocated string based on a routerinfo and
- * (possibly) an extrainfo, sticking the read-history and write-history from
- * <b>ei</b> into the resulting string.  The thing you get back won't
- * necessarily have a valid signature.
- *
- * New code should never use this; it's for backward compatibility.
- *
- * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might
- * not be NUL-terminated. */
-static char *
-munge_extrainfo_into_routerinfo(const char *ri_body,
-                                const signed_descriptor_t *ri,
-                                const signed_descriptor_t *ei)
-{
-  char *out = NULL, *outp;
-  int i;
-  const char *router_sig;
-  const char *ei_body = signed_descriptor_get_body(ei);
-  size_t ri_len = ri->signed_descriptor_len;
-  size_t ei_len = ei->signed_descriptor_len;
-  if (!ei_body)
-    goto bail;
-
-  outp = out = tor_malloc(ri_len+ei_len+1);
-  if (!(router_sig = tor_memstr(ri_body, ri_len, "\nrouter-signature")))
-    goto bail;
-  ++router_sig;
-  memcpy(out, ri_body, router_sig-ri_body);
-  outp += router_sig-ri_body;
-
-  for (i=0; i < 2; ++i) {
-    const char *kwd = i ? "\nwrite-history " : "\nread-history ";
-    const char *cp, *eol;
-    if (!(cp = tor_memstr(ei_body, ei_len, kwd)))
-      continue;
-    ++cp;
-    if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body))))
-      continue;
-    memcpy(outp, cp, eol-cp+1);
-    outp += eol-cp+1;
-  }
-  memcpy(outp, router_sig, ri_len - (router_sig-ri_body));
-  *outp++ = '\0';
-  tor_assert(outp-out < (int)(ri_len+ei_len+1));
-
-  return out;
- bail:
-  tor_free(out);
-  return tor_strndup(ri_body, ri->signed_descriptor_len);
-}
-
-/** Implementation helper for GETINFO: answers requests for information about
- * which ports are bound. */
-static int
-getinfo_helper_listeners(control_connection_t *control_conn,
-                         const char *question,
-                         char **answer, const char **errmsg)
-{
-  int type;
-  smartlist_t *res;
-
-  (void)control_conn;
-  (void)errmsg;
-
-  if (!strcmp(question, "net/listeners/or"))
-    type = CONN_TYPE_OR_LISTENER;
-  else if (!strcmp(question, "net/listeners/extor"))
-    type = CONN_TYPE_EXT_OR_LISTENER;
-  else if (!strcmp(question, "net/listeners/dir"))
-    type = CONN_TYPE_DIR_LISTENER;
-  else if (!strcmp(question, "net/listeners/socks"))
-    type = CONN_TYPE_AP_LISTENER;
-  else if (!strcmp(question, "net/listeners/trans"))
-    type = CONN_TYPE_AP_TRANS_LISTENER;
-  else if (!strcmp(question, "net/listeners/natd"))
-    type = CONN_TYPE_AP_NATD_LISTENER;
-  else if (!strcmp(question, "net/listeners/httptunnel"))
-    type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER;
-  else if (!strcmp(question, "net/listeners/dns"))
-    type = CONN_TYPE_AP_DNS_LISTENER;
-  else if (!strcmp(question, "net/listeners/control"))
-    type = CONN_TYPE_CONTROL_LISTENER;
-  else
-    return 0; /* unknown key */
-
-  res = smartlist_new();
-  SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) {
-    struct sockaddr_storage ss;
-    socklen_t ss_len = sizeof(ss);
-
-    if (conn->type != type || conn->marked_for_close || !SOCKET_OK(conn->s))
-      continue;
-
-    if (getsockname(conn->s, (struct sockaddr *)&ss, &ss_len) < 0) {
-      smartlist_add_asprintf(res, "%s:%d", conn->address, (int)conn->port);
-    } else {
-      char *tmp = tor_sockaddr_to_str((struct sockaddr *)&ss);
-      smartlist_add(res, esc_for_log(tmp));
-      tor_free(tmp);
-    }
-
-  } SMARTLIST_FOREACH_END(conn);
-
-  *answer = smartlist_join_strings(res, " ", 0, NULL);
-
-  SMARTLIST_FOREACH(res, char *, cp, tor_free(cp));
-  smartlist_free(res);
-  return 0;
-}
-
-/** Implementation helper for GETINFO: answers requests for information about
- * the current time in both local and UTC forms. */
-STATIC int
-getinfo_helper_current_time(control_connection_t *control_conn,
-                         const char *question,
-                         char **answer, const char **errmsg)
-{
-  (void)control_conn;
-  (void)errmsg;
-
-  struct timeval now;
-  tor_gettimeofday(&now);
-  char timebuf[ISO_TIME_LEN+1];
-
-  if (!strcmp(question, "current-time/local"))
-    format_local_iso_time_nospace(timebuf, (time_t)now.tv_sec);
-  else if (!strcmp(question, "current-time/utc"))
-    format_iso_time_nospace(timebuf, (time_t)now.tv_sec);
-  else
-    return 0;
-
-  *answer = tor_strdup(timebuf);
-  return 0;
-}
-
-/** Implementation helper for GETINFO: knows the answers for questions about
- * directory information. */
-STATIC int
-getinfo_helper_dir(control_connection_t *control_conn,
-                   const char *question, char **answer,
-                   const char **errmsg)
-{
-  (void) control_conn;
-  if (!strcmpstart(question, "desc/id/")) {
-    const routerinfo_t *ri = NULL;
-    const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0);
-    if (node)
-      ri = node->ri;
-    if (ri) {
-      const char *body = signed_descriptor_get_body(&ri->cache_info);
-      if (body)
-        *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
-    } else if (! we_fetch_router_descriptors(get_options())) {
-      /* Descriptors won't be available, provide proper error */
-      *errmsg = "We fetch microdescriptors, not router "
-                "descriptors. You'll need to use md/id/* "
-                "instead of desc/id/*.";
-      return 0;
-    }
-  } else if (!strcmpstart(question, "desc/name/")) {
-    const routerinfo_t *ri = NULL;
-    /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the
-     * warning goes to the user, not to the controller. */
-    const node_t *node =
-      node_get_by_nickname(question+strlen("desc/name/"), 0);
-    if (node)
-      ri = node->ri;
-    if (ri) {
-      const char *body = signed_descriptor_get_body(&ri->cache_info);
-      if (body)
-        *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
-    } else if (! we_fetch_router_descriptors(get_options())) {
-      /* Descriptors won't be available, provide proper error */
-      *errmsg = "We fetch microdescriptors, not router "
-                "descriptors. You'll need to use md/name/* "
-                "instead of desc/name/*.";
-      return 0;
-    }
-  } else if (!strcmp(question, "desc/download-enabled")) {
-    int r = we_fetch_router_descriptors(get_options());
-    tor_asprintf(answer, "%d", !!r);
-  } else if (!strcmp(question, "desc/all-recent")) {
-    routerlist_t *routerlist = router_get_routerlist();
-    smartlist_t *sl = smartlist_new();
-    if (routerlist && routerlist->routers) {
-      SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri,
-      {
-        const char *body = signed_descriptor_get_body(&ri->cache_info);
-        if (body)
-          smartlist_add(sl,
-                  tor_strndup(body, ri->cache_info.signed_descriptor_len));
-      });
-    }
-    *answer = smartlist_join_strings(sl, "", 0, NULL);
-    SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
-    smartlist_free(sl);
-  } else if (!strcmp(question, "desc/all-recent-extrainfo-hack")) {
-    /* XXXX Remove this once Torstat asks for extrainfos. */
-    routerlist_t *routerlist = router_get_routerlist();
-    smartlist_t *sl = smartlist_new();
-    if (routerlist && routerlist->routers) {
-      SMARTLIST_FOREACH_BEGIN(routerlist->routers, const routerinfo_t *, ri) {
-        const char *body = signed_descriptor_get_body(&ri->cache_info);
-        signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest(
-                                     ri->cache_info.extra_info_digest);
-        if (ei && body) {
-          smartlist_add(sl, munge_extrainfo_into_routerinfo(body,
-                                                        &ri->cache_info, ei));
-        } else if (body) {
-          smartlist_add(sl,
-                  tor_strndup(body, ri->cache_info.signed_descriptor_len));
-        }
-      } SMARTLIST_FOREACH_END(ri);
-    }
-    *answer = smartlist_join_strings(sl, "", 0, NULL);
-    SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
-    smartlist_free(sl);
-  } else if (!strcmpstart(question, "hs/client/desc/id/")) {
-    hostname_type_t addr_type;
-
-    question += strlen("hs/client/desc/id/");
-    if (rend_valid_v2_service_id(question)) {
-      addr_type = ONION_V2_HOSTNAME;
-    } else if (hs_address_is_valid(question)) {
-      addr_type = ONION_V3_HOSTNAME;
-    } else {
-      *errmsg = "Invalid address";
-      return -1;
-    }
-
-    if (addr_type == ONION_V2_HOSTNAME) {
-      rend_cache_entry_t *e = NULL;
-      if (!rend_cache_lookup_entry(question, -1, &e)) {
-        /* Descriptor found in cache */
-        *answer = tor_strdup(e->desc);
-      } else {
-        *errmsg = "Not found in cache";
-        return -1;
-      }
-    } else {
-      ed25519_public_key_t service_pk;
-      const char *desc;
-
-      /* The check before this if/else makes sure of this. */
-      tor_assert(addr_type == ONION_V3_HOSTNAME);
-
-      if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) {
-        *errmsg = "Invalid v3 address";
-        return -1;
-      }
-
-      desc = hs_cache_lookup_encoded_as_client(&service_pk);
-      if (desc) {
-        *answer = tor_strdup(desc);
-      } else {
-        *errmsg = "Not found in cache";
-        return -1;
-      }
-    }
-  } else if (!strcmpstart(question, "hs/service/desc/id/")) {
-    hostname_type_t addr_type;
-
-    question += strlen("hs/service/desc/id/");
-    if (rend_valid_v2_service_id(question)) {
-      addr_type = ONION_V2_HOSTNAME;
-    } else if (hs_address_is_valid(question)) {
-      addr_type = ONION_V3_HOSTNAME;
-    } else {
-      *errmsg = "Invalid address";
-      return -1;
-    }
-    rend_cache_entry_t *e = NULL;
-
-    if (addr_type == ONION_V2_HOSTNAME) {
-      if (!rend_cache_lookup_v2_desc_as_service(question, &e)) {
-        /* Descriptor found in cache */
-        *answer = tor_strdup(e->desc);
-      } else {
-        *errmsg = "Not found in cache";
-        return -1;
-      }
-    } else {
-      ed25519_public_key_t service_pk;
-      char *desc;
-
-      /* The check before this if/else makes sure of this. */
-      tor_assert(addr_type == ONION_V3_HOSTNAME);
-
-      if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) {
-        *errmsg = "Invalid v3 address";
-        return -1;
-      }
-
-      desc = hs_service_lookup_current_desc(&service_pk);
-      if (desc) {
-        /* Newly allocated string, we have ownership. */
-        *answer = desc;
-      } else {
-        *errmsg = "Not found in cache";
-        return -1;
-      }
-    }
-  } else if (!strcmp(question, "md/all")) {
-    const smartlist_t *nodes = nodelist_get_list();
-    tor_assert(nodes);
-
-    if (smartlist_len(nodes) == 0) {
-      *answer = tor_strdup("");
-      return 0;
-    }
-
-    smartlist_t *microdescs = smartlist_new();
-
-    SMARTLIST_FOREACH_BEGIN(nodes, node_t *, n) {
-      if (n->md && n->md->body) {
-        char *copy = tor_strndup(n->md->body, n->md->bodylen);
-        smartlist_add(microdescs, copy);
-      }
-    } SMARTLIST_FOREACH_END(n);
-
-    *answer = smartlist_join_strings(microdescs, "", 0, NULL);
-    SMARTLIST_FOREACH(microdescs, char *, md, tor_free(md));
-    smartlist_free(microdescs);
-  } else if (!strcmpstart(question, "md/id/")) {
-    const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0);
-    const microdesc_t *md = NULL;
-    if (node) md = node->md;
-    if (md && md->body) {
-      *answer = tor_strndup(md->body, md->bodylen);
-    }
-  } else if (!strcmpstart(question, "md/name/")) {
-    /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the
-     * warning goes to the user, not to the controller. */
-    const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0);
-    /* XXXX duplicated code */
-    const microdesc_t *md = NULL;
-    if (node) md = node->md;
-    if (md && md->body) {
-      *answer = tor_strndup(md->body, md->bodylen);
-    }
-  } else if (!strcmp(question, "md/download-enabled")) {
-    int r = we_fetch_microdescriptors(get_options());
-    tor_asprintf(answer, "%d", !!r);
-  } else if (!strcmpstart(question, "desc-annotations/id/")) {
-    const routerinfo_t *ri = NULL;
-    const node_t *node =
-      node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0);
-    if (node)
-      ri = node->ri;
-    if (ri) {
-      const char *annotations =
-        signed_descriptor_get_annotations(&ri->cache_info);
-      if (annotations)
-        *answer = tor_strndup(annotations,
-                              ri->cache_info.annotations_len);
-    }
-  } else if (!strcmpstart(question, "dir/server/")) {
-    size_t answer_len = 0;
-    char *url = NULL;
-    smartlist_t *descs = smartlist_new();
-    const char *msg;
-    int res;
-    char *cp;
-    tor_asprintf(&url, "/tor/%s", question+4);
-    res = dirserv_get_routerdescs(descs, url, &msg);
-    if (res) {
-      log_warn(LD_CONTROL, "getinfo '%s': %s", question, msg);
-      smartlist_free(descs);
-      tor_free(url);
-      *errmsg = msg;
-      return -1;
-    }
-    SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd,
-                      answer_len += sd->signed_descriptor_len);
-    cp = *answer = tor_malloc(answer_len+1);
-    SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd,
-                      {
-                        memcpy(cp, signed_descriptor_get_body(sd),
-                               sd->signed_descriptor_len);
-                        cp += sd->signed_descriptor_len;
-                      });
-    *cp = '\0';
-    tor_free(url);
-    smartlist_free(descs);
-  } else if (!strcmpstart(question, "dir/status/")) {
-    *answer = tor_strdup("");
-  } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */
-    if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) {
-      const cached_dir_t *consensus = dirserv_get_consensus("ns");
-      if (consensus)
-        *answer = tor_strdup(consensus->dir);
-    }
-    if (!*answer) { /* try loading it from disk */
-      tor_mmap_t *mapped = networkstatus_map_cached_consensus("ns");
-      if (mapped) {
-        *answer = tor_memdup_nulterm(mapped->data, mapped->size);
-        tor_munmap_file(mapped);
-      }
-      if (!*answer) { /* generate an error */
-        *errmsg = "Could not open cached consensus. "
-          "Make sure FetchUselessDescriptors is set to 1.";
-        return -1;
-      }
-    }
-  } else if (!strcmp(question, "network-status")) { /* v1 */
-    static int network_status_warned = 0;
-    if (!network_status_warned) {
-      log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will "
-               "go away in a future version of Tor.");
-      network_status_warned = 1;
-    }
-    routerlist_t *routerlist = router_get_routerlist();
-    if (!routerlist || !routerlist->routers ||
-        list_server_status_v1(routerlist->routers, answer, 1) < 0) {
-      return -1;
-    }
-  } else if (!strcmpstart(question, "extra-info/digest/")) {
-    question += strlen("extra-info/digest/");
-    if (strlen(question) == HEX_DIGEST_LEN) {
-      char d[DIGEST_LEN];
-      signed_descriptor_t *sd = NULL;
-      if (base16_decode(d, sizeof(d), question, strlen(question))
-                        == sizeof(d)) {
-        /* XXXX this test should move into extrainfo_get_by_descriptor_digest,
-         * but I don't want to risk affecting other parts of the code,
-         * especially since the rules for using our own extrainfo (including
-         * when it might be freed) are different from those for using one
-         * we have downloaded. */
-        if (router_extrainfo_digest_is_me(d))
-          sd = &(router_get_my_extrainfo()->cache_info);
-        else
-          sd = extrainfo_get_by_descriptor_digest(d);
-      }
-      if (sd) {
-        const char *body = signed_descriptor_get_body(sd);
-        if (body)
-          *answer = tor_strndup(body, sd->signed_descriptor_len);
-      }
-    }
-  }
-
-  return 0;
-}
-
-/** Given a smartlist of 20-byte digests, return a newly allocated string
- * containing each of those digests in order, formatted in HEX, and terminated
- * with a newline. */
-static char *
-digest_list_to_string(const smartlist_t *sl)
-{
-  int len;
-  char *result, *s;
-
-  /* Allow for newlines, and a \0 at the end */
-  len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1;
-  result = tor_malloc_zero(len);
-
-  s = result;
-  SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) {
-    base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN);
-    s[HEX_DIGEST_LEN] = '\n';
-    s += HEX_DIGEST_LEN + 1;
-  } SMARTLIST_FOREACH_END(digest);
-  *s = '\0';
-
-  return result;
-}
-
-/** Turn a download_status_t into a human-readable description in a newly
- * allocated string.  The format is specified in control-spec.txt, under
- * the documentation for "GETINFO download/..." .  */
-static char *
-download_status_to_string(const download_status_t *dl)
-{
-  char *rv = NULL;
-  char tbuf[ISO_TIME_LEN+1];
-  const char *schedule_str, *want_authority_str;
-  const char *increment_on_str, *backoff_str;
-
-  if (dl) {
-    /* Get some substrings of the eventual output ready */
-    format_iso_time(tbuf, download_status_get_next_attempt_at(dl));
-
-    switch (dl->schedule) {
-      case DL_SCHED_GENERIC:
-        schedule_str = "DL_SCHED_GENERIC";
-        break;
-      case DL_SCHED_CONSENSUS:
-        schedule_str = "DL_SCHED_CONSENSUS";
-        break;
-      case DL_SCHED_BRIDGE:
-        schedule_str = "DL_SCHED_BRIDGE";
-        break;
-      default:
-        schedule_str = "unknown";
-        break;
-    }
-
-    switch (dl->want_authority) {
-      case DL_WANT_ANY_DIRSERVER:
-        want_authority_str = "DL_WANT_ANY_DIRSERVER";
-        break;
-      case DL_WANT_AUTHORITY:
-        want_authority_str = "DL_WANT_AUTHORITY";
-        break;
-      default:
-        want_authority_str = "unknown";
-        break;
-    }
-
-    switch (dl->increment_on) {
-      case DL_SCHED_INCREMENT_FAILURE:
-        increment_on_str = "DL_SCHED_INCREMENT_FAILURE";
-        break;
-      case DL_SCHED_INCREMENT_ATTEMPT:
-        increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT";
-        break;
-      default:
-        increment_on_str = "unknown";
-        break;
-    }
-
-    backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL";
-
-    /* Now assemble them */
-    tor_asprintf(&rv,
-                 "next-attempt-at %s\n"
-                 "n-download-failures %u\n"
-                 "n-download-attempts %u\n"
-                 "schedule %s\n"
-                 "want-authority %s\n"
-                 "increment-on %s\n"
-                 "backoff %s\n"
-                 "last-backoff-position %u\n"
-                 "last-delay-used %d\n",
-                 tbuf,
-                 dl->n_download_failures,
-                 dl->n_download_attempts,
-                 schedule_str,
-                 want_authority_str,
-                 increment_on_str,
-                 backoff_str,
-                 dl->last_backoff_position,
-                 dl->last_delay_used);
-  }
-
-  return rv;
-}
-
-/** Handle the consensus download cases for getinfo_helper_downloads() */
-STATIC void
-getinfo_helper_downloads_networkstatus(const char *flavor,
-                                       download_status_t **dl_to_emit,
-                                       const char **errmsg)
-{
-  /*
-   * We get the one for the current bootstrapped status by default, or
-   * take an extra /bootstrap or /running suffix
-   */
-  if (strcmp(flavor, "ns") == 0) {
-    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
-  } else if (strcmp(flavor, "ns/bootstrap") == 0) {
-    *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
-  } else if (strcmp(flavor, "ns/running") == 0 ) {
-    *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
-  } else if (strcmp(flavor, "microdesc") == 0) {
-    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
-  } else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
-    *dl_to_emit =
-      networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
-  } else if (strcmp(flavor, "microdesc/running") == 0) {
-    *dl_to_emit =
-      networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
-  } else {
-    *errmsg = "Unknown flavor";
-  }
-}
-
-/** Handle the cert download cases for getinfo_helper_downloads() */
-STATIC void
-getinfo_helper_downloads_cert(const char *fp_sk_req,
-                              download_status_t **dl_to_emit,
-                              smartlist_t **digest_list,
-                              const char **errmsg)
-{
-  const char *sk_req;
-  char id_digest[DIGEST_LEN];
-  char sk_digest[DIGEST_LEN];
-
-  /*
-   * We have to handle four cases; fp_sk_req is the request with
-   * a prefix of "downloads/cert/" snipped off.
-   *
-   * Case 1: fp_sk_req = "fps"
-   *  - We should emit a digest_list with a list of all the identity
-   *    fingerprints that can be queried for certificate download status;
-   *    get it by calling list_authority_ids_with_downloads().
-   *
-   * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp
-   *  - We want the default certificate for this identity fingerprint's
-   *    download status; this is the download we get from URLs starting
-   *    in /fp/ on the directory server.  We can get it with
-   *    id_only_download_status_for_authority_id().
-   *
-   * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp
-   *  - We want a list of all signing key digests for this identity
-   *    fingerprint which can be queried for certificate download status.
-   *    Get it with list_sk_digests_for_authority_id().
-   *
-   * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and
-   *         signing key digest sk
-   *   - We want the download status for the certificate for this specific
-   *     signing key and fingerprint.  These correspond to the ones we get
-   *     from URLs starting in /fp-sk/ on the directory server.  Get it with
-   *     list_sk_digests_for_authority_id().
-   */
-
-  if (strcmp(fp_sk_req, "fps") == 0) {
-    *digest_list = list_authority_ids_with_downloads();
-    if (!(*digest_list)) {
-      *errmsg = "Failed to get list of authority identity digests (!)";
-    }
-  } else if (!strcmpstart(fp_sk_req, "fp/")) {
-    fp_sk_req += strlen("fp/");
-    /* Okay, look for another / to tell the fp from fp-sk cases */
-    sk_req = strchr(fp_sk_req, '/');
-    if (sk_req) {
-      /* okay, split it here and try to parse <fp> */
-      if (base16_decode(id_digest, DIGEST_LEN,
-                        fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) {
-        /* Skip past the '/' */
-        ++sk_req;
-        if (strcmp(sk_req, "sks") == 0) {
-          /* We're asking for the list of signing key fingerprints */
-          *digest_list = list_sk_digests_for_authority_id(id_digest);
-          if (!(*digest_list)) {
-            *errmsg = "Failed to get list of signing key digests for this "
-                      "authority identity digest";
-          }
-        } else {
-          /* We've got a signing key digest */
-          if (base16_decode(sk_digest, DIGEST_LEN,
-                            sk_req, strlen(sk_req)) == DIGEST_LEN) {
-            *dl_to_emit =
-              download_status_for_authority_id_and_sk(id_digest, sk_digest);
-            if (!(*dl_to_emit)) {
-              *errmsg = "Failed to get download status for this identity/"
-                        "signing key digest pair";
-            }
-          } else {
-            *errmsg = "That didn't look like a signing key digest";
-          }
-        }
-      } else {
-        *errmsg = "That didn't look like an identity digest";
-      }
-    } else {
-      /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */
-      if (strlen(fp_sk_req) == HEX_DIGEST_LEN) {
-        if (base16_decode(id_digest, DIGEST_LEN,
-                          fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) {
-          *dl_to_emit = id_only_download_status_for_authority_id(id_digest);
-          if (!(*dl_to_emit)) {
-            *errmsg = "Failed to get download status for this authority "
-                      "identity digest";
-          }
-        } else {
-          *errmsg = "That didn't look like a digest";
-        }
-      } else {
-        *errmsg = "That didn't look like a digest";
-      }
-    }
-  } else {
-    *errmsg = "Unknown certificate download status query";
-  }
-}
-
-/** Handle the routerdesc download cases for getinfo_helper_downloads() */
-STATIC void
-getinfo_helper_downloads_desc(const char *desc_req,
-                              download_status_t **dl_to_emit,
-                              smartlist_t **digest_list,
-                              const char **errmsg)
-{
-  char desc_digest[DIGEST_LEN];
-  /*
-   * Two cases to handle here:
-   *
-   * Case 1: desc_req = "descs"
-   *   - Emit a list of all router descriptor digests, which we get by
-   *     calling router_get_descriptor_digests(); this can return NULL
-   *     if we have no current ns-flavor consensus.
-   *
-   * Case 2: desc_req = <fp>
-   *   - Check on the specified fingerprint and emit its download_status_t
-   *     using router_get_dl_status_by_descriptor_digest().
-   */
-
-  if (strcmp(desc_req, "descs") == 0) {
-    *digest_list = router_get_descriptor_digests();
-    if (!(*digest_list)) {
-      *errmsg = "We don't seem to have a networkstatus-flavored consensus";
-    }
-    /*
-     * Microdescs don't use the download_status_t mechanism, so we don't
-     * answer queries about their downloads here; see microdesc.c.
-     */
-  } else if (strlen(desc_req) == HEX_DIGEST_LEN) {
-    if (base16_decode(desc_digest, DIGEST_LEN,
-                      desc_req, strlen(desc_req)) == DIGEST_LEN) {
-      /* Okay we got a digest-shaped thing; try asking for it */
-      *dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest);
-      if (!(*dl_to_emit)) {
-        *errmsg = "No such descriptor digest found";
-      }
-    } else {
-      *errmsg = "That didn't look like a digest";
-    }
-  } else {
-    *errmsg = "Unknown router descriptor download status query";
-  }
-}
-
-/** Handle the bridge download cases for getinfo_helper_downloads() */
-STATIC void
-getinfo_helper_downloads_bridge(const char *bridge_req,
-                                download_status_t **dl_to_emit,
-                                smartlist_t **digest_list,
-                                const char **errmsg)
-{
-  char bridge_digest[DIGEST_LEN];
-  /*
-   * Two cases to handle here:
-   *
-   * Case 1: bridge_req = "bridges"
-   *   - Emit a list of all bridge identity digests, which we get by
-   *     calling list_bridge_identities(); this can return NULL if we are
-   *     not using bridges.
-   *
-   * Case 2: bridge_req = <fp>
-   *   - Check on the specified fingerprint and emit its download_status_t
-   *     using get_bridge_dl_status_by_id().
-   */
-
-  if (strcmp(bridge_req, "bridges") == 0) {
-    *digest_list = list_bridge_identities();
-    if (!(*digest_list)) {
-      *errmsg = "We don't seem to be using bridges";
-    }
-  } else if (strlen(bridge_req) == HEX_DIGEST_LEN) {
-    if (base16_decode(bridge_digest, DIGEST_LEN,
-                      bridge_req, strlen(bridge_req)) == DIGEST_LEN) {
-      /* Okay we got a digest-shaped thing; try asking for it */
-      *dl_to_emit = get_bridge_dl_status_by_id(bridge_digest);
-      if (!(*dl_to_emit)) {
-        *errmsg = "No such bridge identity digest found";
-      }
-    } else {
-      *errmsg = "That didn't look like a digest";
-    }
-  } else {
-    *errmsg = "Unknown bridge descriptor download status query";
-  }
-}
-
-/** Implementation helper for GETINFO: knows the answers for questions about
- * download status information. */
-STATIC int
-getinfo_helper_downloads(control_connection_t *control_conn,
-                   const char *question, char **answer,
-                   const char **errmsg)
-{
-  download_status_t *dl_to_emit = NULL;
-  smartlist_t *digest_list = NULL;
-
-  /* Assert args are sane */
-  tor_assert(control_conn != NULL);
-  tor_assert(question != NULL);
-  tor_assert(answer != NULL);
-  tor_assert(errmsg != NULL);
-
-  /* We check for this later to see if we should supply a default */
-  *errmsg = NULL;
-
-  /* Are we after networkstatus downloads? */
-  if (!strcmpstart(question, "downloads/networkstatus/")) {
-    getinfo_helper_downloads_networkstatus(
-        question + strlen("downloads/networkstatus/"),
-        &dl_to_emit, errmsg);
-  /* Certificates? */
-  } else if (!strcmpstart(question, "downloads/cert/")) {
-    getinfo_helper_downloads_cert(
-        question + strlen("downloads/cert/"),
-        &dl_to_emit, &digest_list, errmsg);
-  /* Router descriptors? */
-  } else if (!strcmpstart(question, "downloads/desc/")) {
-    getinfo_helper_downloads_desc(
-        question + strlen("downloads/desc/"),
-        &dl_to_emit, &digest_list, errmsg);
-  /* Bridge descriptors? */
-  } else if (!strcmpstart(question, "downloads/bridge/")) {
-    getinfo_helper_downloads_bridge(
-        question + strlen("downloads/bridge/"),
-        &dl_to_emit, &digest_list, errmsg);
-  } else {
-    *errmsg = "Unknown download status query";
-  }
-
-  if (dl_to_emit) {
-    *answer = download_status_to_string(dl_to_emit);
-
-    return 0;
-  } else if (digest_list) {
-    *answer = digest_list_to_string(digest_list);
-    SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s));
-    smartlist_free(digest_list);
-
-    return 0;
-  } else {
-    if (!(*errmsg)) {
-      *errmsg = "Unknown error";
-    }
-
-    return -1;
-  }
-}
-
-/** Implementation helper for GETINFO: knows how to generate summaries of the
- * current states of things we send events about. */
-static int
-getinfo_helper_events(control_connection_t *control_conn,
-                      const char *question, char **answer,
-                      const char **errmsg)
-{
-  const or_options_t *options = get_options();
-  (void) control_conn;
-  if (!strcmp(question, "circuit-status")) {
-    smartlist_t *status = smartlist_new();
-    SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) {
-      origin_circuit_t *circ;
-      char *circdesc;
-      const char *state;
-      if (! CIRCUIT_IS_ORIGIN(circ_) || circ_->marked_for_close)
-        continue;
-      circ = TO_ORIGIN_CIRCUIT(circ_);
-
-      if (circ->base_.state == CIRCUIT_STATE_OPEN)
-        state = "BUILT";
-      else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT)
-        state = "GUARD_WAIT";
-      else if (circ->cpath)
-        state = "EXTENDED";
-      else
-        state = "LAUNCHED";
-
-      circdesc = circuit_describe_status_for_controller(circ);
-
-      smartlist_add_asprintf(status, "%lu %s%s%s",
-                   (unsigned long)circ->global_identifier,
-                   state, *circdesc ? " " : "", circdesc);
-      tor_free(circdesc);
-    }
-    SMARTLIST_FOREACH_END(circ_);
-    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
-    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
-    smartlist_free(status);
-  } else if (!strcmp(question, "stream-status")) {
-    smartlist_t *conns = get_connection_array();
-    smartlist_t *status = smartlist_new();
-    char buf[256];
-    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
-      const char *state;
-      entry_connection_t *conn;
-      circuit_t *circ;
-      origin_circuit_t *origin_circ = NULL;
-      if (base_conn->type != CONN_TYPE_AP ||
-          base_conn->marked_for_close ||
-          base_conn->state == AP_CONN_STATE_SOCKS_WAIT ||
-          base_conn->state == AP_CONN_STATE_NATD_WAIT)
-        continue;
-      conn = TO_ENTRY_CONN(base_conn);
-      switch (base_conn->state)
-        {
-        case AP_CONN_STATE_CONTROLLER_WAIT:
-        case AP_CONN_STATE_CIRCUIT_WAIT:
-          if (conn->socks_request &&
-              SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command))
-            state = "NEWRESOLVE";
-          else
-            state = "NEW";
-          break;
-        case AP_CONN_STATE_RENDDESC_WAIT:
-        case AP_CONN_STATE_CONNECT_WAIT:
-          state = "SENTCONNECT"; break;
-        case AP_CONN_STATE_RESOLVE_WAIT:
-          state = "SENTRESOLVE"; break;
-        case AP_CONN_STATE_OPEN:
-          state = "SUCCEEDED"; break;
-        default:
-          log_warn(LD_BUG, "Asked for stream in unknown state %d",
-                   base_conn->state);
-          continue;
-        }
-      circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn));
-      if (circ && CIRCUIT_IS_ORIGIN(circ))
-        origin_circ = TO_ORIGIN_CIRCUIT(circ);
-      write_stream_target_to_buf(conn, buf, sizeof(buf));
-      smartlist_add_asprintf(status, "%lu %s %lu %s",
-                   (unsigned long) base_conn->global_identifier,state,
-                   origin_circ?
-                         (unsigned long)origin_circ->global_identifier : 0ul,
-                   buf);
-    } SMARTLIST_FOREACH_END(base_conn);
-    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
-    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
-    smartlist_free(status);
-  } else if (!strcmp(question, "orconn-status")) {
-    smartlist_t *conns = get_connection_array();
-    smartlist_t *status = smartlist_new();
-    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
-      const char *state;
-      char name[128];
-      or_connection_t *conn;
-      if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close)
-        continue;
-      conn = TO_OR_CONN(base_conn);
-      if (conn->base_.state == OR_CONN_STATE_OPEN)
-        state = "CONNECTED";
-      else if (conn->nickname)
-        state = "LAUNCHED";
-      else
-        state = "NEW";
-      orconn_target_get_name(name, sizeof(name), conn);
-      smartlist_add_asprintf(status, "%s %s", name, state);
-    } SMARTLIST_FOREACH_END(base_conn);
-    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
-    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
-    smartlist_free(status);
-  } else if (!strcmpstart(question, "address-mappings/")) {
-    time_t min_e, max_e;
-    smartlist_t *mappings;
-    question += strlen("address-mappings/");
-    if (!strcmp(question, "all")) {
-      min_e = 0; max_e = TIME_MAX;
-    } else if (!strcmp(question, "cache")) {
-      min_e = 2; max_e = TIME_MAX;
-    } else if (!strcmp(question, "config")) {
-      min_e = 0; max_e = 0;
-    } else if (!strcmp(question, "control")) {
-      min_e = 1; max_e = 1;
-    } else {
-      return 0;
-    }
-    mappings = smartlist_new();
-    addressmap_get_mappings(mappings, min_e, max_e, 1);
-    *answer = smartlist_join_strings(mappings, "\r\n", 0, NULL);
-    SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp));
-    smartlist_free(mappings);
-  } else if (!strcmpstart(question, "status/")) {
-    /* Note that status/ is not a catch-all for events; there's only supposed
-     * to be a status GETINFO if there's a corresponding STATUS event. */
-    if (!strcmp(question, "status/circuit-established")) {
-      *answer = tor_strdup(have_completed_a_circuit() ? "1" : "0");
-    } else if (!strcmp(question, "status/enough-dir-info")) {
-      *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0");
-    } else if (!strcmp(question, "status/good-server-descriptor") ||
-               !strcmp(question, "status/accepted-server-descriptor")) {
-      /* They're equivalent for now, until we can figure out how to make
-       * good-server-descriptor be what we want. See comment in
-       * control-spec.txt. */
-      *answer = tor_strdup(directories_have_accepted_server_descriptor()
-                           ? "1" : "0");
-    } else if (!strcmp(question, "status/reachability-succeeded/or")) {
-      *answer = tor_strdup(check_whether_orport_reachable(options) ?
-                            "1" : "0");
-    } else if (!strcmp(question, "status/reachability-succeeded/dir")) {
-      *answer = tor_strdup(check_whether_dirport_reachable(options) ?
-                            "1" : "0");
-    } else if (!strcmp(question, "status/reachability-succeeded")) {
-      tor_asprintf(answer, "OR=%d DIR=%d",
-                   check_whether_orport_reachable(options) ? 1 : 0,
-                   check_whether_dirport_reachable(options) ? 1 : 0);
-    } else if (!strcmp(question, "status/bootstrap-phase")) {
-      *answer = control_event_boot_last_msg();
-    } else if (!strcmpstart(question, "status/version/")) {
-      int is_server = server_mode(options);
-      networkstatus_t *c = networkstatus_get_latest_consensus();
-      version_status_t status;
-      const char *recommended;
-      if (c) {
-        recommended = is_server ? c->server_versions : c->client_versions;
-        status = tor_version_is_obsolete(VERSION, recommended);
-      } else {
-        recommended = "?";
-        status = VS_UNKNOWN;
-      }
-
-      if (!strcmp(question, "status/version/recommended")) {
-        *answer = tor_strdup(recommended);
-        return 0;
-      }
-      if (!strcmp(question, "status/version/current")) {
-        switch (status)
-          {
-          case VS_RECOMMENDED: *answer = tor_strdup("recommended"); break;
-          case VS_OLD: *answer = tor_strdup("obsolete"); break;
-          case VS_NEW: *answer = tor_strdup("new"); break;
-          case VS_NEW_IN_SERIES: *answer = tor_strdup("new in series"); break;
-          case VS_UNRECOMMENDED: *answer = tor_strdup("unrecommended"); break;
-          case VS_EMPTY: *answer = tor_strdup("none recommended"); break;
-          case VS_UNKNOWN: *answer = tor_strdup("unknown"); break;
-          default: tor_fragile_assert();
-          }
-      }
-    } else if (!strcmp(question, "status/clients-seen")) {
-      char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL));
-      if (!bridge_stats) {
-        *errmsg = "No bridge-client stats available";
-        return -1;
-      }
-      *answer = bridge_stats;
-    } else if (!strcmp(question, "status/fresh-relay-descs")) {
-      if (!server_mode(options)) {
-        *errmsg = "Only relays have descriptors";
-        return -1;
-      }
-      routerinfo_t *r;
-      extrainfo_t *e;
-      if (router_build_fresh_descriptor(&r, &e) < 0) {
-        *errmsg = "Error generating descriptor";
-        return -1;
-      }
-      size_t size = r->cache_info.signed_descriptor_len + 1;
-      if (e) {
-        size += e->cache_info.signed_descriptor_len + 1;
-      }
-      tor_assert(r->cache_info.signed_descriptor_len);
-      char *descs = tor_malloc(size);
-      char *cp = descs;
-      memcpy(cp, signed_descriptor_get_body(&r->cache_info),
-             r->cache_info.signed_descriptor_len);
-      cp += r->cache_info.signed_descriptor_len - 1;
-      if (e) {
-        if (cp[0] == '\0') {
-          cp[0] = '\n';
-        } else if (cp[0] != '\n') {
-          cp[1] = '\n';
-          cp++;
-        }
-        memcpy(cp, signed_descriptor_get_body(&e->cache_info),
-               e->cache_info.signed_descriptor_len);
-        cp += e->cache_info.signed_descriptor_len - 1;
-      }
-      if (cp[0] == '\n') {
-        cp[0] = '\0';
-      } else if (cp[0] != '\0') {
-        cp[1] = '\0';
-      }
-      *answer = descs;
-      routerinfo_free(r);
-      extrainfo_free(e);
-    } else {
-      return 0;
-    }
-  }
-  return 0;
-}
-
-/** Implementation helper for GETINFO: knows how to enumerate hidden services
- * created via the control port. */
-STATIC int
-getinfo_helper_onions(control_connection_t *control_conn,
-                      const char *question, char **answer,
-                      const char **errmsg)
-{
-  smartlist_t *onion_list = NULL;
-  (void) errmsg;  /* no errors from this method */
-
-  if (control_conn && !strcmp(question, "onions/current")) {
-    onion_list = control_conn->ephemeral_onion_services;
-  } else if (!strcmp(question, "onions/detached")) {
-    onion_list = detached_onion_services;
-  } else {
-    return 0;
-  }
-  if (!onion_list || smartlist_len(onion_list) == 0) {
-    if (answer) {
-      *answer = tor_strdup("");
-    }
-  } else {
-    if (answer) {
-      *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL);
-    }
-  }
-
-  return 0;
-}
-
-/** Implementation helper for GETINFO: answers queries about network
- * liveness. */
-static int
-getinfo_helper_liveness(control_connection_t *control_conn,
-                      const char *question, char **answer,
-                      const char **errmsg)
-{
-  (void)control_conn;
-  (void)errmsg;
-  if (strcmp(question, "network-liveness") == 0) {
-    if (get_cached_network_liveness()) {
-      *answer = tor_strdup("up");
-    } else {
-      *answer = tor_strdup("down");
-    }
-  }
-
-  return 0;
-}
-
-/** Implementation helper for GETINFO: answers queries about shared random
- * value. */
-static int
-getinfo_helper_sr(control_connection_t *control_conn,
-                  const char *question, char **answer,
-                  const char **errmsg)
-{
-  (void) control_conn;
-  (void) errmsg;
-
-  if (!strcmp(question, "sr/current")) {
-    *answer = sr_get_current_for_control();
-  } else if (!strcmp(question, "sr/previous")) {
-    *answer = sr_get_previous_for_control();
-  }
-  /* Else statement here is unrecognized key so do nothing. */
-
-  return 0;
-}
-
-/** Callback function for GETINFO: on a given control connection, try to
- * answer the question <b>q</b> and store the newly-allocated answer in
- * *<b>a</b>. If an internal error occurs, return -1 and optionally set
- * *<b>error_out</b> to point to an error message to be delivered to the
- * controller. On success, _or if the key is not recognized_, return 0. Do not
- * set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
- * to improve the error message.
- */
-typedef int (*getinfo_helper_t)(control_connection_t *,
-                                const char *q, char **a,
-                                const char **error_out);
-
-/** A single item for the GETINFO question-to-answer-function table. */
-typedef struct getinfo_item_t {
-  const char *varname; /**< The value (or prefix) of the question. */
-  getinfo_helper_t fn; /**< The function that knows the answer: NULL if
-                        * this entry is documentation-only. */
-  const char *desc; /**< Description of the variable. */
-  int is_prefix; /** Must varname match exactly, or must it be a prefix? */
-} getinfo_item_t;
-
-#define ITEM(name, fn, desc) { name, getinfo_helper_##fn, desc, 0 }
-#define PREFIX(name, fn, desc) { name, getinfo_helper_##fn, desc, 1 }
-#define DOC(name, desc) { name, NULL, desc, 0 }
-
-/** Table mapping questions accepted by GETINFO to the functions that know how
- * to answer them. */
-static const getinfo_item_t getinfo_items[] = {
-  ITEM("version", misc, "The current version of Tor."),
-  ITEM("bw-event-cache", misc, "Cached BW events for a short interval."),
-  ITEM("config-file", misc, "Current location of the \"torrc\" file."),
-  ITEM("config-defaults-file", misc, "Current location of the defaults file."),
-  ITEM("config-text", misc,
-       "Return the string that would be written by a saveconf command."),
-  ITEM("config-can-saveconf", misc,
-       "Is it possible to save the configuration to the \"torrc\" file?"),
-  ITEM("accounting/bytes", accounting,
-       "Number of bytes read/written so far in the accounting interval."),
-  ITEM("accounting/bytes-left", accounting,
-      "Number of bytes left to write/read so far in the accounting interval."),
-  ITEM("accounting/enabled", accounting, "Is accounting currently enabled?"),
-  ITEM("accounting/hibernating", accounting, "Are we hibernating or awake?"),
-  ITEM("accounting/interval-start", accounting,
-       "Time when the accounting period starts."),
-  ITEM("accounting/interval-end", accounting,
-       "Time when the accounting period ends."),
-  ITEM("accounting/interval-wake", accounting,
-       "Time to wake up in this accounting period."),
-  ITEM("helper-nodes", entry_guards, NULL), /* deprecated */
-  ITEM("entry-guards", entry_guards,
-       "Which nodes are we using as entry guards?"),
-  ITEM("fingerprint", misc, NULL),
-  PREFIX("config/", config, "Current configuration values."),
-  DOC("config/names",
-      "List of configuration options, types, and documentation."),
-  DOC("config/defaults",
-      "List of default values for configuration options. "
-      "See also config/names"),
-  PREFIX("current-time/", current_time, "Current time."),
-  DOC("current-time/local", "Current time on the local system."),
-  DOC("current-time/utc", "Current UTC time."),
-  PREFIX("downloads/networkstatus/", downloads,
-         "Download statuses for networkstatus objects"),
-  DOC("downloads/networkstatus/ns",
-      "Download status for current-mode networkstatus download"),
-  DOC("downloads/networkstatus/ns/bootstrap",
-      "Download status for bootstrap-time networkstatus download"),
-  DOC("downloads/networkstatus/ns/running",
-      "Download status for run-time networkstatus download"),
-  DOC("downloads/networkstatus/microdesc",
-      "Download status for current-mode microdesc download"),
-  DOC("downloads/networkstatus/microdesc/bootstrap",
-      "Download status for bootstrap-time microdesc download"),
-  DOC("downloads/networkstatus/microdesc/running",
-      "Download status for run-time microdesc download"),
-  PREFIX("downloads/cert/", downloads,
-         "Download statuses for certificates, by id fingerprint and "
-         "signing key"),
-  DOC("downloads/cert/fps",
-      "List of authority fingerprints for which any download statuses "
-      "exist"),
-  DOC("downloads/cert/fp/<fp>",
-      "Download status for <fp> with the default signing key; corresponds "
-      "to /fp/ URLs on directory server."),
-  DOC("downloads/cert/fp/<fp>/sks",
-      "List of signing keys for which specific download statuses are "
-      "available for this id fingerprint"),
-  DOC("downloads/cert/fp/<fp>/<sk>",
-      "Download status for <fp> with signing key <sk>; corresponds "
-      "to /fp-sk/ URLs on directory server."),
-  PREFIX("downloads/desc/", downloads,
-         "Download statuses for router descriptors, by descriptor digest"),
-  DOC("downloads/desc/descs",
-      "Return a list of known router descriptor digests"),
-  DOC("downloads/desc/<desc>",
-      "Return a download status for a given descriptor digest"),
-  PREFIX("downloads/bridge/", downloads,
-         "Download statuses for bridge descriptors, by bridge identity "
-         "digest"),
-  DOC("downloads/bridge/bridges",
-      "Return a list of configured bridge identity digests with download "
-      "statuses"),
-  DOC("downloads/bridge/<desc>",
-      "Return a download status for a given bridge identity digest"),
-  ITEM("info/names", misc,
-       "List of GETINFO options, types, and documentation."),
-  ITEM("events/names", misc,
-       "Events that the controller can ask for with SETEVENTS."),
-  ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"),
-  ITEM("features/names", misc, "What arguments can USEFEATURE take?"),
-  PREFIX("desc/id/", dir, "Router descriptors by ID."),
-  PREFIX("desc/name/", dir, "Router descriptors by nickname."),
-  ITEM("desc/all-recent", dir,
-       "All non-expired, non-superseded router descriptors."),
-  ITEM("desc/download-enabled", dir,
-       "Do we try to download router descriptors?"),
-  ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
-  ITEM("md/all", dir, "All known microdescriptors."),
-  PREFIX("md/id/", dir, "Microdescriptors by ID"),
-  PREFIX("md/name/", dir, "Microdescriptors by name"),
-  ITEM("md/download-enabled", dir,
-       "Do we try to download microdescriptors?"),
-  PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
-  PREFIX("hs/client/desc/id", dir,
-         "Hidden Service descriptor in client's cache by onion."),
-  PREFIX("hs/service/desc/id/", dir,
-         "Hidden Service descriptor in services's cache by onion."),
-  PREFIX("net/listeners/", listeners, "Bound addresses by type"),
-  ITEM("ns/all", networkstatus,
-       "Brief summary of router status (v2 directory format)"),
-  PREFIX("ns/id/", networkstatus,
-         "Brief summary of router status by ID (v2 directory format)."),
-  PREFIX("ns/name/", networkstatus,
-         "Brief summary of router status by nickname (v2 directory format)."),
-  PREFIX("ns/purpose/", networkstatus,
-         "Brief summary of router status by purpose (v2 directory format)."),
-  PREFIX("consensus/", networkstatus,
-         "Information about and from the ns consensus."),
-  ITEM("network-status", dir,
-       "Brief summary of router status (v1 directory format)"),
-  ITEM("network-liveness", liveness,
-       "Current opinion on whether the network is live"),
-  ITEM("circuit-status", events, "List of current circuits originating here."),
-  ITEM("stream-status", events,"List of current streams."),
-  ITEM("orconn-status", events, "A list of current OR connections."),
-  ITEM("dormant", misc,
-       "Is Tor dormant (not building circuits because it's idle)?"),
-  PREFIX("address-mappings/", events, NULL),
-  DOC("address-mappings/all", "Current address mappings."),
-  DOC("address-mappings/cache", "Current cached DNS replies."),
-  DOC("address-mappings/config",
-      "Current address mappings from configuration."),
-  DOC("address-mappings/control", "Current address mappings from controller."),
-  PREFIX("status/", events, NULL),
-  DOC("status/circuit-established",
-      "Whether we think client functionality is working."),
-  DOC("status/enough-dir-info",
-      "Whether we have enough up-to-date directory information to build "
-      "circuits."),
-  DOC("status/bootstrap-phase",
-      "The last bootstrap phase status event that Tor sent."),
-  DOC("status/clients-seen",
-      "Breakdown of client countries seen by a bridge."),
-  DOC("status/fresh-relay-descs",
-      "A fresh relay/ei descriptor pair for Tor's current state. Not stored."),
-  DOC("status/version/recommended", "List of currently recommended versions."),
-  DOC("status/version/current", "Status of the current version."),
-  ITEM("address", misc, "IP address of this Tor host, if we can guess it."),
-  ITEM("traffic/read", misc,"Bytes read since the process was started."),
-  ITEM("traffic/written", misc,
-       "Bytes written since the process was started."),
-  ITEM("uptime", misc, "Uptime of the Tor daemon in seconds."),
-  ITEM("process/pid", misc, "Process id belonging to the main tor process."),
-  ITEM("process/uid", misc, "User id running the tor process."),
-  ITEM("process/user", misc,
-       "Username under which the tor process is running."),
-  ITEM("process/descriptor-limit", misc, "File descriptor limit."),
-  ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"),
-  PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."),
-  PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."),
-  PREFIX("dir/status/", dir,
-         "v2 networkstatus docs as retrieved from a DirPort."),
-  ITEM("dir/status-vote/current/consensus", dir,
-       "v3 Networkstatus consensus as retrieved from a DirPort."),
-  ITEM("exit-policy/default", policies,
-       "The default value appended to the configured exit policy."),
-  ITEM("exit-policy/reject-private/default", policies,
-       "The default rules appended to the configured exit policy by"
-       " ExitPolicyRejectPrivate."),
-  ITEM("exit-policy/reject-private/relay", policies,
-       "The relay-specific rules appended to the configured exit policy by"
-       " ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."),
-  ITEM("exit-policy/full", policies, "The entire exit policy of onion router"),
-  ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"),
-  ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"),
-  PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"),
-  ITEM("onions/current", onions,
-       "Onion services owned by the current control connection."),
-  ITEM("onions/detached", onions,
-       "Onion services detached from the control connection."),
-  ITEM("sr/current", sr, "Get current shared random value."),
-  ITEM("sr/previous", sr, "Get previous shared random value."),
-  { NULL, NULL, NULL, 0 }
-};
-
-/** Allocate and return a list of recognized GETINFO options. */
-static char *
-list_getinfo_options(void)
-{
-  int i;
-  smartlist_t *lines = smartlist_new();
-  char *ans;
-  for (i = 0; getinfo_items[i].varname; ++i) {
-    if (!getinfo_items[i].desc)
-      continue;
-
-    smartlist_add_asprintf(lines, "%s%s -- %s\n",
-                 getinfo_items[i].varname,
-                 getinfo_items[i].is_prefix ? "*" : "",
-                 getinfo_items[i].desc);
-  }
-  smartlist_sort_strings(lines);
-
-  ans = smartlist_join_strings(lines, "", 0, NULL);
-  SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
-  smartlist_free(lines);
-
-  return ans;
-}
-
-/** Lookup the 'getinfo' entry <b>question</b>, and return
- * the answer in <b>*answer</b> (or NULL if key not recognized).
- * Return 0 if success or unrecognized, or -1 if recognized but
- * internal error. */
-static int
-handle_getinfo_helper(control_connection_t *control_conn,
-                      const char *question, char **answer,
-                      const char **err_out)
-{
-  int i;
-  *answer = NULL; /* unrecognized key by default */
-
-  for (i = 0; getinfo_items[i].varname; ++i) {
-    int match;
-    if (getinfo_items[i].is_prefix)
-      match = !strcmpstart(question, getinfo_items[i].varname);
-    else
-      match = !strcmp(question, getinfo_items[i].varname);
-    if (match) {
-      tor_assert(getinfo_items[i].fn);
-      return getinfo_items[i].fn(control_conn, question, answer, err_out);
-    }
-  }
-
-  return 0; /* unrecognized */
-}
-
-/** Called when we receive a GETINFO command.  Try to fetch all requested
- * information, and reply with information or error message. */
-static int
-handle_control_getinfo(control_connection_t *conn, uint32_t len,
-                       const char *body)
-{
-  smartlist_t *questions = smartlist_new();
-  smartlist_t *answers = smartlist_new();
-  smartlist_t *unrecognized = smartlist_new();
-  char *ans = NULL;
-  int i;
-  (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */
-
-  smartlist_split_string(questions, body, " ",
-                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
-  SMARTLIST_FOREACH_BEGIN(questions, const char *, q) {
-    const char *errmsg = NULL;
-
-    if (handle_getinfo_helper(conn, q, &ans, &errmsg) < 0) {
-      if (!errmsg)
-        errmsg = "Internal error";
-      connection_printf_to_buf(conn, "551 %s\r\n", errmsg);
-      goto done;
-    }
-    if (!ans) {
-      if (errmsg) /* use provided error message */
-        smartlist_add_strdup(unrecognized, errmsg);
-      else /* use default error message */
-        smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q);
-    } else {
-      smartlist_add_strdup(answers, q);
-      smartlist_add(answers, ans);
-    }
-  } SMARTLIST_FOREACH_END(q);
-
-  if (smartlist_len(unrecognized)) {
-    /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */
-    for (i=0; i < smartlist_len(unrecognized)-1; ++i)
-      connection_printf_to_buf(conn,
-                               "552-%s\r\n",
-                               (char *)smartlist_get(unrecognized, i));
-
-    connection_printf_to_buf(conn,
-                             "552 %s\r\n",
-                             (char *)smartlist_get(unrecognized, i));
-    goto done;
-  }
-
-  for (i = 0; i < smartlist_len(answers); i += 2) {
-    char *k = smartlist_get(answers, i);
-    char *v = smartlist_get(answers, i+1);
-    if (!strchr(v, '\n') && !strchr(v, '\r')) {
-      connection_printf_to_buf(conn, "250-%s=", k);
-      connection_write_str_to_buf(v, conn);
-      connection_write_str_to_buf("\r\n", conn);
-    } else {
-      char *esc = NULL;
-      size_t esc_len;
-      esc_len = write_escaped_data(v, strlen(v), &esc);
-      connection_printf_to_buf(conn, "250+%s=\r\n", k);
-      connection_buf_add(esc, esc_len, TO_CONN(conn));
-      tor_free(esc);
-    }
-  }
-  connection_write_str_to_buf("250 OK\r\n", conn);
-
- done:
-  SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp));
-  smartlist_free(answers);
-  SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
-  smartlist_free(questions);
-  SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
-  smartlist_free(unrecognized);
-
-  return 0;
-}
-
 /** Given a string, convert it to a circuit purpose. */
 static uint8_t
 circuit_purpose_from_string(const char *string)
@@ -3977,6 +2329,15 @@ add_onion_helper_add_service(int hs_version,
   return ret;
 }
 
+/**
+ *
+ **/
+smartlist_t *
+get_detached_onion_services(void)
+{
+  return detached_onion_services;
+}
+
 /** Called when we get a ADD_ONION command; parse the body, and set up
  * the new ephemeral Onion Service. */
 static int
diff --git a/src/feature/control/control.h b/src/feature/control/control.h
index f9742d555..810fe6784 100644
--- a/src/feature/control/control.h
+++ b/src/feature/control/control.h
@@ -77,48 +77,18 @@ STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk,
 STATIC rend_authorized_client_t *
 add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out);
 
-STATIC int getinfo_helper_onions(
-    control_connection_t *control_conn,
-    const char *question,
-    char **answer,
-    const char **errmsg);
-STATIC void getinfo_helper_downloads_networkstatus(
-    const char *flavor,
-    download_status_t **dl_to_emit,
-    const char **errmsg);
-STATIC void getinfo_helper_downloads_cert(
-    const char *fp_sk_req,
-    download_status_t **dl_to_emit,
-    smartlist_t **digest_list,
-    const char **errmsg);
-STATIC void getinfo_helper_downloads_desc(
-    const char *desc_req,
-    download_status_t **dl_to_emit,
-    smartlist_t **digest_list,
-    const char **errmsg);
-STATIC void getinfo_helper_downloads_bridge(
-    const char *bridge_req,
-    download_status_t **dl_to_emit,
-    smartlist_t **digest_list,
-    const char **errmsg);
-STATIC int getinfo_helper_downloads(
-    control_connection_t *control_conn,
-    const char *question, char **answer,
-    const char **errmsg);
-STATIC int getinfo_helper_dir(
-    control_connection_t *control_conn,
-    const char *question, char **answer,
-    const char **errmsg);
-STATIC int getinfo_helper_current_time(
-    control_connection_t *control_conn,
-    const char *question, char **answer,
-    const char **errmsg);
-
 #endif /* defined(CONTROL_PRIVATE) */
 
 #ifdef CONTROL_MODULE_PRIVATE
+struct signal_name_t {
+  int sig;
+  const char *signal_name;
+};
+extern const struct signal_name_t signal_table[];
+
 int get_cached_network_liveness(void);
 void set_cached_network_liveness(int liveness);
+smartlist_t * get_detached_onion_services(void);
 #endif /* defined(CONTROL_MODULE_PRIVATE) */
 
 #endif /* !defined(TOR_CONTROL_H) */
diff --git a/src/feature/control/control_events.c b/src/feature/control/control_events.c
index 07e1a9d94..129776f49 100644
--- a/src/feature/control/control_events.c
+++ b/src/feature/control/control_events.c
@@ -72,6 +72,43 @@ static void send_control_event(uint16_t event,
                                const char *format, ...)
   CHECK_PRINTF(2,3);
 
+/** Table mapping event values to their names.  Used to implement SETEVENTS
+ * and GETINFO events/names, and to keep they in sync. */
+const struct control_event_t control_event_table[] = {
+  { EVENT_CIRCUIT_STATUS, "CIRC" },
+  { EVENT_CIRCUIT_STATUS_MINOR, "CIRC_MINOR" },
+  { EVENT_STREAM_STATUS, "STREAM" },
+  { EVENT_OR_CONN_STATUS, "ORCONN" },
+  { EVENT_BANDWIDTH_USED, "BW" },
+  { EVENT_DEBUG_MSG, "DEBUG" },
+  { EVENT_INFO_MSG, "INFO" },
+  { EVENT_NOTICE_MSG, "NOTICE" },
+  { EVENT_WARN_MSG, "WARN" },
+  { EVENT_ERR_MSG, "ERR" },
+  { EVENT_NEW_DESC, "NEWDESC" },
+  { EVENT_ADDRMAP, "ADDRMAP" },
+  { EVENT_DESCCHANGED, "DESCCHANGED" },
+  { EVENT_NS, "NS" },
+  { EVENT_STATUS_GENERAL, "STATUS_GENERAL" },
+  { EVENT_STATUS_CLIENT, "STATUS_CLIENT" },
+  { EVENT_STATUS_SERVER, "STATUS_SERVER" },
+  { EVENT_GUARD, "GUARD" },
+  { EVENT_STREAM_BANDWIDTH_USED, "STREAM_BW" },
+  { EVENT_CLIENTS_SEEN, "CLIENTS_SEEN" },
+  { EVENT_NEWCONSENSUS, "NEWCONSENSUS" },
+  { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" },
+  { EVENT_GOT_SIGNAL, "SIGNAL" },
+  { EVENT_CONF_CHANGED, "CONF_CHANGED"},
+  { EVENT_CONN_BW, "CONN_BW" },
+  { EVENT_CELL_STATS, "CELL_STATS" },
+  { EVENT_CIRC_BANDWIDTH_USED, "CIRC_BW" },
+  { EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" },
+  { EVENT_HS_DESC, "HS_DESC" },
+  { EVENT_HS_DESC_CONTENT, "HS_DESC_CONTENT" },
+  { EVENT_NETWORK_LIVENESS, "NETWORK_LIVENESS" },
+  { 0, NULL },
+};
+
 /** Given a log severity, return the corresponding control event code. */
 static inline int
 log_severity_to_event(int severity)
diff --git a/src/feature/control/control_events.h b/src/feature/control/control_events.h
index 9e7934671..0bdbb9cfd 100644
--- a/src/feature/control/control_events.h
+++ b/src/feature/control/control_events.h
@@ -327,6 +327,14 @@ void append_cell_stats_by_command(smartlist_t *event_parts,
 void format_cell_stats(char **event_string, circuit_t *circ,
                        cell_stats_t *cell_stats);
 
+/** Helper structure: maps event values to their names. */
+struct control_event_t {
+  uint16_t event_code;
+  const char *event_name;
+};
+
+extern const struct control_event_t control_event_table[];
+
 #ifdef TOR_UNIT_TESTS
 MOCK_DECL(STATIC void,
           send_control_event_string,(uint16_t event, const char *msg));
diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c
index a6d791991..c87a224ac 100644
--- a/src/feature/control/control_fmt.c
+++ b/src/feature/control/control_fmt.c
@@ -23,6 +23,16 @@
 #include "core/or/socks_request_st.h"
 #include "feature/control/control_connection_st.h"
 
+/** Append a NUL-terminated string <b>s</b> to the end of
+ * <b>conn</b>-\>outbuf.
+ */
+void
+connection_write_str_to_buf(const char *s, control_connection_t *conn)
+{
+  size_t len = strlen(s);
+  connection_buf_add(s, len, TO_CONN(conn));
+}
+
 /** Acts like sprintf, but writes its formatted string to the end of
  * <b>conn</b>-\>outbuf. */
 void
diff --git a/src/feature/control/control_fmt.h b/src/feature/control/control_fmt.h
index e36edd2a4..1b4534cc3 100644
--- a/src/feature/control/control_fmt.h
+++ b/src/feature/control/control_fmt.h
@@ -12,6 +12,7 @@
 #ifndef TOR_CONTROL_FMT_H
 #define TOR_CONTROL_FMT_H
 
+void connection_write_str_to_buf(const char *s, control_connection_t *conn);
 void connection_printf_to_buf(control_connection_t *conn,
                                      const char *format, ...)
   CHECK_PRINTF(2,3);
diff --git a/src/feature/control/control_getinfo.c b/src/feature/control/control_getinfo.c
new file mode 100644
index 000000000..67a6e2731
--- /dev/null
+++ b/src/feature/control/control_getinfo.c
@@ -0,0 +1,1661 @@
+/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file control_getinfo.c
+ * \brief Implementation for miscellaneous controller getinfo commands.
+ */
+
+#define CONTROL_EVENTS_PRIVATE
+#define CONTROL_MODULE_PRIVATE
+#define CONTROL_GETINFO_PRIVATE
+
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "core/mainloop/connection.h"
+#include "core/mainloop/mainloop.h"
+#include "core/or/circuitlist.h"
+#include "core/or/connection_edge.h"
+#include "core/or/connection_or.h"
+#include "core/or/policies.h"
+#include "core/or/versions.h"
+#include "feature/client/addressmap.h"
+#include "feature/client/bridges.h"
+#include "feature/client/entrynodes.h"
+#include "feature/control/control.h"
+#include "feature/control/control_events.h"
+#include "feature/control/control_fmt.h"
+#include "feature/control/control_getinfo.h"
+#include "feature/control/fmt_serverstatus.h"
+#include "feature/control/getinfo_geoip.h"
+#include "feature/dircache/dirserv.h"
+#include "feature/dirclient/dirclient.h"
+#include "feature/dirclient/dlstatus.h"
+#include "feature/hibernate/hibernate.h"
+#include "feature/hs/hs_cache.h"
+#include "feature/hs_common/shared_random_client.h"
+#include "feature/nodelist/authcert.h"
+#include "feature/nodelist/microdesc.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/routerinfo.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/relay/router.h"
+#include "feature/relay/routermode.h"
+#include "feature/relay/selftest.h"
+#include "feature/rend/rendcache.h"
+#include "feature/stats/geoip_stats.h"
+#include "feature/stats/predict_ports.h"
+#include "lib/version/torversion.h"
+
+#include "core/or/entry_connection_st.h"
+#include "core/or/or_connection_st.h"
+#include "core/or/origin_circuit_st.h"
+#include "core/or/socks_request_st.h"
+#include "feature/control/control_connection_st.h"
+#include "feature/dircache/cached_dir_st.h"
+#include "feature/nodelist/extrainfo_st.h"
+#include "feature/nodelist/microdesc_st.h"
+#include "feature/nodelist/networkstatus_st.h"
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/routerlist_st.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef _WIN32
+#include <pwd.h>
+#endif
+
+static char *list_getinfo_options(void);
+static char *download_status_to_string(const download_status_t *dl);
+
+/** Implementation helper for GETINFO: knows the answers for various
+ * trivial-to-implement questions. */
+static int
+getinfo_helper_misc(control_connection_t *conn, const char *question,
+                    char **answer, const char **errmsg)
+{
+  (void) conn;
+  if (!strcmp(question, "version")) {
+    *answer = tor_strdup(get_version());
+  } else if (!strcmp(question, "bw-event-cache")) {
+    *answer = get_bw_samples();
+  } else if (!strcmp(question, "config-file")) {
+    const char *a = get_torrc_fname(0);
+    if (a)
+      *answer = tor_strdup(a);
+  } else if (!strcmp(question, "config-defaults-file")) {
+    const char *a = get_torrc_fname(1);
+    if (a)
+      *answer = tor_strdup(a);
+  } else if (!strcmp(question, "config-text")) {
+    *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
+  } else if (!strcmp(question, "config-can-saveconf")) {
+    *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1");
+  } else if (!strcmp(question, "info/names")) {
+    *answer = list_getinfo_options();
+  } else if (!strcmp(question, "dormant")) {
+    int dormant = rep_hist_circbuilding_dormant(time(NULL));
+    *answer = tor_strdup(dormant ? "1" : "0");
+  } else if (!strcmp(question, "events/names")) {
+    int i;
+    smartlist_t *event_names = smartlist_new();
+
+    for (i = 0; control_event_table[i].event_name != NULL; ++i) {
+      smartlist_add(event_names, (char *)control_event_table[i].event_name);
+    }
+
+    *answer = smartlist_join_strings(event_names, " ", 0, NULL);
+
+    smartlist_free(event_names);
+  } else if (!strcmp(question, "signal/names")) {
+    smartlist_t *signal_names = smartlist_new();
+    int j;
+    for (j = 0; signal_table[j].signal_name != NULL; ++j) {
+      smartlist_add(signal_names, (char*)signal_table[j].signal_name);
+    }
+
+    *answer = smartlist_join_strings(signal_names, " ", 0, NULL);
+
+    smartlist_free(signal_names);
+  } else if (!strcmp(question, "features/names")) {
+    *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS");
+  } else if (!strcmp(question, "address")) {
+    uint32_t addr;
+    if (router_pick_published_address(get_options(), &addr, 0) < 0) {
+      *errmsg = "Address unknown";
+      return -1;
+    }
+    *answer = tor_dup_ip(addr);
+  } else if (!strcmp(question, "traffic/read")) {
+    tor_asprintf(answer, "%"PRIu64, (get_bytes_read()));
+  } else if (!strcmp(question, "traffic/written")) {
+    tor_asprintf(answer, "%"PRIu64, (get_bytes_written()));
+  } else if (!strcmp(question, "uptime")) {
+    long uptime_secs = get_uptime();
+    tor_asprintf(answer, "%ld", uptime_secs);
+  } else if (!strcmp(question, "process/pid")) {
+    int myPid = -1;
+
+#ifdef _WIN32
+      myPid = _getpid();
+#else
+      myPid = getpid();
+#endif
+
+    tor_asprintf(answer, "%d", myPid);
+  } else if (!strcmp(question, "process/uid")) {
+#ifdef _WIN32
+      *answer = tor_strdup("-1");
+#else
+      int myUid = geteuid();
+      tor_asprintf(answer, "%d", myUid);
+#endif /* defined(_WIN32) */
+  } else if (!strcmp(question, "process/user")) {
+#ifdef _WIN32
+      *answer = tor_strdup("");
+#else
+      int myUid = geteuid();
+      const struct passwd *myPwEntry = tor_getpwuid(myUid);
+
+      if (myPwEntry) {
+        *answer = tor_strdup(myPwEntry->pw_name);
+      } else {
+        *answer = tor_strdup("");
+      }
+#endif /* defined(_WIN32) */
+  } else if (!strcmp(question, "process/descriptor-limit")) {
+    int max_fds = get_max_sockets();
+    tor_asprintf(answer, "%d", max_fds);
+  } else if (!strcmp(question, "limits/max-mem-in-queues")) {
+    tor_asprintf(answer, "%"PRIu64,
+                 (get_options()->MaxMemInQueues));
+  } else if (!strcmp(question, "fingerprint")) {
+    crypto_pk_t *server_key;
+    if (!server_mode(get_options())) {
+      *errmsg = "Not running in server mode";
+      return -1;
+    }
+    server_key = get_server_identity_key();
+    *answer = tor_malloc(HEX_DIGEST_LEN+1);
+    crypto_pk_get_fingerprint(server_key, *answer, 0);
+  }
+  return 0;
+}
+
+/** Awful hack: return a newly allocated string based on a routerinfo and
+ * (possibly) an extrainfo, sticking the read-history and write-history from
+ * <b>ei</b> into the resulting string.  The thing you get back won't
+ * necessarily have a valid signature.
+ *
+ * New code should never use this; it's for backward compatibility.
+ *
+ * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might
+ * not be NUL-terminated. */
+static char *
+munge_extrainfo_into_routerinfo(const char *ri_body,
+                                const signed_descriptor_t *ri,
+                                const signed_descriptor_t *ei)
+{
+  char *out = NULL, *outp;
+  int i;
+  const char *router_sig;
+  const char *ei_body = signed_descriptor_get_body(ei);
+  size_t ri_len = ri->signed_descriptor_len;
+  size_t ei_len = ei->signed_descriptor_len;
+  if (!ei_body)
+    goto bail;
+
+  outp = out = tor_malloc(ri_len+ei_len+1);
+  if (!(router_sig = tor_memstr(ri_body, ri_len, "\nrouter-signature")))
+    goto bail;
+  ++router_sig;
+  memcpy(out, ri_body, router_sig-ri_body);
+  outp += router_sig-ri_body;
+
+  for (i=0; i < 2; ++i) {
+    const char *kwd = i ? "\nwrite-history " : "\nread-history ";
+    const char *cp, *eol;
+    if (!(cp = tor_memstr(ei_body, ei_len, kwd)))
+      continue;
+    ++cp;
+    if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body))))
+      continue;
+    memcpy(outp, cp, eol-cp+1);
+    outp += eol-cp+1;
+  }
+  memcpy(outp, router_sig, ri_len - (router_sig-ri_body));
+  *outp++ = '\0';
+  tor_assert(outp-out < (int)(ri_len+ei_len+1));
+
+  return out;
+ bail:
+  tor_free(out);
+  return tor_strndup(ri_body, ri->signed_descriptor_len);
+}
+
+/** Implementation helper for GETINFO: answers requests for information about
+ * which ports are bound. */
+static int
+getinfo_helper_listeners(control_connection_t *control_conn,
+                         const char *question,
+                         char **answer, const char **errmsg)
+{
+  int type;
+  smartlist_t *res;
+
+  (void)control_conn;
+  (void)errmsg;
+
+  if (!strcmp(question, "net/listeners/or"))
+    type = CONN_TYPE_OR_LISTENER;
+  else if (!strcmp(question, "net/listeners/extor"))
+    type = CONN_TYPE_EXT_OR_LISTENER;
+  else if (!strcmp(question, "net/listeners/dir"))
+    type = CONN_TYPE_DIR_LISTENER;
+  else if (!strcmp(question, "net/listeners/socks"))
+    type = CONN_TYPE_AP_LISTENER;
+  else if (!strcmp(question, "net/listeners/trans"))
+    type = CONN_TYPE_AP_TRANS_LISTENER;
+  else if (!strcmp(question, "net/listeners/natd"))
+    type = CONN_TYPE_AP_NATD_LISTENER;
+  else if (!strcmp(question, "net/listeners/httptunnel"))
+    type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER;
+  else if (!strcmp(question, "net/listeners/dns"))
+    type = CONN_TYPE_AP_DNS_LISTENER;
+  else if (!strcmp(question, "net/listeners/control"))
+    type = CONN_TYPE_CONTROL_LISTENER;
+  else
+    return 0; /* unknown key */
+
+  res = smartlist_new();
+  SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) {
+    struct sockaddr_storage ss;
+    socklen_t ss_len = sizeof(ss);
+
+    if (conn->type != type || conn->marked_for_close || !SOCKET_OK(conn->s))
+      continue;
+
+    if (getsockname(conn->s, (struct sockaddr *)&ss, &ss_len) < 0) {
+      smartlist_add_asprintf(res, "%s:%d", conn->address, (int)conn->port);
+    } else {
+      char *tmp = tor_sockaddr_to_str((struct sockaddr *)&ss);
+      smartlist_add(res, esc_for_log(tmp));
+      tor_free(tmp);
+    }
+
+  } SMARTLIST_FOREACH_END(conn);
+
+  *answer = smartlist_join_strings(res, " ", 0, NULL);
+
+  SMARTLIST_FOREACH(res, char *, cp, tor_free(cp));
+  smartlist_free(res);
+  return 0;
+}
+
+/** Implementation helper for GETINFO: answers requests for information about
+ * the current time in both local and UTC forms. */
+STATIC int
+getinfo_helper_current_time(control_connection_t *control_conn,
+                         const char *question,
+                         char **answer, const char **errmsg)
+{
+  (void)control_conn;
+  (void)errmsg;
+
+  struct timeval now;
+  tor_gettimeofday(&now);
+  char timebuf[ISO_TIME_LEN+1];
+
+  if (!strcmp(question, "current-time/local"))
+    format_local_iso_time_nospace(timebuf, (time_t)now.tv_sec);
+  else if (!strcmp(question, "current-time/utc"))
+    format_iso_time_nospace(timebuf, (time_t)now.tv_sec);
+  else
+    return 0;
+
+  *answer = tor_strdup(timebuf);
+  return 0;
+}
+
+/** Implementation helper for GETINFO: knows the answers for questions about
+ * directory information. */
+STATIC int
+getinfo_helper_dir(control_connection_t *control_conn,
+                   const char *question, char **answer,
+                   const char **errmsg)
+{
+  (void) control_conn;
+  if (!strcmpstart(question, "desc/id/")) {
+    const routerinfo_t *ri = NULL;
+    const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0);
+    if (node)
+      ri = node->ri;
+    if (ri) {
+      const char *body = signed_descriptor_get_body(&ri->cache_info);
+      if (body)
+        *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+    } else if (! we_fetch_router_descriptors(get_options())) {
+      /* Descriptors won't be available, provide proper error */
+      *errmsg = "We fetch microdescriptors, not router "
+                "descriptors. You'll need to use md/id/* "
+                "instead of desc/id/*.";
+      return 0;
+    }
+  } else if (!strcmpstart(question, "desc/name/")) {
+    const routerinfo_t *ri = NULL;
+    /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the
+     * warning goes to the user, not to the controller. */
+    const node_t *node =
+      node_get_by_nickname(question+strlen("desc/name/"), 0);
+    if (node)
+      ri = node->ri;
+    if (ri) {
+      const char *body = signed_descriptor_get_body(&ri->cache_info);
+      if (body)
+        *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+    } else if (! we_fetch_router_descriptors(get_options())) {
+      /* Descriptors won't be available, provide proper error */
+      *errmsg = "We fetch microdescriptors, not router "
+                "descriptors. You'll need to use md/name/* "
+                "instead of desc/name/*.";
+      return 0;
+    }
+  } else if (!strcmp(question, "desc/download-enabled")) {
+    int r = we_fetch_router_descriptors(get_options());
+    tor_asprintf(answer, "%d", !!r);
+  } else if (!strcmp(question, "desc/all-recent")) {
+    routerlist_t *routerlist = router_get_routerlist();
+    smartlist_t *sl = smartlist_new();
+    if (routerlist && routerlist->routers) {
+      SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri,
+      {
+        const char *body = signed_descriptor_get_body(&ri->cache_info);
+        if (body)
+          smartlist_add(sl,
+                  tor_strndup(body, ri->cache_info.signed_descriptor_len));
+      });
+    }
+    *answer = smartlist_join_strings(sl, "", 0, NULL);
+    SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+    smartlist_free(sl);
+  } else if (!strcmp(question, "desc/all-recent-extrainfo-hack")) {
+    /* XXXX Remove this once Torstat asks for extrainfos. */
+    routerlist_t *routerlist = router_get_routerlist();
+    smartlist_t *sl = smartlist_new();
+    if (routerlist && routerlist->routers) {
+      SMARTLIST_FOREACH_BEGIN(routerlist->routers, const routerinfo_t *, ri) {
+        const char *body = signed_descriptor_get_body(&ri->cache_info);
+        signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest(
+                                     ri->cache_info.extra_info_digest);
+        if (ei && body) {
+          smartlist_add(sl, munge_extrainfo_into_routerinfo(body,
+                                                        &ri->cache_info, ei));
+        } else if (body) {
+          smartlist_add(sl,
+                  tor_strndup(body, ri->cache_info.signed_descriptor_len));
+        }
+      } SMARTLIST_FOREACH_END(ri);
+    }
+    *answer = smartlist_join_strings(sl, "", 0, NULL);
+    SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+    smartlist_free(sl);
+  } else if (!strcmpstart(question, "hs/client/desc/id/")) {
+    hostname_type_t addr_type;
+
+    question += strlen("hs/client/desc/id/");
+    if (rend_valid_v2_service_id(question)) {
+      addr_type = ONION_V2_HOSTNAME;
+    } else if (hs_address_is_valid(question)) {
+      addr_type = ONION_V3_HOSTNAME;
+    } else {
+      *errmsg = "Invalid address";
+      return -1;
+    }
+
+    if (addr_type == ONION_V2_HOSTNAME) {
+      rend_cache_entry_t *e = NULL;
+      if (!rend_cache_lookup_entry(question, -1, &e)) {
+        /* Descriptor found in cache */
+        *answer = tor_strdup(e->desc);
+      } else {
+        *errmsg = "Not found in cache";
+        return -1;
+      }
+    } else {
+      ed25519_public_key_t service_pk;
+      const char *desc;
+
+      /* The check before this if/else makes sure of this. */
+      tor_assert(addr_type == ONION_V3_HOSTNAME);
+
+      if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) {
+        *errmsg = "Invalid v3 address";
+        return -1;
+      }
+
+      desc = hs_cache_lookup_encoded_as_client(&service_pk);
+      if (desc) {
+        *answer = tor_strdup(desc);
+      } else {
+        *errmsg = "Not found in cache";
+        return -1;
+      }
+    }
+  } else if (!strcmpstart(question, "hs/service/desc/id/")) {
+    hostname_type_t addr_type;
+
+    question += strlen("hs/service/desc/id/");
+    if (rend_valid_v2_service_id(question)) {
+      addr_type = ONION_V2_HOSTNAME;
+    } else if (hs_address_is_valid(question)) {
+      addr_type = ONION_V3_HOSTNAME;
+    } else {
+      *errmsg = "Invalid address";
+      return -1;
+    }
+    rend_cache_entry_t *e = NULL;
+
+    if (addr_type == ONION_V2_HOSTNAME) {
+      if (!rend_cache_lookup_v2_desc_as_service(question, &e)) {
+        /* Descriptor found in cache */
+        *answer = tor_strdup(e->desc);
+      } else {
+        *errmsg = "Not found in cache";
+        return -1;
+      }
+    } else {
+      ed25519_public_key_t service_pk;
+      char *desc;
+
+      /* The check before this if/else makes sure of this. */
+      tor_assert(addr_type == ONION_V3_HOSTNAME);
+
+      if (hs_parse_address(question, &service_pk, NULL, NULL) < 0) {
+        *errmsg = "Invalid v3 address";
+        return -1;
+      }
+
+      desc = hs_service_lookup_current_desc(&service_pk);
+      if (desc) {
+        /* Newly allocated string, we have ownership. */
+        *answer = desc;
+      } else {
+        *errmsg = "Not found in cache";
+        return -1;
+      }
+    }
+  } else if (!strcmp(question, "md/all")) {
+    const smartlist_t *nodes = nodelist_get_list();
+    tor_assert(nodes);
+
+    if (smartlist_len(nodes) == 0) {
+      *answer = tor_strdup("");
+      return 0;
+    }
+
+    smartlist_t *microdescs = smartlist_new();
+
+    SMARTLIST_FOREACH_BEGIN(nodes, node_t *, n) {
+      if (n->md && n->md->body) {
+        char *copy = tor_strndup(n->md->body, n->md->bodylen);
+        smartlist_add(microdescs, copy);
+      }
+    } SMARTLIST_FOREACH_END(n);
+
+    *answer = smartlist_join_strings(microdescs, "", 0, NULL);
+    SMARTLIST_FOREACH(microdescs, char *, md, tor_free(md));
+    smartlist_free(microdescs);
+  } else if (!strcmpstart(question, "md/id/")) {
+    const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0);
+    const microdesc_t *md = NULL;
+    if (node) md = node->md;
+    if (md && md->body) {
+      *answer = tor_strndup(md->body, md->bodylen);
+    }
+  } else if (!strcmpstart(question, "md/name/")) {
+    /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the
+     * warning goes to the user, not to the controller. */
+    const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0);
+    /* XXXX duplicated code */
+    const microdesc_t *md = NULL;
+    if (node) md = node->md;
+    if (md && md->body) {
+      *answer = tor_strndup(md->body, md->bodylen);
+    }
+  } else if (!strcmp(question, "md/download-enabled")) {
+    int r = we_fetch_microdescriptors(get_options());
+    tor_asprintf(answer, "%d", !!r);
+  } else if (!strcmpstart(question, "desc-annotations/id/")) {
+    const routerinfo_t *ri = NULL;
+    const node_t *node =
+      node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0);
+    if (node)
+      ri = node->ri;
+    if (ri) {
+      const char *annotations =
+        signed_descriptor_get_annotations(&ri->cache_info);
+      if (annotations)
+        *answer = tor_strndup(annotations,
+                              ri->cache_info.annotations_len);
+    }
+  } else if (!strcmpstart(question, "dir/server/")) {
+    size_t answer_len = 0;
+    char *url = NULL;
+    smartlist_t *descs = smartlist_new();
+    const char *msg;
+    int res;
+    char *cp;
+    tor_asprintf(&url, "/tor/%s", question+4);
+    res = dirserv_get_routerdescs(descs, url, &msg);
+    if (res) {
+      log_warn(LD_CONTROL, "getinfo '%s': %s", question, msg);
+      smartlist_free(descs);
+      tor_free(url);
+      *errmsg = msg;
+      return -1;
+    }
+    SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd,
+                      answer_len += sd->signed_descriptor_len);
+    cp = *answer = tor_malloc(answer_len+1);
+    SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd,
+                      {
+                        memcpy(cp, signed_descriptor_get_body(sd),
+                               sd->signed_descriptor_len);
+                        cp += sd->signed_descriptor_len;
+                      });
+    *cp = '\0';
+    tor_free(url);
+    smartlist_free(descs);
+  } else if (!strcmpstart(question, "dir/status/")) {
+    *answer = tor_strdup("");
+  } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */
+    if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) {
+      const cached_dir_t *consensus = dirserv_get_consensus("ns");
+      if (consensus)
+        *answer = tor_strdup(consensus->dir);
+    }
+    if (!*answer) { /* try loading it from disk */
+      tor_mmap_t *mapped = networkstatus_map_cached_consensus("ns");
+      if (mapped) {
+        *answer = tor_memdup_nulterm(mapped->data, mapped->size);
+        tor_munmap_file(mapped);
+      }
+      if (!*answer) { /* generate an error */
+        *errmsg = "Could not open cached consensus. "
+          "Make sure FetchUselessDescriptors is set to 1.";
+        return -1;
+      }
+    }
+  } else if (!strcmp(question, "network-status")) { /* v1 */
+    static int network_status_warned = 0;
+    if (!network_status_warned) {
+      log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will "
+               "go away in a future version of Tor.");
+      network_status_warned = 1;
+    }
+    routerlist_t *routerlist = router_get_routerlist();
+    if (!routerlist || !routerlist->routers ||
+        list_server_status_v1(routerlist->routers, answer, 1) < 0) {
+      return -1;
+    }
+  } else if (!strcmpstart(question, "extra-info/digest/")) {
+    question += strlen("extra-info/digest/");
+    if (strlen(question) == HEX_DIGEST_LEN) {
+      char d[DIGEST_LEN];
+      signed_descriptor_t *sd = NULL;
+      if (base16_decode(d, sizeof(d), question, strlen(question))
+                        == sizeof(d)) {
+        /* XXXX this test should move into extrainfo_get_by_descriptor_digest,
+         * but I don't want to risk affecting other parts of the code,
+         * especially since the rules for using our own extrainfo (including
+         * when it might be freed) are different from those for using one
+         * we have downloaded. */
+        if (router_extrainfo_digest_is_me(d))
+          sd = &(router_get_my_extrainfo()->cache_info);
+        else
+          sd = extrainfo_get_by_descriptor_digest(d);
+      }
+      if (sd) {
+        const char *body = signed_descriptor_get_body(sd);
+        if (body)
+          *answer = tor_strndup(body, sd->signed_descriptor_len);
+      }
+    }
+  }
+
+  return 0;
+}
+
+/** Given a smartlist of 20-byte digests, return a newly allocated string
+ * containing each of those digests in order, formatted in HEX, and terminated
+ * with a newline. */
+static char *
+digest_list_to_string(const smartlist_t *sl)
+{
+  int len;
+  char *result, *s;
+
+  /* Allow for newlines, and a \0 at the end */
+  len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1;
+  result = tor_malloc_zero(len);
+
+  s = result;
+  SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) {
+    base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN);
+    s[HEX_DIGEST_LEN] = '\n';
+    s += HEX_DIGEST_LEN + 1;
+  } SMARTLIST_FOREACH_END(digest);
+  *s = '\0';
+
+  return result;
+}
+
+/** Turn a download_status_t into a human-readable description in a newly
+ * allocated string.  The format is specified in control-spec.txt, under
+ * the documentation for "GETINFO download/..." .  */
+static char *
+download_status_to_string(const download_status_t *dl)
+{
+  char *rv = NULL;
+  char tbuf[ISO_TIME_LEN+1];
+  const char *schedule_str, *want_authority_str;
+  const char *increment_on_str, *backoff_str;
+
+  if (dl) {
+    /* Get some substrings of the eventual output ready */
+    format_iso_time(tbuf, download_status_get_next_attempt_at(dl));
+
+    switch (dl->schedule) {
+      case DL_SCHED_GENERIC:
+        schedule_str = "DL_SCHED_GENERIC";
+        break;
+      case DL_SCHED_CONSENSUS:
+        schedule_str = "DL_SCHED_CONSENSUS";
+        break;
+      case DL_SCHED_BRIDGE:
+        schedule_str = "DL_SCHED_BRIDGE";
+        break;
+      default:
+        schedule_str = "unknown";
+        break;
+    }
+
+    switch (dl->want_authority) {
+      case DL_WANT_ANY_DIRSERVER:
+        want_authority_str = "DL_WANT_ANY_DIRSERVER";
+        break;
+      case DL_WANT_AUTHORITY:
+        want_authority_str = "DL_WANT_AUTHORITY";
+        break;
+      default:
+        want_authority_str = "unknown";
+        break;
+    }
+
+    switch (dl->increment_on) {
+      case DL_SCHED_INCREMENT_FAILURE:
+        increment_on_str = "DL_SCHED_INCREMENT_FAILURE";
+        break;
+      case DL_SCHED_INCREMENT_ATTEMPT:
+        increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT";
+        break;
+      default:
+        increment_on_str = "unknown";
+        break;
+    }
+
+    backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL";
+
+    /* Now assemble them */
+    tor_asprintf(&rv,
+                 "next-attempt-at %s\n"
+                 "n-download-failures %u\n"
+                 "n-download-attempts %u\n"
+                 "schedule %s\n"
+                 "want-authority %s\n"
+                 "increment-on %s\n"
+                 "backoff %s\n"
+                 "last-backoff-position %u\n"
+                 "last-delay-used %d\n",
+                 tbuf,
+                 dl->n_download_failures,
+                 dl->n_download_attempts,
+                 schedule_str,
+                 want_authority_str,
+                 increment_on_str,
+                 backoff_str,
+                 dl->last_backoff_position,
+                 dl->last_delay_used);
+  }
+
+  return rv;
+}
+
+/** Handle the consensus download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_networkstatus(const char *flavor,
+                                       download_status_t **dl_to_emit,
+                                       const char **errmsg)
+{
+  /*
+   * We get the one for the current bootstrapped status by default, or
+   * take an extra /bootstrap or /running suffix
+   */
+  if (strcmp(flavor, "ns") == 0) {
+    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
+  } else if (strcmp(flavor, "ns/bootstrap") == 0) {
+    *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
+  } else if (strcmp(flavor, "ns/running") == 0 ) {
+    *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
+  } else if (strcmp(flavor, "microdesc") == 0) {
+    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
+  } else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
+    *dl_to_emit =
+      networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
+  } else if (strcmp(flavor, "microdesc/running") == 0) {
+    *dl_to_emit =
+      networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
+  } else {
+    *errmsg = "Unknown flavor";
+  }
+}
+
+/** Handle the cert download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_cert(const char *fp_sk_req,
+                              download_status_t **dl_to_emit,
+                              smartlist_t **digest_list,
+                              const char **errmsg)
+{
+  const char *sk_req;
+  char id_digest[DIGEST_LEN];
+  char sk_digest[DIGEST_LEN];
+
+  /*
+   * We have to handle four cases; fp_sk_req is the request with
+   * a prefix of "downloads/cert/" snipped off.
+   *
+   * Case 1: fp_sk_req = "fps"
+   *  - We should emit a digest_list with a list of all the identity
+   *    fingerprints that can be queried for certificate download status;
+   *    get it by calling list_authority_ids_with_downloads().
+   *
+   * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp
+   *  - We want the default certificate for this identity fingerprint's
+   *    download status; this is the download we get from URLs starting
+   *    in /fp/ on the directory server.  We can get it with
+   *    id_only_download_status_for_authority_id().
+   *
+   * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp
+   *  - We want a list of all signing key digests for this identity
+   *    fingerprint which can be queried for certificate download status.
+   *    Get it with list_sk_digests_for_authority_id().
+   *
+   * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and
+   *         signing key digest sk
+   *   - We want the download status for the certificate for this specific
+   *     signing key and fingerprint.  These correspond to the ones we get
+   *     from URLs starting in /fp-sk/ on the directory server.  Get it with
+   *     list_sk_digests_for_authority_id().
+   */
+
+  if (strcmp(fp_sk_req, "fps") == 0) {
+    *digest_list = list_authority_ids_with_downloads();
+    if (!(*digest_list)) {
+      *errmsg = "Failed to get list of authority identity digests (!)";
+    }
+  } else if (!strcmpstart(fp_sk_req, "fp/")) {
+    fp_sk_req += strlen("fp/");
+    /* Okay, look for another / to tell the fp from fp-sk cases */
+    sk_req = strchr(fp_sk_req, '/');
+    if (sk_req) {
+      /* okay, split it here and try to parse <fp> */
+      if (base16_decode(id_digest, DIGEST_LEN,
+                        fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) {
+        /* Skip past the '/' */
+        ++sk_req;
+        if (strcmp(sk_req, "sks") == 0) {
+          /* We're asking for the list of signing key fingerprints */
+          *digest_list = list_sk_digests_for_authority_id(id_digest);
+          if (!(*digest_list)) {
+            *errmsg = "Failed to get list of signing key digests for this "
+                      "authority identity digest";
+          }
+        } else {
+          /* We've got a signing key digest */
+          if (base16_decode(sk_digest, DIGEST_LEN,
+                            sk_req, strlen(sk_req)) == DIGEST_LEN) {
+            *dl_to_emit =
+              download_status_for_authority_id_and_sk(id_digest, sk_digest);
+            if (!(*dl_to_emit)) {
+              *errmsg = "Failed to get download status for this identity/"
+                        "signing key digest pair";
+            }
+          } else {
+            *errmsg = "That didn't look like a signing key digest";
+          }
+        }
+      } else {
+        *errmsg = "That didn't look like an identity digest";
+      }
+    } else {
+      /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */
+      if (strlen(fp_sk_req) == HEX_DIGEST_LEN) {
+        if (base16_decode(id_digest, DIGEST_LEN,
+                          fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) {
+          *dl_to_emit = id_only_download_status_for_authority_id(id_digest);
+          if (!(*dl_to_emit)) {
+            *errmsg = "Failed to get download status for this authority "
+                      "identity digest";
+          }
+        } else {
+          *errmsg = "That didn't look like a digest";
+        }
+      } else {
+        *errmsg = "That didn't look like a digest";
+      }
+    }
+  } else {
+    *errmsg = "Unknown certificate download status query";
+  }
+}
+
+/** Handle the routerdesc download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_desc(const char *desc_req,
+                              download_status_t **dl_to_emit,
+                              smartlist_t **digest_list,
+                              const char **errmsg)
+{
+  char desc_digest[DIGEST_LEN];
+  /*
+   * Two cases to handle here:
+   *
+   * Case 1: desc_req = "descs"
+   *   - Emit a list of all router descriptor digests, which we get by
+   *     calling router_get_descriptor_digests(); this can return NULL
+   *     if we have no current ns-flavor consensus.
+   *
+   * Case 2: desc_req = <fp>
+   *   - Check on the specified fingerprint and emit its download_status_t
+   *     using router_get_dl_status_by_descriptor_digest().
+   */
+
+  if (strcmp(desc_req, "descs") == 0) {
+    *digest_list = router_get_descriptor_digests();
+    if (!(*digest_list)) {
+      *errmsg = "We don't seem to have a networkstatus-flavored consensus";
+    }
+    /*
+     * Microdescs don't use the download_status_t mechanism, so we don't
+     * answer queries about their downloads here; see microdesc.c.
+     */
+  } else if (strlen(desc_req) == HEX_DIGEST_LEN) {
+    if (base16_decode(desc_digest, DIGEST_LEN,
+                      desc_req, strlen(desc_req)) == DIGEST_LEN) {
+      /* Okay we got a digest-shaped thing; try asking for it */
+      *dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest);
+      if (!(*dl_to_emit)) {
+        *errmsg = "No such descriptor digest found";
+      }
+    } else {
+      *errmsg = "That didn't look like a digest";
+    }
+  } else {
+    *errmsg = "Unknown router descriptor download status query";
+  }
+}
+
+/** Handle the bridge download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_bridge(const char *bridge_req,
+                                download_status_t **dl_to_emit,
+                                smartlist_t **digest_list,
+                                const char **errmsg)
+{
+  char bridge_digest[DIGEST_LEN];
+  /*
+   * Two cases to handle here:
+   *
+   * Case 1: bridge_req = "bridges"
+   *   - Emit a list of all bridge identity digests, which we get by
+   *     calling list_bridge_identities(); this can return NULL if we are
+   *     not using bridges.
+   *
+   * Case 2: bridge_req = <fp>
+   *   - Check on the specified fingerprint and emit its download_status_t
+   *     using get_bridge_dl_status_by_id().
+   */
+
+  if (strcmp(bridge_req, "bridges") == 0) {
+    *digest_list = list_bridge_identities();
+    if (!(*digest_list)) {
+      *errmsg = "We don't seem to be using bridges";
+    }
+  } else if (strlen(bridge_req) == HEX_DIGEST_LEN) {
+    if (base16_decode(bridge_digest, DIGEST_LEN,
+                      bridge_req, strlen(bridge_req)) == DIGEST_LEN) {
+      /* Okay we got a digest-shaped thing; try asking for it */
+      *dl_to_emit = get_bridge_dl_status_by_id(bridge_digest);
+      if (!(*dl_to_emit)) {
+        *errmsg = "No such bridge identity digest found";
+      }
+    } else {
+      *errmsg = "That didn't look like a digest";
+    }
+  } else {
+    *errmsg = "Unknown bridge descriptor download status query";
+  }
+}
+
+/** Implementation helper for GETINFO: knows the answers for questions about
+ * download status information. */
+STATIC int
+getinfo_helper_downloads(control_connection_t *control_conn,
+                   const char *question, char **answer,
+                   const char **errmsg)
+{
+  download_status_t *dl_to_emit = NULL;
+  smartlist_t *digest_list = NULL;
+
+  /* Assert args are sane */
+  tor_assert(control_conn != NULL);
+  tor_assert(question != NULL);
+  tor_assert(answer != NULL);
+  tor_assert(errmsg != NULL);
+
+  /* We check for this later to see if we should supply a default */
+  *errmsg = NULL;
+
+  /* Are we after networkstatus downloads? */
+  if (!strcmpstart(question, "downloads/networkstatus/")) {
+    getinfo_helper_downloads_networkstatus(
+        question + strlen("downloads/networkstatus/"),
+        &dl_to_emit, errmsg);
+  /* Certificates? */
+  } else if (!strcmpstart(question, "downloads/cert/")) {
+    getinfo_helper_downloads_cert(
+        question + strlen("downloads/cert/"),
+        &dl_to_emit, &digest_list, errmsg);
+  /* Router descriptors? */
+  } else if (!strcmpstart(question, "downloads/desc/")) {
+    getinfo_helper_downloads_desc(
+        question + strlen("downloads/desc/"),
+        &dl_to_emit, &digest_list, errmsg);
+  /* Bridge descriptors? */
+  } else if (!strcmpstart(question, "downloads/bridge/")) {
+    getinfo_helper_downloads_bridge(
+        question + strlen("downloads/bridge/"),
+        &dl_to_emit, &digest_list, errmsg);
+  } else {
+    *errmsg = "Unknown download status query";
+  }
+
+  if (dl_to_emit) {
+    *answer = download_status_to_string(dl_to_emit);
+
+    return 0;
+  } else if (digest_list) {
+    *answer = digest_list_to_string(digest_list);
+    SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s));
+    smartlist_free(digest_list);
+
+    return 0;
+  } else {
+    if (!(*errmsg)) {
+      *errmsg = "Unknown error";
+    }
+
+    return -1;
+  }
+}
+
+/** Implementation helper for GETINFO: knows how to generate summaries of the
+ * current states of things we send events about. */
+static int
+getinfo_helper_events(control_connection_t *control_conn,
+                      const char *question, char **answer,
+                      const char **errmsg)
+{
+  const or_options_t *options = get_options();
+  (void) control_conn;
+  if (!strcmp(question, "circuit-status")) {
+    smartlist_t *status = smartlist_new();
+    SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) {
+      origin_circuit_t *circ;
+      char *circdesc;
+      const char *state;
+      if (! CIRCUIT_IS_ORIGIN(circ_) || circ_->marked_for_close)
+        continue;
+      circ = TO_ORIGIN_CIRCUIT(circ_);
+
+      if (circ->base_.state == CIRCUIT_STATE_OPEN)
+        state = "BUILT";
+      else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT)
+        state = "GUARD_WAIT";
+      else if (circ->cpath)
+        state = "EXTENDED";
+      else
+        state = "LAUNCHED";
+
+      circdesc = circuit_describe_status_for_controller(circ);
+
+      smartlist_add_asprintf(status, "%lu %s%s%s",
+                   (unsigned long)circ->global_identifier,
+                   state, *circdesc ? " " : "", circdesc);
+      tor_free(circdesc);
+    }
+    SMARTLIST_FOREACH_END(circ_);
+    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
+    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
+    smartlist_free(status);
+  } else if (!strcmp(question, "stream-status")) {
+    smartlist_t *conns = get_connection_array();
+    smartlist_t *status = smartlist_new();
+    char buf[256];
+    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+      const char *state;
+      entry_connection_t *conn;
+      circuit_t *circ;
+      origin_circuit_t *origin_circ = NULL;
+      if (base_conn->type != CONN_TYPE_AP ||
+          base_conn->marked_for_close ||
+          base_conn->state == AP_CONN_STATE_SOCKS_WAIT ||
+          base_conn->state == AP_CONN_STATE_NATD_WAIT)
+        continue;
+      conn = TO_ENTRY_CONN(base_conn);
+      switch (base_conn->state)
+        {
+        case AP_CONN_STATE_CONTROLLER_WAIT:
+        case AP_CONN_STATE_CIRCUIT_WAIT:
+          if (conn->socks_request &&
+              SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command))
+            state = "NEWRESOLVE";
+          else
+            state = "NEW";
+          break;
+        case AP_CONN_STATE_RENDDESC_WAIT:
+        case AP_CONN_STATE_CONNECT_WAIT:
+          state = "SENTCONNECT"; break;
+        case AP_CONN_STATE_RESOLVE_WAIT:
+          state = "SENTRESOLVE"; break;
+        case AP_CONN_STATE_OPEN:
+          state = "SUCCEEDED"; break;
+        default:
+          log_warn(LD_BUG, "Asked for stream in unknown state %d",
+                   base_conn->state);
+          continue;
+        }
+      circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn));
+      if (circ && CIRCUIT_IS_ORIGIN(circ))
+        origin_circ = TO_ORIGIN_CIRCUIT(circ);
+      write_stream_target_to_buf(conn, buf, sizeof(buf));
+      smartlist_add_asprintf(status, "%lu %s %lu %s",
+                   (unsigned long) base_conn->global_identifier,state,
+                   origin_circ?
+                         (unsigned long)origin_circ->global_identifier : 0ul,
+                   buf);
+    } SMARTLIST_FOREACH_END(base_conn);
+    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
+    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
+    smartlist_free(status);
+  } else if (!strcmp(question, "orconn-status")) {
+    smartlist_t *conns = get_connection_array();
+    smartlist_t *status = smartlist_new();
+    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+      const char *state;
+      char name[128];
+      or_connection_t *conn;
+      if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close)
+        continue;
+      conn = TO_OR_CONN(base_conn);
+      if (conn->base_.state == OR_CONN_STATE_OPEN)
+        state = "CONNECTED";
+      else if (conn->nickname)
+        state = "LAUNCHED";
+      else
+        state = "NEW";
+      orconn_target_get_name(name, sizeof(name), conn);
+      smartlist_add_asprintf(status, "%s %s", name, state);
+    } SMARTLIST_FOREACH_END(base_conn);
+    *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
+    SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
+    smartlist_free(status);
+  } else if (!strcmpstart(question, "address-mappings/")) {
+    time_t min_e, max_e;
+    smartlist_t *mappings;
+    question += strlen("address-mappings/");
+    if (!strcmp(question, "all")) {
+      min_e = 0; max_e = TIME_MAX;
+    } else if (!strcmp(question, "cache")) {
+      min_e = 2; max_e = TIME_MAX;
+    } else if (!strcmp(question, "config")) {
+      min_e = 0; max_e = 0;
+    } else if (!strcmp(question, "control")) {
+      min_e = 1; max_e = 1;
+    } else {
+      return 0;
+    }
+    mappings = smartlist_new();
+    addressmap_get_mappings(mappings, min_e, max_e, 1);
+    *answer = smartlist_join_strings(mappings, "\r\n", 0, NULL);
+    SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp));
+    smartlist_free(mappings);
+  } else if (!strcmpstart(question, "status/")) {
+    /* Note that status/ is not a catch-all for events; there's only supposed
+     * to be a status GETINFO if there's a corresponding STATUS event. */
+    if (!strcmp(question, "status/circuit-established")) {
+      *answer = tor_strdup(have_completed_a_circuit() ? "1" : "0");
+    } else if (!strcmp(question, "status/enough-dir-info")) {
+      *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0");
+    } else if (!strcmp(question, "status/good-server-descriptor") ||
+               !strcmp(question, "status/accepted-server-descriptor")) {
+      /* They're equivalent for now, until we can figure out how to make
+       * good-server-descriptor be what we want. See comment in
+       * control-spec.txt. */
+      *answer = tor_strdup(directories_have_accepted_server_descriptor()
+                           ? "1" : "0");
+    } else if (!strcmp(question, "status/reachability-succeeded/or")) {
+      *answer = tor_strdup(check_whether_orport_reachable(options) ?
+                            "1" : "0");
+    } else if (!strcmp(question, "status/reachability-succeeded/dir")) {
+      *answer = tor_strdup(check_whether_dirport_reachable(options) ?
+                            "1" : "0");
+    } else if (!strcmp(question, "status/reachability-succeeded")) {
+      tor_asprintf(answer, "OR=%d DIR=%d",
+                   check_whether_orport_reachable(options) ? 1 : 0,
+                   check_whether_dirport_reachable(options) ? 1 : 0);
+    } else if (!strcmp(question, "status/bootstrap-phase")) {
+      *answer = control_event_boot_last_msg();
+    } else if (!strcmpstart(question, "status/version/")) {
+      int is_server = server_mode(options);
+      networkstatus_t *c = networkstatus_get_latest_consensus();
+      version_status_t status;
+      const char *recommended;
+      if (c) {
+        recommended = is_server ? c->server_versions : c->client_versions;
+        status = tor_version_is_obsolete(VERSION, recommended);
+      } else {
+        recommended = "?";
+        status = VS_UNKNOWN;
+      }
+
+      if (!strcmp(question, "status/version/recommended")) {
+        *answer = tor_strdup(recommended);
+        return 0;
+      }
+      if (!strcmp(question, "status/version/current")) {
+        switch (status)
+          {
+          case VS_RECOMMENDED: *answer = tor_strdup("recommended"); break;
+          case VS_OLD: *answer = tor_strdup("obsolete"); break;
+          case VS_NEW: *answer = tor_strdup("new"); break;
+          case VS_NEW_IN_SERIES: *answer = tor_strdup("new in series"); break;
+          case VS_UNRECOMMENDED: *answer = tor_strdup("unrecommended"); break;
+          case VS_EMPTY: *answer = tor_strdup("none recommended"); break;
+          case VS_UNKNOWN: *answer = tor_strdup("unknown"); break;
+          default: tor_fragile_assert();
+          }
+      }
+    } else if (!strcmp(question, "status/clients-seen")) {
+      char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL));
+      if (!bridge_stats) {
+        *errmsg = "No bridge-client stats available";
+        return -1;
+      }
+      *answer = bridge_stats;
+    } else if (!strcmp(question, "status/fresh-relay-descs")) {
+      if (!server_mode(options)) {
+        *errmsg = "Only relays have descriptors";
+        return -1;
+      }
+      routerinfo_t *r;
+      extrainfo_t *e;
+      if (router_build_fresh_descriptor(&r, &e) < 0) {
+        *errmsg = "Error generating descriptor";
+        return -1;
+      }
+      size_t size = r->cache_info.signed_descriptor_len + 1;
+      if (e) {
+        size += e->cache_info.signed_descriptor_len + 1;
+      }
+      tor_assert(r->cache_info.signed_descriptor_len);
+      char *descs = tor_malloc(size);
+      char *cp = descs;
+      memcpy(cp, signed_descriptor_get_body(&r->cache_info),
+             r->cache_info.signed_descriptor_len);
+      cp += r->cache_info.signed_descriptor_len - 1;
+      if (e) {
+        if (cp[0] == '\0') {
+          cp[0] = '\n';
+        } else if (cp[0] != '\n') {
+          cp[1] = '\n';
+          cp++;
+        }
+        memcpy(cp, signed_descriptor_get_body(&e->cache_info),
+               e->cache_info.signed_descriptor_len);
+        cp += e->cache_info.signed_descriptor_len - 1;
+      }
+      if (cp[0] == '\n') {
+        cp[0] = '\0';
+      } else if (cp[0] != '\0') {
+        cp[1] = '\0';
+      }
+      *answer = descs;
+      routerinfo_free(r);
+      extrainfo_free(e);
+    } else {
+      return 0;
+    }
+  }
+  return 0;
+}
+
+/** Implementation helper for GETINFO: knows how to enumerate hidden services
+ * created via the control port. */
+STATIC int
+getinfo_helper_onions(control_connection_t *control_conn,
+                      const char *question, char **answer,
+                      const char **errmsg)
+{
+  smartlist_t *onion_list = NULL;
+  (void) errmsg;  /* no errors from this method */
+
+  if (control_conn && !strcmp(question, "onions/current")) {
+    onion_list = control_conn->ephemeral_onion_services;
+  } else if (!strcmp(question, "onions/detached")) {
+    onion_list = get_detached_onion_services();
+  } else {
+    return 0;
+  }
+  if (!onion_list || smartlist_len(onion_list) == 0) {
+    if (answer) {
+      *answer = tor_strdup("");
+    }
+  } else {
+    if (answer) {
+      *answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL);
+    }
+  }
+
+  return 0;
+}
+
+/** Implementation helper for GETINFO: answers queries about network
+ * liveness. */
+static int
+getinfo_helper_liveness(control_connection_t *control_conn,
+                      const char *question, char **answer,
+                      const char **errmsg)
+{
+  (void)control_conn;
+  (void)errmsg;
+  if (strcmp(question, "network-liveness") == 0) {
+    if (get_cached_network_liveness()) {
+      *answer = tor_strdup("up");
+    } else {
+      *answer = tor_strdup("down");
+    }
+  }
+
+  return 0;
+}
+
+/** Implementation helper for GETINFO: answers queries about shared random
+ * value. */
+static int
+getinfo_helper_sr(control_connection_t *control_conn,
+                  const char *question, char **answer,
+                  const char **errmsg)
+{
+  (void) control_conn;
+  (void) errmsg;
+
+  if (!strcmp(question, "sr/current")) {
+    *answer = sr_get_current_for_control();
+  } else if (!strcmp(question, "sr/previous")) {
+    *answer = sr_get_previous_for_control();
+  }
+  /* Else statement here is unrecognized key so do nothing. */
+
+  return 0;
+}
+
+/** Callback function for GETINFO: on a given control connection, try to
+ * answer the question <b>q</b> and store the newly-allocated answer in
+ * *<b>a</b>. If an internal error occurs, return -1 and optionally set
+ * *<b>error_out</b> to point to an error message to be delivered to the
+ * controller. On success, _or if the key is not recognized_, return 0. Do not
+ * set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
+ * to improve the error message.
+ */
+typedef int (*getinfo_helper_t)(control_connection_t *,
+                                const char *q, char **a,
+                                const char **error_out);
+
+/** A single item for the GETINFO question-to-answer-function table. */
+typedef struct getinfo_item_t {
+  const char *varname; /**< The value (or prefix) of the question. */
+  getinfo_helper_t fn; /**< The function that knows the answer: NULL if
+                        * this entry is documentation-only. */
+  const char *desc; /**< Description of the variable. */
+  int is_prefix; /** Must varname match exactly, or must it be a prefix? */
+} getinfo_item_t;
+
+#define ITEM(name, fn, desc) { name, getinfo_helper_##fn, desc, 0 }
+#define PREFIX(name, fn, desc) { name, getinfo_helper_##fn, desc, 1 }
+#define DOC(name, desc) { name, NULL, desc, 0 }
+
+/** Table mapping questions accepted by GETINFO to the functions that know how
+ * to answer them. */
+static const getinfo_item_t getinfo_items[] = {
+  ITEM("version", misc, "The current version of Tor."),
+  ITEM("bw-event-cache", misc, "Cached BW events for a short interval."),
+  ITEM("config-file", misc, "Current location of the \"torrc\" file."),
+  ITEM("config-defaults-file", misc, "Current location of the defaults file."),
+  ITEM("config-text", misc,
+       "Return the string that would be written by a saveconf command."),
+  ITEM("config-can-saveconf", misc,
+       "Is it possible to save the configuration to the \"torrc\" file?"),
+  ITEM("accounting/bytes", accounting,
+       "Number of bytes read/written so far in the accounting interval."),
+  ITEM("accounting/bytes-left", accounting,
+      "Number of bytes left to write/read so far in the accounting interval."),
+  ITEM("accounting/enabled", accounting, "Is accounting currently enabled?"),
+  ITEM("accounting/hibernating", accounting, "Are we hibernating or awake?"),
+  ITEM("accounting/interval-start", accounting,
+       "Time when the accounting period starts."),
+  ITEM("accounting/interval-end", accounting,
+       "Time when the accounting period ends."),
+  ITEM("accounting/interval-wake", accounting,
+       "Time to wake up in this accounting period."),
+  ITEM("helper-nodes", entry_guards, NULL), /* deprecated */
+  ITEM("entry-guards", entry_guards,
+       "Which nodes are we using as entry guards?"),
+  ITEM("fingerprint", misc, NULL),
+  PREFIX("config/", config, "Current configuration values."),
+  DOC("config/names",
+      "List of configuration options, types, and documentation."),
+  DOC("config/defaults",
+      "List of default values for configuration options. "
+      "See also config/names"),
+  PREFIX("current-time/", current_time, "Current time."),
+  DOC("current-time/local", "Current time on the local system."),
+  DOC("current-time/utc", "Current UTC time."),
+  PREFIX("downloads/networkstatus/", downloads,
+         "Download statuses for networkstatus objects"),
+  DOC("downloads/networkstatus/ns",
+      "Download status for current-mode networkstatus download"),
+  DOC("downloads/networkstatus/ns/bootstrap",
+      "Download status for bootstrap-time networkstatus download"),
+  DOC("downloads/networkstatus/ns/running",
+      "Download status for run-time networkstatus download"),
+  DOC("downloads/networkstatus/microdesc",
+      "Download status for current-mode microdesc download"),
+  DOC("downloads/networkstatus/microdesc/bootstrap",
+      "Download status for bootstrap-time microdesc download"),
+  DOC("downloads/networkstatus/microdesc/running",
+      "Download status for run-time microdesc download"),
+  PREFIX("downloads/cert/", downloads,
+         "Download statuses for certificates, by id fingerprint and "
+         "signing key"),
+  DOC("downloads/cert/fps",
+      "List of authority fingerprints for which any download statuses "
+      "exist"),
+  DOC("downloads/cert/fp/<fp>",
+      "Download status for <fp> with the default signing key; corresponds "
+      "to /fp/ URLs on directory server."),
+  DOC("downloads/cert/fp/<fp>/sks",
+      "List of signing keys for which specific download statuses are "
+      "available for this id fingerprint"),
+  DOC("downloads/cert/fp/<fp>/<sk>",
+      "Download status for <fp> with signing key <sk>; corresponds "
+      "to /fp-sk/ URLs on directory server."),
+  PREFIX("downloads/desc/", downloads,
+         "Download statuses for router descriptors, by descriptor digest"),
+  DOC("downloads/desc/descs",
+      "Return a list of known router descriptor digests"),
+  DOC("downloads/desc/<desc>",
+      "Return a download status for a given descriptor digest"),
+  PREFIX("downloads/bridge/", downloads,
+         "Download statuses for bridge descriptors, by bridge identity "
+         "digest"),
+  DOC("downloads/bridge/bridges",
+      "Return a list of configured bridge identity digests with download "
+      "statuses"),
+  DOC("downloads/bridge/<desc>",
+      "Return a download status for a given bridge identity digest"),
+  ITEM("info/names", misc,
+       "List of GETINFO options, types, and documentation."),
+  ITEM("events/names", misc,
+       "Events that the controller can ask for with SETEVENTS."),
+  ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"),
+  ITEM("features/names", misc, "What arguments can USEFEATURE take?"),
+  PREFIX("desc/id/", dir, "Router descriptors by ID."),
+  PREFIX("desc/name/", dir, "Router descriptors by nickname."),
+  ITEM("desc/all-recent", dir,
+       "All non-expired, non-superseded router descriptors."),
+  ITEM("desc/download-enabled", dir,
+       "Do we try to download router descriptors?"),
+  ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
+  ITEM("md/all", dir, "All known microdescriptors."),
+  PREFIX("md/id/", dir, "Microdescriptors by ID"),
+  PREFIX("md/name/", dir, "Microdescriptors by name"),
+  ITEM("md/download-enabled", dir,
+       "Do we try to download microdescriptors?"),
+  PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
+  PREFIX("hs/client/desc/id", dir,
+         "Hidden Service descriptor in client's cache by onion."),
+  PREFIX("hs/service/desc/id/", dir,
+         "Hidden Service descriptor in services's cache by onion."),
+  PREFIX("net/listeners/", listeners, "Bound addresses by type"),
+  ITEM("ns/all", networkstatus,
+       "Brief summary of router status (v2 directory format)"),
+  PREFIX("ns/id/", networkstatus,
+         "Brief summary of router status by ID (v2 directory format)."),
+  PREFIX("ns/name/", networkstatus,
+         "Brief summary of router status by nickname (v2 directory format)."),
+  PREFIX("ns/purpose/", networkstatus,
+         "Brief summary of router status by purpose (v2 directory format)."),
+  PREFIX("consensus/", networkstatus,
+         "Information about and from the ns consensus."),
+  ITEM("network-status", dir,
+       "Brief summary of router status (v1 directory format)"),
+  ITEM("network-liveness", liveness,
+       "Current opinion on whether the network is live"),
+  ITEM("circuit-status", events, "List of current circuits originating here."),
+  ITEM("stream-status", events,"List of current streams."),
+  ITEM("orconn-status", events, "A list of current OR connections."),
+  ITEM("dormant", misc,
+       "Is Tor dormant (not building circuits because it's idle)?"),
+  PREFIX("address-mappings/", events, NULL),
+  DOC("address-mappings/all", "Current address mappings."),
+  DOC("address-mappings/cache", "Current cached DNS replies."),
+  DOC("address-mappings/config",
+      "Current address mappings from configuration."),
+  DOC("address-mappings/control", "Current address mappings from controller."),
+  PREFIX("status/", events, NULL),
+  DOC("status/circuit-established",
+      "Whether we think client functionality is working."),
+  DOC("status/enough-dir-info",
+      "Whether we have enough up-to-date directory information to build "
+      "circuits."),
+  DOC("status/bootstrap-phase",
+      "The last bootstrap phase status event that Tor sent."),
+  DOC("status/clients-seen",
+      "Breakdown of client countries seen by a bridge."),
+  DOC("status/fresh-relay-descs",
+      "A fresh relay/ei descriptor pair for Tor's current state. Not stored."),
+  DOC("status/version/recommended", "List of currently recommended versions."),
+  DOC("status/version/current", "Status of the current version."),
+  ITEM("address", misc, "IP address of this Tor host, if we can guess it."),
+  ITEM("traffic/read", misc,"Bytes read since the process was started."),
+  ITEM("traffic/written", misc,
+       "Bytes written since the process was started."),
+  ITEM("uptime", misc, "Uptime of the Tor daemon in seconds."),
+  ITEM("process/pid", misc, "Process id belonging to the main tor process."),
+  ITEM("process/uid", misc, "User id running the tor process."),
+  ITEM("process/user", misc,
+       "Username under which the tor process is running."),
+  ITEM("process/descriptor-limit", misc, "File descriptor limit."),
+  ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"),
+  PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."),
+  PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."),
+  PREFIX("dir/status/", dir,
+         "v2 networkstatus docs as retrieved from a DirPort."),
+  ITEM("dir/status-vote/current/consensus", dir,
+       "v3 Networkstatus consensus as retrieved from a DirPort."),
+  ITEM("exit-policy/default", policies,
+       "The default value appended to the configured exit policy."),
+  ITEM("exit-policy/reject-private/default", policies,
+       "The default rules appended to the configured exit policy by"
+       " ExitPolicyRejectPrivate."),
+  ITEM("exit-policy/reject-private/relay", policies,
+       "The relay-specific rules appended to the configured exit policy by"
+       " ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."),
+  ITEM("exit-policy/full", policies, "The entire exit policy of onion router"),
+  ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"),
+  ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"),
+  PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"),
+  ITEM("onions/current", onions,
+       "Onion services owned by the current control connection."),
+  ITEM("onions/detached", onions,
+       "Onion services detached from the control connection."),
+  ITEM("sr/current", sr, "Get current shared random value."),
+  ITEM("sr/previous", sr, "Get previous shared random value."),
+  { NULL, NULL, NULL, 0 }
+};
+
+/** Allocate and return a list of recognized GETINFO options. */
+static char *
+list_getinfo_options(void)
+{
+  int i;
+  smartlist_t *lines = smartlist_new();
+  char *ans;
+  for (i = 0; getinfo_items[i].varname; ++i) {
+    if (!getinfo_items[i].desc)
+      continue;
+
+    smartlist_add_asprintf(lines, "%s%s -- %s\n",
+                 getinfo_items[i].varname,
+                 getinfo_items[i].is_prefix ? "*" : "",
+                 getinfo_items[i].desc);
+  }
+  smartlist_sort_strings(lines);
+
+  ans = smartlist_join_strings(lines, "", 0, NULL);
+  SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
+  smartlist_free(lines);
+
+  return ans;
+}
+
+/** Lookup the 'getinfo' entry <b>question</b>, and return
+ * the answer in <b>*answer</b> (or NULL if key not recognized).
+ * Return 0 if success or unrecognized, or -1 if recognized but
+ * internal error. */
+static int
+handle_getinfo_helper(control_connection_t *control_conn,
+                      const char *question, char **answer,
+                      const char **err_out)
+{
+  int i;
+  *answer = NULL; /* unrecognized key by default */
+
+  for (i = 0; getinfo_items[i].varname; ++i) {
+    int match;
+    if (getinfo_items[i].is_prefix)
+      match = !strcmpstart(question, getinfo_items[i].varname);
+    else
+      match = !strcmp(question, getinfo_items[i].varname);
+    if (match) {
+      tor_assert(getinfo_items[i].fn);
+      return getinfo_items[i].fn(control_conn, question, answer, err_out);
+    }
+  }
+
+  return 0; /* unrecognized */
+}
+
+/** Called when we receive a GETINFO command.  Try to fetch all requested
+ * information, and reply with information or error message. */
+int
+handle_control_getinfo(control_connection_t *conn, uint32_t len,
+                       const char *body)
+{
+  smartlist_t *questions = smartlist_new();
+  smartlist_t *answers = smartlist_new();
+  smartlist_t *unrecognized = smartlist_new();
+  char *ans = NULL;
+  int i;
+  (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */
+
+  smartlist_split_string(questions, body, " ",
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  SMARTLIST_FOREACH_BEGIN(questions, const char *, q) {
+    const char *errmsg = NULL;
+
+    if (handle_getinfo_helper(conn, q, &ans, &errmsg) < 0) {
+      if (!errmsg)
+        errmsg = "Internal error";
+      connection_printf_to_buf(conn, "551 %s\r\n", errmsg);
+      goto done;
+    }
+    if (!ans) {
+      if (errmsg) /* use provided error message */
+        smartlist_add_strdup(unrecognized, errmsg);
+      else /* use default error message */
+        smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q);
+    } else {
+      smartlist_add_strdup(answers, q);
+      smartlist_add(answers, ans);
+    }
+  } SMARTLIST_FOREACH_END(q);
+
+  if (smartlist_len(unrecognized)) {
+    /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */
+    for (i=0; i < smartlist_len(unrecognized)-1; ++i)
+      connection_printf_to_buf(conn,
+                               "552-%s\r\n",
+                               (char *)smartlist_get(unrecognized, i));
+
+    connection_printf_to_buf(conn,
+                             "552 %s\r\n",
+                             (char *)smartlist_get(unrecognized, i));
+    goto done;
+  }
+
+  for (i = 0; i < smartlist_len(answers); i += 2) {
+    char *k = smartlist_get(answers, i);
+    char *v = smartlist_get(answers, i+1);
+    if (!strchr(v, '\n') && !strchr(v, '\r')) {
+      connection_printf_to_buf(conn, "250-%s=", k);
+      connection_write_str_to_buf(v, conn);
+      connection_write_str_to_buf("\r\n", conn);
+    } else {
+      char *esc = NULL;
+      size_t esc_len;
+      esc_len = write_escaped_data(v, strlen(v), &esc);
+      connection_printf_to_buf(conn, "250+%s=\r\n", k);
+      connection_buf_add(esc, esc_len, TO_CONN(conn));
+      tor_free(esc);
+    }
+  }
+  connection_write_str_to_buf("250 OK\r\n", conn);
+
+ done:
+  SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp));
+  smartlist_free(answers);
+  SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
+  smartlist_free(questions);
+  SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
+  smartlist_free(unrecognized);
+
+  return 0;
+}
diff --git a/src/feature/control/control_getinfo.h b/src/feature/control/control_getinfo.h
new file mode 100644
index 000000000..d5a2feb3e
--- /dev/null
+++ b/src/feature/control/control_getinfo.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file control.h
+ * \brief Header file for control.c.
+ **/
+
+#ifndef TOR_CONTROL_GETINFO_H
+#define TOR_CONTROL_GETINFO_H
+
+int handle_control_getinfo(control_connection_t *conn, uint32_t len,
+                           const char *body);
+
+#ifdef CONTROL_GETINFO_PRIVATE
+STATIC int getinfo_helper_onions(
+    control_connection_t *control_conn,
+    const char *question,
+    char **answer,
+    const char **errmsg);
+STATIC void getinfo_helper_downloads_networkstatus(
+    const char *flavor,
+    download_status_t **dl_to_emit,
+    const char **errmsg);
+STATIC void getinfo_helper_downloads_cert(
+    const char *fp_sk_req,
+    download_status_t **dl_to_emit,
+    smartlist_t **digest_list,
+    const char **errmsg);
+STATIC void getinfo_helper_downloads_desc(
+    const char *desc_req,
+    download_status_t **dl_to_emit,
+    smartlist_t **digest_list,
+    const char **errmsg);
+STATIC void getinfo_helper_downloads_bridge(
+    const char *bridge_req,
+    download_status_t **dl_to_emit,
+    smartlist_t **digest_list,
+    const char **errmsg);
+STATIC int getinfo_helper_downloads(
+    control_connection_t *control_conn,
+    const char *question, char **answer,
+    const char **errmsg);
+STATIC int getinfo_helper_dir(
+    control_connection_t *control_conn,
+    const char *question, char **answer,
+    const char **errmsg);
+STATIC int getinfo_helper_current_time(
+    control_connection_t *control_conn,
+    const char *question, char **answer,
+    const char **errmsg);
+#endif /* defined(CONTROL_GETINFO_PRIVATE) */
+
+#endif /* !defined(TOR_CONTROL_GETINFO) */
diff --git a/src/test/test_controller.c b/src/test/test_controller.c
index 5b406e159..82922d202 100644
--- a/src/test/test_controller.c
+++ b/src/test/test_controller.c
@@ -2,10 +2,12 @@
 /* See LICENSE for licensing information */
 
 #define CONTROL_PRIVATE
+#define CONTROL_GETINFO_PRIVATE
 #include "core/or/or.h"
 #include "lib/crypt_ops/crypto_ed25519.h"
 #include "feature/client/bridges.h"
 #include "feature/control/control.h"
+#include "feature/control/control_getinfo.h"
 #include "feature/client/entrynodes.h"
 #include "feature/hs/hs_common.h"
 #include "feature/nodelist/networkstatus.h"
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index 4132d42d1..bb8c8970e 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -8,7 +8,7 @@
 
 #define BWAUTH_PRIVATE
 #define CONFIG_PRIVATE
-#define CONTROL_PRIVATE
+#define CONTROL_GETINFO_PRIVATE
 #define DIRCACHE_PRIVATE
 #define DIRCLIENT_PRIVATE
 #define DIRSERV_PRIVATE
@@ -32,7 +32,7 @@
 #include "core/or/versions.h"
 #include "feature/client/bridges.h"
 #include "feature/client/entrynodes.h"
-#include "feature/control/control.h"
+#include "feature/control/control_getinfo.h"
 #include "feature/dirauth/bwauth.h"
 #include "feature/dirauth/dirvote.h"
 #include "feature/dirauth/dsigs_parse.h"





More information about the tor-commits mailing list