From 4754e9058b7521967dd92092e54483cec456bb2f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 25 Mar 2019 12:48:52 -0400 Subject: [PATCH] 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(-) create mode 100644 src/feature/control/control_getinfo.c create mode 100644 src/feature/control/control_getinfo.h diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt index 15f16fdb4e..142fda5806 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 58fcb784fd..a8ec360592 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 d56ff41b0e..94643efde8 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 s to the end of - * conn-\>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 in_len_max characters in start 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 - * ei 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: ri_body 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/" 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//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//" 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 */ - 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/, or we can't parse */ - 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 = - * - 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 = - * - 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 q and store the newly-allocated answer in - * *a. If an internal error occurs, return -1 and optionally set - * *error_out 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 a if the key is not recognized but you may set error_out - * 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/", - "Download status for with the default signing key; corresponds " - "to /fp/ URLs on directory server."), - DOC("downloads/cert/fp//sks", - "List of signing keys for which specific download statuses are " - "available for this id fingerprint"), - DOC("downloads/cert/fp//", - "Download status for with signing key ; 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/", - "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/", - "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 question, and return - * the answer in *answer (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 f9742d555e..810fe67843 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 07e1a9d94a..129776f49f 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 9e79346714..0bdbb9cfd2 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 a6d7919915..c87a224ac5 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 s to the end of + * conn-\>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 * conn-\>outbuf. */ void diff --git a/src/feature/control/control_fmt.h b/src/feature/control/control_fmt.h index e36edd2a46..1b4534cc3b 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 0000000000..67a6e27316 --- /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 +#endif + +#ifndef _WIN32 +#include +#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 + * ei 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: ri_body 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/" 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//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//" 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 */ + 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/, or we can't parse */ + 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 = + * - 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 = + * - 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 q and store the newly-allocated answer in + * *a. If an internal error occurs, return -1 and optionally set + * *error_out 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 a if the key is not recognized but you may set error_out + * 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/", + "Download status for with the default signing key; corresponds " + "to /fp/ URLs on directory server."), + DOC("downloads/cert/fp//sks", + "List of signing keys for which specific download statuses are " + "available for this id fingerprint"), + DOC("downloads/cert/fp//", + "Download status for with signing key ; 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/", + "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/", + "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 question, and return + * the answer in *answer (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 0000000000..d5a2feb3e0 --- /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 5b406e159b..82922d2028 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 4132d42d12..bb8c8970ea 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"