2019-03-25 12:48:52 -04:00
|
|
|
/* 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"
|
2019-03-25 14:03:49 -04:00
|
|
|
#include "feature/control/control_cmd.h"
|
2019-03-25 12:48:52 -04:00
|
|
|
#include "feature/control/control_events.h"
|
|
|
|
#include "feature/control/control_fmt.h"
|
|
|
|
#include "feature/control/control_getinfo.h"
|
2019-04-08 11:34:12 -05:00
|
|
|
#include "feature/control/control_proto.h"
|
2019-03-25 12:48:52 -04:00
|
|
|
#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"
|
2019-04-02 19:00:23 -04:00
|
|
|
#include "feature/control/control_cmd_args_st.h"
|
2019-03-25 12:48:52 -04:00
|
|
|
#include "feature/dircache/cached_dir_st.h"
|
|
|
|
#include "feature/nodelist/extrainfo_st.h"
|
|
|
|
#include "feature/nodelist/microdesc_st.h"
|
|
|
|
#include "feature/nodelist/networkstatus_st.h"
|
|
|
|
#include "feature/nodelist/node_st.h"
|
|
|
|
#include "feature/nodelist/routerinfo_st.h"
|
|
|
|
#include "feature/nodelist/routerlist_st.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <pwd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static char *list_getinfo_options(void);
|
|
|
|
static char *download_status_to_string(const download_status_t *dl);
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: knows the answers for various
|
|
|
|
* trivial-to-implement questions. */
|
|
|
|
static int
|
|
|
|
getinfo_helper_misc(control_connection_t *conn, const char *question,
|
|
|
|
char **answer, const char **errmsg)
|
|
|
|
{
|
|
|
|
(void) conn;
|
|
|
|
if (!strcmp(question, "version")) {
|
|
|
|
*answer = tor_strdup(get_version());
|
|
|
|
} else if (!strcmp(question, "bw-event-cache")) {
|
|
|
|
*answer = get_bw_samples();
|
|
|
|
} else if (!strcmp(question, "config-file")) {
|
|
|
|
const char *a = get_torrc_fname(0);
|
|
|
|
if (a)
|
|
|
|
*answer = tor_strdup(a);
|
|
|
|
} else if (!strcmp(question, "config-defaults-file")) {
|
|
|
|
const char *a = get_torrc_fname(1);
|
|
|
|
if (a)
|
|
|
|
*answer = tor_strdup(a);
|
|
|
|
} else if (!strcmp(question, "config-text")) {
|
|
|
|
*answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
|
|
|
|
} else if (!strcmp(question, "config-can-saveconf")) {
|
|
|
|
*answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1");
|
|
|
|
} else if (!strcmp(question, "info/names")) {
|
|
|
|
*answer = list_getinfo_options();
|
|
|
|
} else if (!strcmp(question, "dormant")) {
|
|
|
|
int dormant = rep_hist_circbuilding_dormant(time(NULL));
|
|
|
|
*answer = tor_strdup(dormant ? "1" : "0");
|
|
|
|
} else if (!strcmp(question, "events/names")) {
|
|
|
|
int i;
|
|
|
|
smartlist_t *event_names = smartlist_new();
|
|
|
|
|
|
|
|
for (i = 0; control_event_table[i].event_name != NULL; ++i) {
|
|
|
|
smartlist_add(event_names, (char *)control_event_table[i].event_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
*answer = smartlist_join_strings(event_names, " ", 0, NULL);
|
|
|
|
|
|
|
|
smartlist_free(event_names);
|
|
|
|
} else if (!strcmp(question, "signal/names")) {
|
|
|
|
smartlist_t *signal_names = smartlist_new();
|
|
|
|
int j;
|
|
|
|
for (j = 0; signal_table[j].signal_name != NULL; ++j) {
|
|
|
|
smartlist_add(signal_names, (char*)signal_table[j].signal_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
*answer = smartlist_join_strings(signal_names, " ", 0, NULL);
|
|
|
|
|
|
|
|
smartlist_free(signal_names);
|
|
|
|
} else if (!strcmp(question, "features/names")) {
|
|
|
|
*answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS");
|
|
|
|
} else if (!strcmp(question, "address")) {
|
|
|
|
uint32_t addr;
|
|
|
|
if (router_pick_published_address(get_options(), &addr, 0) < 0) {
|
|
|
|
*errmsg = "Address unknown";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*answer = tor_dup_ip(addr);
|
|
|
|
} else if (!strcmp(question, "traffic/read")) {
|
|
|
|
tor_asprintf(answer, "%"PRIu64, (get_bytes_read()));
|
|
|
|
} else if (!strcmp(question, "traffic/written")) {
|
|
|
|
tor_asprintf(answer, "%"PRIu64, (get_bytes_written()));
|
|
|
|
} else if (!strcmp(question, "uptime")) {
|
|
|
|
long uptime_secs = get_uptime();
|
|
|
|
tor_asprintf(answer, "%ld", uptime_secs);
|
|
|
|
} else if (!strcmp(question, "process/pid")) {
|
|
|
|
int myPid = -1;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
myPid = _getpid();
|
|
|
|
#else
|
|
|
|
myPid = getpid();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
tor_asprintf(answer, "%d", myPid);
|
|
|
|
} else if (!strcmp(question, "process/uid")) {
|
|
|
|
#ifdef _WIN32
|
|
|
|
*answer = tor_strdup("-1");
|
|
|
|
#else
|
|
|
|
int myUid = geteuid();
|
|
|
|
tor_asprintf(answer, "%d", myUid);
|
|
|
|
#endif /* defined(_WIN32) */
|
|
|
|
} else if (!strcmp(question, "process/user")) {
|
|
|
|
#ifdef _WIN32
|
|
|
|
*answer = tor_strdup("");
|
|
|
|
#else
|
|
|
|
int myUid = geteuid();
|
|
|
|
const struct passwd *myPwEntry = tor_getpwuid(myUid);
|
|
|
|
|
|
|
|
if (myPwEntry) {
|
|
|
|
*answer = tor_strdup(myPwEntry->pw_name);
|
|
|
|
} else {
|
|
|
|
*answer = tor_strdup("");
|
|
|
|
}
|
|
|
|
#endif /* defined(_WIN32) */
|
|
|
|
} else if (!strcmp(question, "process/descriptor-limit")) {
|
|
|
|
int max_fds = get_max_sockets();
|
|
|
|
tor_asprintf(answer, "%d", max_fds);
|
|
|
|
} else if (!strcmp(question, "limits/max-mem-in-queues")) {
|
|
|
|
tor_asprintf(answer, "%"PRIu64,
|
|
|
|
(get_options()->MaxMemInQueues));
|
|
|
|
} else if (!strcmp(question, "fingerprint")) {
|
|
|
|
crypto_pk_t *server_key;
|
|
|
|
if (!server_mode(get_options())) {
|
|
|
|
*errmsg = "Not running in server mode";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
server_key = get_server_identity_key();
|
|
|
|
*answer = tor_malloc(HEX_DIGEST_LEN+1);
|
|
|
|
crypto_pk_get_fingerprint(server_key, *answer, 0);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Awful hack: return a newly allocated string based on a routerinfo and
|
|
|
|
* (possibly) an extrainfo, sticking the read-history and write-history from
|
|
|
|
* <b>ei</b> into the resulting string. The thing you get back won't
|
|
|
|
* necessarily have a valid signature.
|
|
|
|
*
|
|
|
|
* New code should never use this; it's for backward compatibility.
|
|
|
|
*
|
|
|
|
* NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might
|
|
|
|
* not be NUL-terminated. */
|
|
|
|
static char *
|
|
|
|
munge_extrainfo_into_routerinfo(const char *ri_body,
|
|
|
|
const signed_descriptor_t *ri,
|
|
|
|
const signed_descriptor_t *ei)
|
|
|
|
{
|
|
|
|
char *out = NULL, *outp;
|
|
|
|
int i;
|
|
|
|
const char *router_sig;
|
|
|
|
const char *ei_body = signed_descriptor_get_body(ei);
|
|
|
|
size_t ri_len = ri->signed_descriptor_len;
|
|
|
|
size_t ei_len = ei->signed_descriptor_len;
|
|
|
|
if (!ei_body)
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
outp = out = tor_malloc(ri_len+ei_len+1);
|
|
|
|
if (!(router_sig = tor_memstr(ri_body, ri_len, "\nrouter-signature")))
|
|
|
|
goto bail;
|
|
|
|
++router_sig;
|
|
|
|
memcpy(out, ri_body, router_sig-ri_body);
|
|
|
|
outp += router_sig-ri_body;
|
|
|
|
|
|
|
|
for (i=0; i < 2; ++i) {
|
|
|
|
const char *kwd = i ? "\nwrite-history " : "\nread-history ";
|
|
|
|
const char *cp, *eol;
|
|
|
|
if (!(cp = tor_memstr(ei_body, ei_len, kwd)))
|
|
|
|
continue;
|
|
|
|
++cp;
|
|
|
|
if (!(eol = memchr(cp, '\n', ei_len - (cp-ei_body))))
|
|
|
|
continue;
|
|
|
|
memcpy(outp, cp, eol-cp+1);
|
|
|
|
outp += eol-cp+1;
|
|
|
|
}
|
|
|
|
memcpy(outp, router_sig, ri_len - (router_sig-ri_body));
|
|
|
|
*outp++ = '\0';
|
|
|
|
tor_assert(outp-out < (int)(ri_len+ei_len+1));
|
|
|
|
|
|
|
|
return out;
|
|
|
|
bail:
|
|
|
|
tor_free(out);
|
|
|
|
return tor_strndup(ri_body, ri->signed_descriptor_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: answers requests for information about
|
|
|
|
* which ports are bound. */
|
|
|
|
static int
|
|
|
|
getinfo_helper_listeners(control_connection_t *control_conn,
|
|
|
|
const char *question,
|
|
|
|
char **answer, const char **errmsg)
|
|
|
|
{
|
|
|
|
int type;
|
|
|
|
smartlist_t *res;
|
|
|
|
|
|
|
|
(void)control_conn;
|
|
|
|
(void)errmsg;
|
|
|
|
|
|
|
|
if (!strcmp(question, "net/listeners/or"))
|
|
|
|
type = CONN_TYPE_OR_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/extor"))
|
|
|
|
type = CONN_TYPE_EXT_OR_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/dir"))
|
|
|
|
type = CONN_TYPE_DIR_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/socks"))
|
|
|
|
type = CONN_TYPE_AP_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/trans"))
|
|
|
|
type = CONN_TYPE_AP_TRANS_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/natd"))
|
|
|
|
type = CONN_TYPE_AP_NATD_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/httptunnel"))
|
|
|
|
type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/dns"))
|
|
|
|
type = CONN_TYPE_AP_DNS_LISTENER;
|
|
|
|
else if (!strcmp(question, "net/listeners/control"))
|
|
|
|
type = CONN_TYPE_CONTROL_LISTENER;
|
|
|
|
else
|
|
|
|
return 0; /* unknown key */
|
|
|
|
|
|
|
|
res = smartlist_new();
|
|
|
|
SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) {
|
|
|
|
struct sockaddr_storage ss;
|
|
|
|
socklen_t ss_len = sizeof(ss);
|
|
|
|
|
|
|
|
if (conn->type != type || conn->marked_for_close || !SOCKET_OK(conn->s))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (getsockname(conn->s, (struct sockaddr *)&ss, &ss_len) < 0) {
|
|
|
|
smartlist_add_asprintf(res, "%s:%d", conn->address, (int)conn->port);
|
|
|
|
} else {
|
|
|
|
char *tmp = tor_sockaddr_to_str((struct sockaddr *)&ss);
|
|
|
|
smartlist_add(res, esc_for_log(tmp));
|
|
|
|
tor_free(tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
} SMARTLIST_FOREACH_END(conn);
|
|
|
|
|
|
|
|
*answer = smartlist_join_strings(res, " ", 0, NULL);
|
|
|
|
|
|
|
|
SMARTLIST_FOREACH(res, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(res);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: answers requests for information about
|
|
|
|
* the current time in both local and UTC forms. */
|
|
|
|
STATIC int
|
|
|
|
getinfo_helper_current_time(control_connection_t *control_conn,
|
|
|
|
const char *question,
|
|
|
|
char **answer, const char **errmsg)
|
|
|
|
{
|
|
|
|
(void)control_conn;
|
|
|
|
(void)errmsg;
|
|
|
|
|
|
|
|
struct timeval now;
|
|
|
|
tor_gettimeofday(&now);
|
|
|
|
char timebuf[ISO_TIME_LEN+1];
|
|
|
|
|
|
|
|
if (!strcmp(question, "current-time/local"))
|
|
|
|
format_local_iso_time_nospace(timebuf, (time_t)now.tv_sec);
|
|
|
|
else if (!strcmp(question, "current-time/utc"))
|
|
|
|
format_iso_time_nospace(timebuf, (time_t)now.tv_sec);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*answer = tor_strdup(timebuf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-17 00:11:18 +05:30
|
|
|
/** GETINFO helper for dumping different consensus flavors
|
|
|
|
* returns: 0 on success -1 on error. */
|
|
|
|
STATIC int
|
|
|
|
getinfo_helper_current_consensus(consensus_flavor_t flavor,
|
|
|
|
char** answer,
|
|
|
|
const char** errmsg)
|
|
|
|
{
|
|
|
|
const char *flavor_name = networkstatus_get_flavor_name(flavor);
|
|
|
|
if (!strcmp(flavor_name, "??")) {
|
|
|
|
*errmsg = "Could not open cached consensus. "
|
|
|
|
"Make sure FetchUselessDescriptors is set to 1.";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (we_want_to_fetch_flavor(get_options(), flavor)) {
|
|
|
|
/** Check from the cache */
|
|
|
|
const cached_dir_t *consensus = dirserv_get_consensus(flavor_name);
|
|
|
|
if (consensus) {
|
|
|
|
*answer = tor_strdup(consensus->dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!*answer) { /* try loading it from disk */
|
|
|
|
|
|
|
|
tor_mmap_t *mapped = networkstatus_map_cached_consensus(flavor_name);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-25 12:48:52 -04:00
|
|
|
/** 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("");
|
2019-09-17 00:11:18 +05:30
|
|
|
} else if (!strcmp(question, "dir/status-vote/current/consensus")) {
|
|
|
|
int consensus_result = getinfo_helper_current_consensus(FLAV_NS,
|
|
|
|
answer, errmsg);
|
|
|
|
if (consensus_result == -1) {
|
|
|
|
return -1;
|
2019-03-25 12:48:52 -04:00
|
|
|
}
|
2019-09-17 00:11:18 +05:30
|
|
|
} else if (!strcmp(question,
|
|
|
|
"dir/status-vote/current/consensus-microdesc")) {
|
|
|
|
int consensus_result = getinfo_helper_current_consensus(FLAV_MICRODESC,
|
|
|
|
answer, errmsg);
|
|
|
|
if (consensus_result == -1) {
|
|
|
|
return -1;
|
2019-03-25 12:48:52 -04:00
|
|
|
}
|
|
|
|
} else if (!strcmp(question, "network-status")) { /* v1 */
|
|
|
|
static int network_status_warned = 0;
|
|
|
|
if (!network_status_warned) {
|
|
|
|
log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will "
|
|
|
|
"go away in a future version of Tor.");
|
|
|
|
network_status_warned = 1;
|
|
|
|
}
|
|
|
|
routerlist_t *routerlist = router_get_routerlist();
|
|
|
|
if (!routerlist || !routerlist->routers ||
|
|
|
|
list_server_status_v1(routerlist->routers, answer, 1) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (!strcmpstart(question, "extra-info/digest/")) {
|
|
|
|
question += strlen("extra-info/digest/");
|
|
|
|
if (strlen(question) == HEX_DIGEST_LEN) {
|
|
|
|
char d[DIGEST_LEN];
|
|
|
|
signed_descriptor_t *sd = NULL;
|
|
|
|
if (base16_decode(d, sizeof(d), question, strlen(question))
|
|
|
|
== sizeof(d)) {
|
|
|
|
/* XXXX this test should move into extrainfo_get_by_descriptor_digest,
|
|
|
|
* but I don't want to risk affecting other parts of the code,
|
|
|
|
* especially since the rules for using our own extrainfo (including
|
|
|
|
* when it might be freed) are different from those for using one
|
|
|
|
* we have downloaded. */
|
|
|
|
if (router_extrainfo_digest_is_me(d))
|
|
|
|
sd = &(router_get_my_extrainfo()->cache_info);
|
|
|
|
else
|
|
|
|
sd = extrainfo_get_by_descriptor_digest(d);
|
|
|
|
}
|
|
|
|
if (sd) {
|
|
|
|
const char *body = signed_descriptor_get_body(sd);
|
|
|
|
if (body)
|
|
|
|
*answer = tor_strndup(body, sd->signed_descriptor_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Given a smartlist of 20-byte digests, return a newly allocated string
|
|
|
|
* containing each of those digests in order, formatted in HEX, and terminated
|
|
|
|
* with a newline. */
|
|
|
|
static char *
|
|
|
|
digest_list_to_string(const smartlist_t *sl)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
char *result, *s;
|
|
|
|
|
|
|
|
/* Allow for newlines, and a \0 at the end */
|
|
|
|
len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1;
|
|
|
|
result = tor_malloc_zero(len);
|
|
|
|
|
|
|
|
s = result;
|
|
|
|
SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) {
|
|
|
|
base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN);
|
|
|
|
s[HEX_DIGEST_LEN] = '\n';
|
|
|
|
s += HEX_DIGEST_LEN + 1;
|
|
|
|
} SMARTLIST_FOREACH_END(digest);
|
|
|
|
*s = '\0';
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Turn a download_status_t into a human-readable description in a newly
|
|
|
|
* allocated string. The format is specified in control-spec.txt, under
|
|
|
|
* the documentation for "GETINFO download/..." . */
|
|
|
|
static char *
|
|
|
|
download_status_to_string(const download_status_t *dl)
|
|
|
|
{
|
|
|
|
char *rv = NULL;
|
|
|
|
char tbuf[ISO_TIME_LEN+1];
|
|
|
|
const char *schedule_str, *want_authority_str;
|
|
|
|
const char *increment_on_str, *backoff_str;
|
|
|
|
|
|
|
|
if (dl) {
|
|
|
|
/* Get some substrings of the eventual output ready */
|
|
|
|
format_iso_time(tbuf, download_status_get_next_attempt_at(dl));
|
|
|
|
|
|
|
|
switch (dl->schedule) {
|
|
|
|
case DL_SCHED_GENERIC:
|
|
|
|
schedule_str = "DL_SCHED_GENERIC";
|
|
|
|
break;
|
|
|
|
case DL_SCHED_CONSENSUS:
|
|
|
|
schedule_str = "DL_SCHED_CONSENSUS";
|
|
|
|
break;
|
|
|
|
case DL_SCHED_BRIDGE:
|
|
|
|
schedule_str = "DL_SCHED_BRIDGE";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
schedule_str = "unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dl->want_authority) {
|
|
|
|
case DL_WANT_ANY_DIRSERVER:
|
|
|
|
want_authority_str = "DL_WANT_ANY_DIRSERVER";
|
|
|
|
break;
|
|
|
|
case DL_WANT_AUTHORITY:
|
|
|
|
want_authority_str = "DL_WANT_AUTHORITY";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
want_authority_str = "unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dl->increment_on) {
|
|
|
|
case DL_SCHED_INCREMENT_FAILURE:
|
|
|
|
increment_on_str = "DL_SCHED_INCREMENT_FAILURE";
|
|
|
|
break;
|
|
|
|
case DL_SCHED_INCREMENT_ATTEMPT:
|
|
|
|
increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
increment_on_str = "unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL";
|
|
|
|
|
|
|
|
/* Now assemble them */
|
|
|
|
tor_asprintf(&rv,
|
|
|
|
"next-attempt-at %s\n"
|
|
|
|
"n-download-failures %u\n"
|
|
|
|
"n-download-attempts %u\n"
|
|
|
|
"schedule %s\n"
|
|
|
|
"want-authority %s\n"
|
|
|
|
"increment-on %s\n"
|
|
|
|
"backoff %s\n"
|
|
|
|
"last-backoff-position %u\n"
|
|
|
|
"last-delay-used %d\n",
|
|
|
|
tbuf,
|
|
|
|
dl->n_download_failures,
|
|
|
|
dl->n_download_attempts,
|
|
|
|
schedule_str,
|
|
|
|
want_authority_str,
|
|
|
|
increment_on_str,
|
|
|
|
backoff_str,
|
|
|
|
dl->last_backoff_position,
|
|
|
|
dl->last_delay_used);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle the consensus download cases for getinfo_helper_downloads() */
|
|
|
|
STATIC void
|
|
|
|
getinfo_helper_downloads_networkstatus(const char *flavor,
|
|
|
|
download_status_t **dl_to_emit,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We get the one for the current bootstrapped status by default, or
|
|
|
|
* take an extra /bootstrap or /running suffix
|
|
|
|
*/
|
|
|
|
if (strcmp(flavor, "ns") == 0) {
|
|
|
|
*dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
|
|
|
|
} else if (strcmp(flavor, "ns/bootstrap") == 0) {
|
|
|
|
*dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
|
|
|
|
} else if (strcmp(flavor, "ns/running") == 0 ) {
|
|
|
|
*dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
|
|
|
|
} else if (strcmp(flavor, "microdesc") == 0) {
|
|
|
|
*dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
|
|
|
|
} else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
|
|
|
|
*dl_to_emit =
|
|
|
|
networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
|
|
|
|
} else if (strcmp(flavor, "microdesc/running") == 0) {
|
|
|
|
*dl_to_emit =
|
|
|
|
networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
|
|
|
|
} else {
|
|
|
|
*errmsg = "Unknown flavor";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle the cert download cases for getinfo_helper_downloads() */
|
|
|
|
STATIC void
|
|
|
|
getinfo_helper_downloads_cert(const char *fp_sk_req,
|
|
|
|
download_status_t **dl_to_emit,
|
|
|
|
smartlist_t **digest_list,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
const char *sk_req;
|
|
|
|
char id_digest[DIGEST_LEN];
|
|
|
|
char sk_digest[DIGEST_LEN];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have to handle four cases; fp_sk_req is the request with
|
|
|
|
* a prefix of "downloads/cert/" snipped off.
|
|
|
|
*
|
|
|
|
* Case 1: fp_sk_req = "fps"
|
|
|
|
* - We should emit a digest_list with a list of all the identity
|
|
|
|
* fingerprints that can be queried for certificate download status;
|
|
|
|
* get it by calling list_authority_ids_with_downloads().
|
|
|
|
*
|
|
|
|
* Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp
|
|
|
|
* - We want the default certificate for this identity fingerprint's
|
|
|
|
* download status; this is the download we get from URLs starting
|
|
|
|
* in /fp/ on the directory server. We can get it with
|
|
|
|
* id_only_download_status_for_authority_id().
|
|
|
|
*
|
|
|
|
* Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp
|
|
|
|
* - We want a list of all signing key digests for this identity
|
|
|
|
* fingerprint which can be queried for certificate download status.
|
|
|
|
* Get it with list_sk_digests_for_authority_id().
|
|
|
|
*
|
|
|
|
* Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and
|
|
|
|
* signing key digest sk
|
|
|
|
* - We want the download status for the certificate for this specific
|
|
|
|
* signing key and fingerprint. These correspond to the ones we get
|
|
|
|
* from URLs starting in /fp-sk/ on the directory server. Get it with
|
|
|
|
* list_sk_digests_for_authority_id().
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (strcmp(fp_sk_req, "fps") == 0) {
|
|
|
|
*digest_list = list_authority_ids_with_downloads();
|
|
|
|
if (!(*digest_list)) {
|
|
|
|
*errmsg = "Failed to get list of authority identity digests (!)";
|
|
|
|
}
|
|
|
|
} else if (!strcmpstart(fp_sk_req, "fp/")) {
|
|
|
|
fp_sk_req += strlen("fp/");
|
|
|
|
/* Okay, look for another / to tell the fp from fp-sk cases */
|
|
|
|
sk_req = strchr(fp_sk_req, '/');
|
|
|
|
if (sk_req) {
|
|
|
|
/* okay, split it here and try to parse <fp> */
|
|
|
|
if (base16_decode(id_digest, DIGEST_LEN,
|
|
|
|
fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) {
|
|
|
|
/* Skip past the '/' */
|
|
|
|
++sk_req;
|
|
|
|
if (strcmp(sk_req, "sks") == 0) {
|
|
|
|
/* We're asking for the list of signing key fingerprints */
|
|
|
|
*digest_list = list_sk_digests_for_authority_id(id_digest);
|
|
|
|
if (!(*digest_list)) {
|
|
|
|
*errmsg = "Failed to get list of signing key digests for this "
|
|
|
|
"authority identity digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* We've got a signing key digest */
|
|
|
|
if (base16_decode(sk_digest, DIGEST_LEN,
|
|
|
|
sk_req, strlen(sk_req)) == DIGEST_LEN) {
|
|
|
|
*dl_to_emit =
|
|
|
|
download_status_for_authority_id_and_sk(id_digest, sk_digest);
|
|
|
|
if (!(*dl_to_emit)) {
|
|
|
|
*errmsg = "Failed to get download status for this identity/"
|
|
|
|
"signing key digest pair";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like a signing key digest";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like an identity digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */
|
|
|
|
if (strlen(fp_sk_req) == HEX_DIGEST_LEN) {
|
|
|
|
if (base16_decode(id_digest, DIGEST_LEN,
|
|
|
|
fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) {
|
|
|
|
*dl_to_emit = id_only_download_status_for_authority_id(id_digest);
|
|
|
|
if (!(*dl_to_emit)) {
|
|
|
|
*errmsg = "Failed to get download status for this authority "
|
|
|
|
"identity digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like a digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like a digest";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "Unknown certificate download status query";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle the routerdesc download cases for getinfo_helper_downloads() */
|
|
|
|
STATIC void
|
|
|
|
getinfo_helper_downloads_desc(const char *desc_req,
|
|
|
|
download_status_t **dl_to_emit,
|
|
|
|
smartlist_t **digest_list,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
char desc_digest[DIGEST_LEN];
|
|
|
|
/*
|
|
|
|
* Two cases to handle here:
|
|
|
|
*
|
|
|
|
* Case 1: desc_req = "descs"
|
|
|
|
* - Emit a list of all router descriptor digests, which we get by
|
|
|
|
* calling router_get_descriptor_digests(); this can return NULL
|
|
|
|
* if we have no current ns-flavor consensus.
|
|
|
|
*
|
|
|
|
* Case 2: desc_req = <fp>
|
|
|
|
* - Check on the specified fingerprint and emit its download_status_t
|
|
|
|
* using router_get_dl_status_by_descriptor_digest().
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (strcmp(desc_req, "descs") == 0) {
|
|
|
|
*digest_list = router_get_descriptor_digests();
|
|
|
|
if (!(*digest_list)) {
|
|
|
|
*errmsg = "We don't seem to have a networkstatus-flavored consensus";
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Microdescs don't use the download_status_t mechanism, so we don't
|
|
|
|
* answer queries about their downloads here; see microdesc.c.
|
|
|
|
*/
|
|
|
|
} else if (strlen(desc_req) == HEX_DIGEST_LEN) {
|
|
|
|
if (base16_decode(desc_digest, DIGEST_LEN,
|
|
|
|
desc_req, strlen(desc_req)) == DIGEST_LEN) {
|
|
|
|
/* Okay we got a digest-shaped thing; try asking for it */
|
|
|
|
*dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest);
|
|
|
|
if (!(*dl_to_emit)) {
|
|
|
|
*errmsg = "No such descriptor digest found";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like a digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "Unknown router descriptor download status query";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle the bridge download cases for getinfo_helper_downloads() */
|
|
|
|
STATIC void
|
|
|
|
getinfo_helper_downloads_bridge(const char *bridge_req,
|
|
|
|
download_status_t **dl_to_emit,
|
|
|
|
smartlist_t **digest_list,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
char bridge_digest[DIGEST_LEN];
|
|
|
|
/*
|
|
|
|
* Two cases to handle here:
|
|
|
|
*
|
|
|
|
* Case 1: bridge_req = "bridges"
|
|
|
|
* - Emit a list of all bridge identity digests, which we get by
|
|
|
|
* calling list_bridge_identities(); this can return NULL if we are
|
|
|
|
* not using bridges.
|
|
|
|
*
|
|
|
|
* Case 2: bridge_req = <fp>
|
|
|
|
* - Check on the specified fingerprint and emit its download_status_t
|
|
|
|
* using get_bridge_dl_status_by_id().
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (strcmp(bridge_req, "bridges") == 0) {
|
|
|
|
*digest_list = list_bridge_identities();
|
|
|
|
if (!(*digest_list)) {
|
|
|
|
*errmsg = "We don't seem to be using bridges";
|
|
|
|
}
|
|
|
|
} else if (strlen(bridge_req) == HEX_DIGEST_LEN) {
|
|
|
|
if (base16_decode(bridge_digest, DIGEST_LEN,
|
|
|
|
bridge_req, strlen(bridge_req)) == DIGEST_LEN) {
|
|
|
|
/* Okay we got a digest-shaped thing; try asking for it */
|
|
|
|
*dl_to_emit = get_bridge_dl_status_by_id(bridge_digest);
|
|
|
|
if (!(*dl_to_emit)) {
|
|
|
|
*errmsg = "No such bridge identity digest found";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "That didn't look like a digest";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*errmsg = "Unknown bridge descriptor download status query";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: knows the answers for questions about
|
|
|
|
* download status information. */
|
|
|
|
STATIC int
|
|
|
|
getinfo_helper_downloads(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
download_status_t *dl_to_emit = NULL;
|
|
|
|
smartlist_t *digest_list = NULL;
|
|
|
|
|
|
|
|
/* Assert args are sane */
|
|
|
|
tor_assert(control_conn != NULL);
|
|
|
|
tor_assert(question != NULL);
|
|
|
|
tor_assert(answer != NULL);
|
|
|
|
tor_assert(errmsg != NULL);
|
|
|
|
|
|
|
|
/* We check for this later to see if we should supply a default */
|
|
|
|
*errmsg = NULL;
|
|
|
|
|
|
|
|
/* Are we after networkstatus downloads? */
|
|
|
|
if (!strcmpstart(question, "downloads/networkstatus/")) {
|
|
|
|
getinfo_helper_downloads_networkstatus(
|
|
|
|
question + strlen("downloads/networkstatus/"),
|
|
|
|
&dl_to_emit, errmsg);
|
|
|
|
/* Certificates? */
|
|
|
|
} else if (!strcmpstart(question, "downloads/cert/")) {
|
|
|
|
getinfo_helper_downloads_cert(
|
|
|
|
question + strlen("downloads/cert/"),
|
|
|
|
&dl_to_emit, &digest_list, errmsg);
|
|
|
|
/* Router descriptors? */
|
|
|
|
} else if (!strcmpstart(question, "downloads/desc/")) {
|
|
|
|
getinfo_helper_downloads_desc(
|
|
|
|
question + strlen("downloads/desc/"),
|
|
|
|
&dl_to_emit, &digest_list, errmsg);
|
|
|
|
/* Bridge descriptors? */
|
|
|
|
} else if (!strcmpstart(question, "downloads/bridge/")) {
|
|
|
|
getinfo_helper_downloads_bridge(
|
|
|
|
question + strlen("downloads/bridge/"),
|
|
|
|
&dl_to_emit, &digest_list, errmsg);
|
|
|
|
} else {
|
|
|
|
*errmsg = "Unknown download status query";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dl_to_emit) {
|
|
|
|
*answer = download_status_to_string(dl_to_emit);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else if (digest_list) {
|
|
|
|
*answer = digest_list_to_string(digest_list);
|
|
|
|
SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s));
|
|
|
|
smartlist_free(digest_list);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (!(*errmsg)) {
|
|
|
|
*errmsg = "Unknown error";
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: knows how to generate summaries of the
|
|
|
|
* current states of things we send events about. */
|
|
|
|
static int
|
|
|
|
getinfo_helper_events(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
const or_options_t *options = get_options();
|
|
|
|
(void) control_conn;
|
|
|
|
if (!strcmp(question, "circuit-status")) {
|
|
|
|
smartlist_t *status = smartlist_new();
|
|
|
|
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ_) {
|
|
|
|
origin_circuit_t *circ;
|
|
|
|
char *circdesc;
|
|
|
|
const char *state;
|
|
|
|
if (! CIRCUIT_IS_ORIGIN(circ_) || circ_->marked_for_close)
|
|
|
|
continue;
|
|
|
|
circ = TO_ORIGIN_CIRCUIT(circ_);
|
|
|
|
|
|
|
|
if (circ->base_.state == CIRCUIT_STATE_OPEN)
|
|
|
|
state = "BUILT";
|
|
|
|
else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT)
|
|
|
|
state = "GUARD_WAIT";
|
|
|
|
else if (circ->cpath)
|
|
|
|
state = "EXTENDED";
|
|
|
|
else
|
|
|
|
state = "LAUNCHED";
|
|
|
|
|
|
|
|
circdesc = circuit_describe_status_for_controller(circ);
|
|
|
|
|
|
|
|
smartlist_add_asprintf(status, "%lu %s%s%s",
|
|
|
|
(unsigned long)circ->global_identifier,
|
|
|
|
state, *circdesc ? " " : "", circdesc);
|
|
|
|
tor_free(circdesc);
|
|
|
|
}
|
|
|
|
SMARTLIST_FOREACH_END(circ_);
|
|
|
|
*answer = smartlist_join_strings(status, "\r\n", 0, NULL);
|
|
|
|
SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(status);
|
|
|
|
} else if (!strcmp(question, "stream-status")) {
|
|
|
|
smartlist_t *conns = get_connection_array();
|
|
|
|
smartlist_t *status = smartlist_new();
|
|
|
|
char buf[256];
|
|
|
|
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
|
|
|
|
const char *state;
|
|
|
|
entry_connection_t *conn;
|
|
|
|
circuit_t *circ;
|
|
|
|
origin_circuit_t *origin_circ = NULL;
|
|
|
|
if (base_conn->type != CONN_TYPE_AP ||
|
|
|
|
base_conn->marked_for_close ||
|
|
|
|
base_conn->state == AP_CONN_STATE_SOCKS_WAIT ||
|
|
|
|
base_conn->state == AP_CONN_STATE_NATD_WAIT)
|
|
|
|
continue;
|
|
|
|
conn = TO_ENTRY_CONN(base_conn);
|
|
|
|
switch (base_conn->state)
|
|
|
|
{
|
|
|
|
case AP_CONN_STATE_CONTROLLER_WAIT:
|
|
|
|
case AP_CONN_STATE_CIRCUIT_WAIT:
|
|
|
|
if (conn->socks_request &&
|
|
|
|
SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command))
|
|
|
|
state = "NEWRESOLVE";
|
|
|
|
else
|
|
|
|
state = "NEW";
|
|
|
|
break;
|
|
|
|
case AP_CONN_STATE_RENDDESC_WAIT:
|
|
|
|
case AP_CONN_STATE_CONNECT_WAIT:
|
|
|
|
state = "SENTCONNECT"; break;
|
|
|
|
case AP_CONN_STATE_RESOLVE_WAIT:
|
|
|
|
state = "SENTRESOLVE"; break;
|
|
|
|
case AP_CONN_STATE_OPEN:
|
|
|
|
state = "SUCCEEDED"; break;
|
|
|
|
default:
|
|
|
|
log_warn(LD_BUG, "Asked for stream in unknown state %d",
|
|
|
|
base_conn->state);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn));
|
|
|
|
if (circ && CIRCUIT_IS_ORIGIN(circ))
|
|
|
|
origin_circ = TO_ORIGIN_CIRCUIT(circ);
|
|
|
|
write_stream_target_to_buf(conn, buf, sizeof(buf));
|
|
|
|
smartlist_add_asprintf(status, "%lu %s %lu %s",
|
|
|
|
(unsigned long) base_conn->global_identifier,state,
|
|
|
|
origin_circ?
|
|
|
|
(unsigned long)origin_circ->global_identifier : 0ul,
|
|
|
|
buf);
|
|
|
|
} SMARTLIST_FOREACH_END(base_conn);
|
|
|
|
*answer = smartlist_join_strings(status, "\r\n", 0, NULL);
|
|
|
|
SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(status);
|
|
|
|
} else if (!strcmp(question, "orconn-status")) {
|
|
|
|
smartlist_t *conns = get_connection_array();
|
|
|
|
smartlist_t *status = smartlist_new();
|
|
|
|
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
|
|
|
|
const char *state;
|
|
|
|
char name[128];
|
|
|
|
or_connection_t *conn;
|
|
|
|
if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close)
|
|
|
|
continue;
|
|
|
|
conn = TO_OR_CONN(base_conn);
|
|
|
|
if (conn->base_.state == OR_CONN_STATE_OPEN)
|
|
|
|
state = "CONNECTED";
|
|
|
|
else if (conn->nickname)
|
|
|
|
state = "LAUNCHED";
|
|
|
|
else
|
|
|
|
state = "NEW";
|
|
|
|
orconn_target_get_name(name, sizeof(name), conn);
|
|
|
|
smartlist_add_asprintf(status, "%s %s", name, state);
|
|
|
|
} SMARTLIST_FOREACH_END(base_conn);
|
|
|
|
*answer = smartlist_join_strings(status, "\r\n", 0, NULL);
|
|
|
|
SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(status);
|
|
|
|
} else if (!strcmpstart(question, "address-mappings/")) {
|
|
|
|
time_t min_e, max_e;
|
|
|
|
smartlist_t *mappings;
|
|
|
|
question += strlen("address-mappings/");
|
|
|
|
if (!strcmp(question, "all")) {
|
|
|
|
min_e = 0; max_e = TIME_MAX;
|
|
|
|
} else if (!strcmp(question, "cache")) {
|
|
|
|
min_e = 2; max_e = TIME_MAX;
|
|
|
|
} else if (!strcmp(question, "config")) {
|
|
|
|
min_e = 0; max_e = 0;
|
|
|
|
} else if (!strcmp(question, "control")) {
|
|
|
|
min_e = 1; max_e = 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
mappings = smartlist_new();
|
|
|
|
addressmap_get_mappings(mappings, min_e, max_e, 1);
|
|
|
|
*answer = smartlist_join_strings(mappings, "\r\n", 0, NULL);
|
|
|
|
SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(mappings);
|
|
|
|
} else if (!strcmpstart(question, "status/")) {
|
|
|
|
/* Note that status/ is not a catch-all for events; there's only supposed
|
|
|
|
* to be a status GETINFO if there's a corresponding STATUS event. */
|
|
|
|
if (!strcmp(question, "status/circuit-established")) {
|
|
|
|
*answer = tor_strdup(have_completed_a_circuit() ? "1" : "0");
|
|
|
|
} else if (!strcmp(question, "status/enough-dir-info")) {
|
|
|
|
*answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0");
|
|
|
|
} else if (!strcmp(question, "status/good-server-descriptor") ||
|
|
|
|
!strcmp(question, "status/accepted-server-descriptor")) {
|
|
|
|
/* They're equivalent for now, until we can figure out how to make
|
|
|
|
* good-server-descriptor be what we want. See comment in
|
|
|
|
* control-spec.txt. */
|
|
|
|
*answer = tor_strdup(directories_have_accepted_server_descriptor()
|
|
|
|
? "1" : "0");
|
|
|
|
} else if (!strcmp(question, "status/reachability-succeeded/or")) {
|
|
|
|
*answer = tor_strdup(check_whether_orport_reachable(options) ?
|
|
|
|
"1" : "0");
|
|
|
|
} else if (!strcmp(question, "status/reachability-succeeded/dir")) {
|
|
|
|
*answer = tor_strdup(check_whether_dirport_reachable(options) ?
|
|
|
|
"1" : "0");
|
|
|
|
} else if (!strcmp(question, "status/reachability-succeeded")) {
|
|
|
|
tor_asprintf(answer, "OR=%d DIR=%d",
|
|
|
|
check_whether_orport_reachable(options) ? 1 : 0,
|
|
|
|
check_whether_dirport_reachable(options) ? 1 : 0);
|
|
|
|
} else if (!strcmp(question, "status/bootstrap-phase")) {
|
|
|
|
*answer = control_event_boot_last_msg();
|
|
|
|
} else if (!strcmpstart(question, "status/version/")) {
|
|
|
|
int is_server = server_mode(options);
|
|
|
|
networkstatus_t *c = networkstatus_get_latest_consensus();
|
|
|
|
version_status_t status;
|
|
|
|
const char *recommended;
|
|
|
|
if (c) {
|
|
|
|
recommended = is_server ? c->server_versions : c->client_versions;
|
|
|
|
status = tor_version_is_obsolete(VERSION, recommended);
|
|
|
|
} else {
|
|
|
|
recommended = "?";
|
|
|
|
status = VS_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(question, "status/version/recommended")) {
|
|
|
|
*answer = tor_strdup(recommended);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(question, "status/version/current")) {
|
|
|
|
switch (status)
|
|
|
|
{
|
|
|
|
case VS_RECOMMENDED: *answer = tor_strdup("recommended"); break;
|
|
|
|
case VS_OLD: *answer = tor_strdup("obsolete"); break;
|
|
|
|
case VS_NEW: *answer = tor_strdup("new"); break;
|
|
|
|
case VS_NEW_IN_SERIES: *answer = tor_strdup("new in series"); break;
|
|
|
|
case VS_UNRECOMMENDED: *answer = tor_strdup("unrecommended"); break;
|
|
|
|
case VS_EMPTY: *answer = tor_strdup("none recommended"); break;
|
|
|
|
case VS_UNKNOWN: *answer = tor_strdup("unknown"); break;
|
|
|
|
default: tor_fragile_assert();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!strcmp(question, "status/clients-seen")) {
|
|
|
|
char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL));
|
|
|
|
if (!bridge_stats) {
|
|
|
|
*errmsg = "No bridge-client stats available";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*answer = bridge_stats;
|
|
|
|
} else if (!strcmp(question, "status/fresh-relay-descs")) {
|
|
|
|
if (!server_mode(options)) {
|
|
|
|
*errmsg = "Only relays have descriptors";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
routerinfo_t *r;
|
|
|
|
extrainfo_t *e;
|
|
|
|
if (router_build_fresh_descriptor(&r, &e) < 0) {
|
|
|
|
*errmsg = "Error generating descriptor";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
size_t size = r->cache_info.signed_descriptor_len + 1;
|
|
|
|
if (e) {
|
|
|
|
size += e->cache_info.signed_descriptor_len + 1;
|
|
|
|
}
|
|
|
|
tor_assert(r->cache_info.signed_descriptor_len);
|
|
|
|
char *descs = tor_malloc(size);
|
|
|
|
char *cp = descs;
|
|
|
|
memcpy(cp, signed_descriptor_get_body(&r->cache_info),
|
|
|
|
r->cache_info.signed_descriptor_len);
|
|
|
|
cp += r->cache_info.signed_descriptor_len - 1;
|
|
|
|
if (e) {
|
|
|
|
if (cp[0] == '\0') {
|
|
|
|
cp[0] = '\n';
|
|
|
|
} else if (cp[0] != '\n') {
|
|
|
|
cp[1] = '\n';
|
|
|
|
cp++;
|
|
|
|
}
|
|
|
|
memcpy(cp, signed_descriptor_get_body(&e->cache_info),
|
|
|
|
e->cache_info.signed_descriptor_len);
|
|
|
|
cp += e->cache_info.signed_descriptor_len - 1;
|
|
|
|
}
|
|
|
|
if (cp[0] == '\n') {
|
|
|
|
cp[0] = '\0';
|
|
|
|
} else if (cp[0] != '\0') {
|
|
|
|
cp[1] = '\0';
|
|
|
|
}
|
|
|
|
*answer = descs;
|
|
|
|
routerinfo_free(r);
|
|
|
|
extrainfo_free(e);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: knows how to enumerate hidden services
|
|
|
|
* created via the control port. */
|
|
|
|
STATIC int
|
|
|
|
getinfo_helper_onions(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
smartlist_t *onion_list = NULL;
|
|
|
|
(void) errmsg; /* no errors from this method */
|
|
|
|
|
|
|
|
if (control_conn && !strcmp(question, "onions/current")) {
|
|
|
|
onion_list = control_conn->ephemeral_onion_services;
|
|
|
|
} else if (!strcmp(question, "onions/detached")) {
|
|
|
|
onion_list = get_detached_onion_services();
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!onion_list || smartlist_len(onion_list) == 0) {
|
|
|
|
if (answer) {
|
|
|
|
*answer = tor_strdup("");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (answer) {
|
|
|
|
*answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: answers queries about network
|
|
|
|
* liveness. */
|
|
|
|
static int
|
|
|
|
getinfo_helper_liveness(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
(void)control_conn;
|
|
|
|
(void)errmsg;
|
|
|
|
if (strcmp(question, "network-liveness") == 0) {
|
|
|
|
if (get_cached_network_liveness()) {
|
|
|
|
*answer = tor_strdup("up");
|
|
|
|
} else {
|
|
|
|
*answer = tor_strdup("down");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Implementation helper for GETINFO: answers queries about shared random
|
|
|
|
* value. */
|
|
|
|
static int
|
|
|
|
getinfo_helper_sr(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **errmsg)
|
|
|
|
{
|
|
|
|
(void) control_conn;
|
|
|
|
(void) errmsg;
|
|
|
|
|
|
|
|
if (!strcmp(question, "sr/current")) {
|
|
|
|
*answer = sr_get_current_for_control();
|
|
|
|
} else if (!strcmp(question, "sr/previous")) {
|
|
|
|
*answer = sr_get_previous_for_control();
|
|
|
|
}
|
|
|
|
/* Else statement here is unrecognized key so do nothing. */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Callback function for GETINFO: on a given control connection, try to
|
|
|
|
* answer the question <b>q</b> and store the newly-allocated answer in
|
|
|
|
* *<b>a</b>. If an internal error occurs, return -1 and optionally set
|
|
|
|
* *<b>error_out</b> to point to an error message to be delivered to the
|
|
|
|
* controller. On success, _or if the key is not recognized_, return 0. Do not
|
|
|
|
* set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
|
|
|
|
* to improve the error message.
|
|
|
|
*/
|
|
|
|
typedef int (*getinfo_helper_t)(control_connection_t *,
|
|
|
|
const char *q, char **a,
|
|
|
|
const char **error_out);
|
|
|
|
|
|
|
|
/** A single item for the GETINFO question-to-answer-function table. */
|
|
|
|
typedef struct getinfo_item_t {
|
|
|
|
const char *varname; /**< The value (or prefix) of the question. */
|
|
|
|
getinfo_helper_t fn; /**< The function that knows the answer: NULL if
|
|
|
|
* this entry is documentation-only. */
|
|
|
|
const char *desc; /**< Description of the variable. */
|
|
|
|
int is_prefix; /** Must varname match exactly, or must it be a prefix? */
|
|
|
|
} getinfo_item_t;
|
|
|
|
|
|
|
|
#define ITEM(name, fn, desc) { name, getinfo_helper_##fn, desc, 0 }
|
|
|
|
#define PREFIX(name, fn, desc) { name, getinfo_helper_##fn, desc, 1 }
|
|
|
|
#define DOC(name, desc) { name, NULL, desc, 0 }
|
|
|
|
|
|
|
|
/** Table mapping questions accepted by GETINFO to the functions that know how
|
|
|
|
* to answer them. */
|
|
|
|
static const getinfo_item_t getinfo_items[] = {
|
|
|
|
ITEM("version", misc, "The current version of Tor."),
|
|
|
|
ITEM("bw-event-cache", misc, "Cached BW events for a short interval."),
|
|
|
|
ITEM("config-file", misc, "Current location of the \"torrc\" file."),
|
|
|
|
ITEM("config-defaults-file", misc, "Current location of the defaults file."),
|
|
|
|
ITEM("config-text", misc,
|
|
|
|
"Return the string that would be written by a saveconf command."),
|
|
|
|
ITEM("config-can-saveconf", misc,
|
|
|
|
"Is it possible to save the configuration to the \"torrc\" file?"),
|
|
|
|
ITEM("accounting/bytes", accounting,
|
|
|
|
"Number of bytes read/written so far in the accounting interval."),
|
|
|
|
ITEM("accounting/bytes-left", accounting,
|
|
|
|
"Number of bytes left to write/read so far in the accounting interval."),
|
|
|
|
ITEM("accounting/enabled", accounting, "Is accounting currently enabled?"),
|
|
|
|
ITEM("accounting/hibernating", accounting, "Are we hibernating or awake?"),
|
|
|
|
ITEM("accounting/interval-start", accounting,
|
|
|
|
"Time when the accounting period starts."),
|
|
|
|
ITEM("accounting/interval-end", accounting,
|
|
|
|
"Time when the accounting period ends."),
|
|
|
|
ITEM("accounting/interval-wake", accounting,
|
|
|
|
"Time to wake up in this accounting period."),
|
|
|
|
ITEM("helper-nodes", entry_guards, NULL), /* deprecated */
|
|
|
|
ITEM("entry-guards", entry_guards,
|
|
|
|
"Which nodes are we using as entry guards?"),
|
|
|
|
ITEM("fingerprint", misc, NULL),
|
|
|
|
PREFIX("config/", config, "Current configuration values."),
|
|
|
|
DOC("config/names",
|
|
|
|
"List of configuration options, types, and documentation."),
|
|
|
|
DOC("config/defaults",
|
|
|
|
"List of default values for configuration options. "
|
|
|
|
"See also config/names"),
|
|
|
|
PREFIX("current-time/", current_time, "Current time."),
|
|
|
|
DOC("current-time/local", "Current time on the local system."),
|
|
|
|
DOC("current-time/utc", "Current UTC time."),
|
|
|
|
PREFIX("downloads/networkstatus/", downloads,
|
|
|
|
"Download statuses for networkstatus objects"),
|
|
|
|
DOC("downloads/networkstatus/ns",
|
|
|
|
"Download status for current-mode networkstatus download"),
|
|
|
|
DOC("downloads/networkstatus/ns/bootstrap",
|
|
|
|
"Download status for bootstrap-time networkstatus download"),
|
|
|
|
DOC("downloads/networkstatus/ns/running",
|
|
|
|
"Download status for run-time networkstatus download"),
|
|
|
|
DOC("downloads/networkstatus/microdesc",
|
|
|
|
"Download status for current-mode microdesc download"),
|
|
|
|
DOC("downloads/networkstatus/microdesc/bootstrap",
|
|
|
|
"Download status for bootstrap-time microdesc download"),
|
|
|
|
DOC("downloads/networkstatus/microdesc/running",
|
|
|
|
"Download status for run-time microdesc download"),
|
|
|
|
PREFIX("downloads/cert/", downloads,
|
|
|
|
"Download statuses for certificates, by id fingerprint and "
|
|
|
|
"signing key"),
|
|
|
|
DOC("downloads/cert/fps",
|
|
|
|
"List of authority fingerprints for which any download statuses "
|
|
|
|
"exist"),
|
|
|
|
DOC("downloads/cert/fp/<fp>",
|
|
|
|
"Download status for <fp> with the default signing key; corresponds "
|
|
|
|
"to /fp/ URLs on directory server."),
|
|
|
|
DOC("downloads/cert/fp/<fp>/sks",
|
|
|
|
"List of signing keys for which specific download statuses are "
|
|
|
|
"available for this id fingerprint"),
|
|
|
|
DOC("downloads/cert/fp/<fp>/<sk>",
|
|
|
|
"Download status for <fp> with signing key <sk>; corresponds "
|
|
|
|
"to /fp-sk/ URLs on directory server."),
|
|
|
|
PREFIX("downloads/desc/", downloads,
|
|
|
|
"Download statuses for router descriptors, by descriptor digest"),
|
|
|
|
DOC("downloads/desc/descs",
|
|
|
|
"Return a list of known router descriptor digests"),
|
|
|
|
DOC("downloads/desc/<desc>",
|
|
|
|
"Return a download status for a given descriptor digest"),
|
|
|
|
PREFIX("downloads/bridge/", downloads,
|
|
|
|
"Download statuses for bridge descriptors, by bridge identity "
|
|
|
|
"digest"),
|
|
|
|
DOC("downloads/bridge/bridges",
|
|
|
|
"Return a list of configured bridge identity digests with download "
|
|
|
|
"statuses"),
|
|
|
|
DOC("downloads/bridge/<desc>",
|
|
|
|
"Return a download status for a given bridge identity digest"),
|
|
|
|
ITEM("info/names", misc,
|
|
|
|
"List of GETINFO options, types, and documentation."),
|
|
|
|
ITEM("events/names", misc,
|
|
|
|
"Events that the controller can ask for with SETEVENTS."),
|
|
|
|
ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"),
|
|
|
|
ITEM("features/names", misc, "What arguments can USEFEATURE take?"),
|
|
|
|
PREFIX("desc/id/", dir, "Router descriptors by ID."),
|
|
|
|
PREFIX("desc/name/", dir, "Router descriptors by nickname."),
|
|
|
|
ITEM("desc/all-recent", dir,
|
|
|
|
"All non-expired, non-superseded router descriptors."),
|
|
|
|
ITEM("desc/download-enabled", dir,
|
|
|
|
"Do we try to download router descriptors?"),
|
|
|
|
ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
|
|
|
|
ITEM("md/all", dir, "All known microdescriptors."),
|
|
|
|
PREFIX("md/id/", dir, "Microdescriptors by ID"),
|
|
|
|
PREFIX("md/name/", dir, "Microdescriptors by name"),
|
|
|
|
ITEM("md/download-enabled", dir,
|
|
|
|
"Do we try to download microdescriptors?"),
|
|
|
|
PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
|
|
|
|
PREFIX("hs/client/desc/id", dir,
|
|
|
|
"Hidden Service descriptor in client's cache by onion."),
|
|
|
|
PREFIX("hs/service/desc/id/", dir,
|
|
|
|
"Hidden Service descriptor in services's cache by onion."),
|
|
|
|
PREFIX("net/listeners/", listeners, "Bound addresses by type"),
|
|
|
|
ITEM("ns/all", networkstatus,
|
|
|
|
"Brief summary of router status (v2 directory format)"),
|
|
|
|
PREFIX("ns/id/", networkstatus,
|
|
|
|
"Brief summary of router status by ID (v2 directory format)."),
|
|
|
|
PREFIX("ns/name/", networkstatus,
|
|
|
|
"Brief summary of router status by nickname (v2 directory format)."),
|
|
|
|
PREFIX("ns/purpose/", networkstatus,
|
|
|
|
"Brief summary of router status by purpose (v2 directory format)."),
|
|
|
|
PREFIX("consensus/", networkstatus,
|
|
|
|
"Information about and from the ns consensus."),
|
|
|
|
ITEM("network-status", dir,
|
|
|
|
"Brief summary of router status (v1 directory format)"),
|
|
|
|
ITEM("network-liveness", liveness,
|
|
|
|
"Current opinion on whether the network is live"),
|
|
|
|
ITEM("circuit-status", events, "List of current circuits originating here."),
|
|
|
|
ITEM("stream-status", events,"List of current streams."),
|
|
|
|
ITEM("orconn-status", events, "A list of current OR connections."),
|
|
|
|
ITEM("dormant", misc,
|
|
|
|
"Is Tor dormant (not building circuits because it's idle)?"),
|
|
|
|
PREFIX("address-mappings/", events, NULL),
|
|
|
|
DOC("address-mappings/all", "Current address mappings."),
|
|
|
|
DOC("address-mappings/cache", "Current cached DNS replies."),
|
|
|
|
DOC("address-mappings/config",
|
|
|
|
"Current address mappings from configuration."),
|
|
|
|
DOC("address-mappings/control", "Current address mappings from controller."),
|
|
|
|
PREFIX("status/", events, NULL),
|
|
|
|
DOC("status/circuit-established",
|
|
|
|
"Whether we think client functionality is working."),
|
|
|
|
DOC("status/enough-dir-info",
|
|
|
|
"Whether we have enough up-to-date directory information to build "
|
|
|
|
"circuits."),
|
|
|
|
DOC("status/bootstrap-phase",
|
|
|
|
"The last bootstrap phase status event that Tor sent."),
|
|
|
|
DOC("status/clients-seen",
|
|
|
|
"Breakdown of client countries seen by a bridge."),
|
|
|
|
DOC("status/fresh-relay-descs",
|
|
|
|
"A fresh relay/ei descriptor pair for Tor's current state. Not stored."),
|
|
|
|
DOC("status/version/recommended", "List of currently recommended versions."),
|
|
|
|
DOC("status/version/current", "Status of the current version."),
|
|
|
|
ITEM("address", misc, "IP address of this Tor host, if we can guess it."),
|
|
|
|
ITEM("traffic/read", misc,"Bytes read since the process was started."),
|
|
|
|
ITEM("traffic/written", misc,
|
|
|
|
"Bytes written since the process was started."),
|
|
|
|
ITEM("uptime", misc, "Uptime of the Tor daemon in seconds."),
|
|
|
|
ITEM("process/pid", misc, "Process id belonging to the main tor process."),
|
|
|
|
ITEM("process/uid", misc, "User id running the tor process."),
|
|
|
|
ITEM("process/user", misc,
|
|
|
|
"Username under which the tor process is running."),
|
|
|
|
ITEM("process/descriptor-limit", misc, "File descriptor limit."),
|
|
|
|
ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"),
|
|
|
|
PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."),
|
|
|
|
PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."),
|
|
|
|
PREFIX("dir/status/", dir,
|
|
|
|
"v2 networkstatus docs as retrieved from a DirPort."),
|
|
|
|
ITEM("dir/status-vote/current/consensus", dir,
|
|
|
|
"v3 Networkstatus consensus as retrieved from a DirPort."),
|
2019-09-17 00:11:18 +05:30
|
|
|
ITEM("dir/status-vote/current/consensus-microdesc", dir,
|
|
|
|
"v3 Microdescriptors consensus as retrieved from a DirPort."),
|
2019-03-25 12:48:52 -04:00
|
|
|
ITEM("exit-policy/default", policies,
|
|
|
|
"The default value appended to the configured exit policy."),
|
|
|
|
ITEM("exit-policy/reject-private/default", policies,
|
|
|
|
"The default rules appended to the configured exit policy by"
|
|
|
|
" ExitPolicyRejectPrivate."),
|
|
|
|
ITEM("exit-policy/reject-private/relay", policies,
|
|
|
|
"The relay-specific rules appended to the configured exit policy by"
|
|
|
|
" ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."),
|
|
|
|
ITEM("exit-policy/full", policies, "The entire exit policy of onion router"),
|
|
|
|
ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"),
|
|
|
|
ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"),
|
|
|
|
PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"),
|
|
|
|
ITEM("onions/current", onions,
|
|
|
|
"Onion services owned by the current control connection."),
|
|
|
|
ITEM("onions/detached", onions,
|
|
|
|
"Onion services detached from the control connection."),
|
|
|
|
ITEM("sr/current", sr, "Get current shared random value."),
|
|
|
|
ITEM("sr/previous", sr, "Get previous shared random value."),
|
|
|
|
{ NULL, NULL, NULL, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Allocate and return a list of recognized GETINFO options. */
|
|
|
|
static char *
|
|
|
|
list_getinfo_options(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
smartlist_t *lines = smartlist_new();
|
|
|
|
char *ans;
|
|
|
|
for (i = 0; getinfo_items[i].varname; ++i) {
|
|
|
|
if (!getinfo_items[i].desc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
smartlist_add_asprintf(lines, "%s%s -- %s\n",
|
|
|
|
getinfo_items[i].varname,
|
|
|
|
getinfo_items[i].is_prefix ? "*" : "",
|
|
|
|
getinfo_items[i].desc);
|
|
|
|
}
|
|
|
|
smartlist_sort_strings(lines);
|
|
|
|
|
|
|
|
ans = smartlist_join_strings(lines, "", 0, NULL);
|
|
|
|
SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(lines);
|
|
|
|
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Lookup the 'getinfo' entry <b>question</b>, and return
|
|
|
|
* the answer in <b>*answer</b> (or NULL if key not recognized).
|
|
|
|
* Return 0 if success or unrecognized, or -1 if recognized but
|
|
|
|
* internal error. */
|
|
|
|
static int
|
|
|
|
handle_getinfo_helper(control_connection_t *control_conn,
|
|
|
|
const char *question, char **answer,
|
|
|
|
const char **err_out)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
*answer = NULL; /* unrecognized key by default */
|
|
|
|
|
|
|
|
for (i = 0; getinfo_items[i].varname; ++i) {
|
|
|
|
int match;
|
|
|
|
if (getinfo_items[i].is_prefix)
|
|
|
|
match = !strcmpstart(question, getinfo_items[i].varname);
|
|
|
|
else
|
|
|
|
match = !strcmp(question, getinfo_items[i].varname);
|
|
|
|
if (match) {
|
|
|
|
tor_assert(getinfo_items[i].fn);
|
|
|
|
return getinfo_items[i].fn(control_conn, question, answer, err_out);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0; /* unrecognized */
|
|
|
|
}
|
|
|
|
|
2019-04-02 19:00:23 -04:00
|
|
|
const control_cmd_syntax_t getinfo_syntax = {
|
|
|
|
.max_args = UINT_MAX,
|
|
|
|
};
|
|
|
|
|
2019-03-25 12:48:52 -04:00
|
|
|
/** Called when we receive a GETINFO command. Try to fetch all requested
|
|
|
|
* information, and reply with information or error message. */
|
|
|
|
int
|
2019-04-02 19:00:23 -04:00
|
|
|
handle_control_getinfo(control_connection_t *conn,
|
|
|
|
const control_cmd_args_t *args)
|
2019-03-25 12:48:52 -04:00
|
|
|
{
|
2019-04-02 19:00:23 -04:00
|
|
|
const smartlist_t *questions = args->args;
|
2019-03-25 12:48:52 -04:00
|
|
|
smartlist_t *answers = smartlist_new();
|
|
|
|
smartlist_t *unrecognized = smartlist_new();
|
|
|
|
char *ans = NULL;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
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";
|
2019-04-10 17:04:09 -05:00
|
|
|
control_write_endreply(conn, 551, errmsg);
|
2019-03-25 12:48:52 -04:00
|
|
|
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)
|
2019-04-10 17:04:09 -05:00
|
|
|
control_write_midreply(conn, 552,
|
2019-03-25 12:48:52 -04:00
|
|
|
(char *)smartlist_get(unrecognized, i));
|
2019-04-10 17:04:09 -05:00
|
|
|
|
|
|
|
control_write_endreply(conn, 552, (char *)smartlist_get(unrecognized, i));
|
2019-03-25 12:48:52 -04:00
|
|
|
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')) {
|
2019-04-10 15:11:36 -05:00
|
|
|
control_printf_midreply(conn, 250, "%s=%s", k, v);
|
2019-03-25 12:48:52 -04:00
|
|
|
} else {
|
2019-04-10 15:11:36 -05:00
|
|
|
control_printf_datareply(conn, 250, "%s=", k);
|
|
|
|
control_write_data(conn, v);
|
2019-03-25 12:48:52 -04:00
|
|
|
}
|
|
|
|
}
|
2019-04-10 17:04:09 -05:00
|
|
|
send_control_done(conn);
|
2019-03-25 12:48:52 -04:00
|
|
|
|
|
|
|
done:
|
|
|
|
SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(answers);
|
|
|
|
SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
|
|
|
|
smartlist_free(unrecognized);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|