[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [obfsproxy/master] Introduce managed.[ch] to the world.
commit d3ea7d5c0461d237ec3c1e437bc6609d0d0a1075
Author: George Kadianakis <desnacked@xxxxxxxxx>
Date: Sat Aug 20 06:35:30 2011 +0200
Introduce managed.[ch] to the world.
---
src/managed.c | 670 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/managed.h | 16 ++
2 files changed, 686 insertions(+), 0 deletions(-)
diff --git a/src/managed.c b/src/managed.c
new file mode 100644
index 0000000..979bc38
--- /dev/null
+++ b/src/managed.c
@@ -0,0 +1,670 @@
+/* Copyright 2011 Nick Mathewson, George Kadianakis
+ See LICENSE for other credits and copying information
+*/
+
+#include "util.h"
+
+#define MANAGED_PRIVATE
+#include "managed.h"
+#include "network.h"
+#include "protocol.h"
+#include "main.h"
+
+#include "container.h"
+
+#include <event2/event.h>
+#include <event2/listener.h>
+
+#include <arpa/inet.h> /* for inet_ntoa() */
+
+/** Holds all the parameters provided by tor through environment
+ variables. */
+typedef struct managed_env_vars_t {
+ char *state_loc; /* where we should store our state */
+ char *conf_proto_version; /* managed proxy configuration protocols tor supports */
+ char *transports; /* pluggable transports tor wants us to launch */
+
+ /* server-only */
+ char *extended_port; /* extended OR port tor wants us to use */
+ char *or_port; /* ORPort that tor uses */
+ char *bindaddrs; /* address to listen for client proxy connections */
+} managed_env_vars_t;
+
+/** Holds data for this managed proxy. */
+typedef struct managed_proxy_t {
+ unsigned int is_server : 1; /* whether we are a server or a client */
+
+ managed_env_vars_t vars; /* environment variables */
+
+ smartlist_t *configs; /* configs created */
+} managed_proxy_t;
+
+/* Holds the status of the environment variables parsing, so that we
+ can report a granular error if something fails. */
+enum env_parsing_status {
+ ST_ENV_SMOOTH,
+ ST_ENV_FAIL_STATE_LOC,
+ ST_ENV_FAIL_CONF_PROTO_VER,
+ ST_ENV_FAIL_CLIENT_TRANSPORTS,
+ ST_ENV_FAIL_EXTENDED_PORT,
+ ST_ENV_FAIL_ORPORT,
+ ST_ENV_FAIL_BINDADDR,
+ ST_ENV_FAIL_SERVER_TRANSPORTS,
+};
+
+/* Holds the status of a listener launch. */
+enum launch_status {
+ ST_LAUNCH_SMOOTH,
+ ST_LAUNCH_FAIL_SETUP,
+ ST_LAUNCH_FAIL_LSN,
+};
+
+/** Managed proxy protocol strings */
+#define PROTO_ENV_ERROR "ENV-ERROR"
+#define PROTO_NEG_SUCCESS "VERSION"
+#define PROTO_NEG_FAIL "VERSION-ERROR no-version\n"
+#define PROTO_CMETHOD "CMETHOD"
+#define PROTO_SMETHOD "SMETHOD"
+#define PROTO_CMETHOD_ERROR "CMETHOD-ERROR"
+#define PROTO_SMETHOD_ERROR "SMETHOD-ERROR"
+#define PROTO_CMETHODS_DONE "CMETHODS DONE\n"
+#define PROTO_SMETHODS_DONE "SMETHODS DONE\n"
+
+static int handle_environment(managed_proxy_t *proxy);
+static int conf_proto_version_negotiation(const managed_proxy_t *proxy);
+static int launch_listeners(const managed_proxy_t *proxy);
+static void print_method_line(const char *transport,
+ const managed_proxy_t *proxy,
+ config_t *cfg);
+static void print_method_error_line(const char *protocol,
+ const managed_proxy_t *proxy,
+ enum launch_status status);
+static const char *get_launch_error_message(enum launch_status status);
+
+static void print_method_done_line(const managed_proxy_t *proxy);
+
+static const char *get_env_parsing_error_message(enum env_parsing_status status);
+
+static void proxy_free(managed_proxy_t *proxy);
+static int is_supported_conf_protocol(const char *name);
+static void print_protocol_line(const char *format, ...);
+
+static int validate_environment(managed_proxy_t *proxy);
+
+const char *supported_conf_protocols[] = { "1" };
+const size_t n_supported_conf_protocols =
+ sizeof(supported_conf_protocols)/sizeof(supported_conf_protocols[0]);
+
+/**
+ This function fires up the managed proxy.
+ It first reads the environment that tor should have prepared, and then
+ launches the appropriate listeners.
+
+ Returns 0 if we managed to launch at least one proxy,
+ returns -1 if something went wrong or we didn't launch any proxies.
+*/
+int
+launch_managed_proxy(void)
+{
+ int r=-1;
+ managed_proxy_t *proxy = xzalloc(sizeof(managed_proxy_t));
+ proxy->configs = smartlist_create();
+
+ obfsproxy_init();
+
+ if (handle_environment(proxy) < 0)
+ goto done;
+
+ if (validate_environment(proxy) < 0)
+ goto done;
+
+ if (conf_proto_version_negotiation(proxy) < 0)
+ goto done;
+
+ if (launch_listeners(proxy) < 0)
+ goto done;
+
+ /* Kickstart libevent */
+ event_base_dispatch(get_event_base());
+
+ r=0;
+
+ done:
+ obfsproxy_cleanup();
+ proxy_free(proxy);
+
+ return r;
+}
+
+/**
+ Make sure that we got *all* the necessary environment variables off tor.
+*/
+static inline void
+assert_proxy_env(managed_proxy_t *proxy)
+{
+ obfs_assert(proxy);
+
+ obfs_assert(proxy->vars.state_loc);
+ obfs_assert(proxy->vars.conf_proto_version);
+ obfs_assert(proxy->vars.transports);
+ if (proxy->is_server) {
+ obfs_assert(proxy->vars.extended_port);
+ obfs_assert(proxy->vars.or_port);
+ obfs_assert(proxy->vars.bindaddrs);
+ } else {
+ obfs_assert(!proxy->vars.extended_port);
+ obfs_assert(!proxy->vars.or_port);
+ obfs_assert(!proxy->vars.bindaddrs);
+ }
+}
+
+/**
+ Validates the environment variables that we got off tor.
+*/
+static int
+validate_environment(managed_proxy_t *proxy)
+{
+ enum env_parsing_status status;
+
+ assert_proxy_env(proxy);
+
+ if (proxy->is_server) {
+ if (validate_bindaddrs(proxy->vars.bindaddrs, proxy->vars.transports) < 0) {
+ status = ST_ENV_FAIL_BINDADDR;
+ goto err;
+ }
+ }
+
+ return 0;
+
+ err:
+ print_protocol_line("%s %s\n", PROTO_ENV_ERROR,
+ get_env_parsing_error_message(status));
+
+ return -1;
+}
+
+/**
+ Free memory allocated by 'proxy'.
+*/
+static void
+proxy_free(managed_proxy_t *proxy)
+{
+ free(proxy->vars.state_loc);
+ free(proxy->vars.conf_proto_version);
+ free(proxy->vars.transports);
+ free(proxy->vars.extended_port);
+ free(proxy->vars.or_port);
+ free(proxy->vars.bindaddrs);
+
+ SMARTLIST_FOREACH(proxy->configs, config_t *, cfg, config_free(cfg));
+ smartlist_free(proxy->configs);
+
+ free(proxy);
+}
+
+/**
+ Given a smartlist of listener configs; launch them all and print
+ method lines as appropriate. Return 0 if at least a listener was
+ spawned, -1 otherwise.
+*/
+static int
+open_listeners_managed(const managed_proxy_t *proxy)
+{
+ int ret=-1;
+ const char *transport;
+
+ /* Open listeners for each configuration. */
+ SMARTLIST_FOREACH_BEGIN(proxy->configs, config_t *, cfg) {
+ transport = get_transport_name_from_config(cfg);
+
+ if (open_listeners(get_event_base(), cfg)) {
+ ret=0; /* success! launched at least one listener. */
+ print_method_line(transport, proxy, cfg);
+ } else { /* fail. print a method error line. */
+ print_method_error_line(transport, proxy, ST_LAUNCH_FAIL_LSN);
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+
+ return ret;
+}
+
+/**
+ Launch all server listeners that tor wants us to launch.
+*/
+static int
+launch_server_listeners(const managed_proxy_t *proxy)
+{
+ int ret=-1;
+ int i, n_transports;
+ const char *transport = NULL;
+ const char *bindaddr_temp = NULL;
+ const char *bindaddr = NULL;
+ config_t *cfg = NULL;
+
+ /* list of transports */
+ smartlist_t *transports = smartlist_create();
+ /* a list of "<transport>-<bindaddr>" strings */
+ smartlist_t *bindaddrs = smartlist_create();
+
+ /* split the comma-separated transports/bindaddrs to their smartlists */
+ smartlist_split_string(transports, proxy->vars.transports, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ smartlist_split_string(bindaddrs, proxy->vars.bindaddrs, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ n_transports = smartlist_len(transports);
+
+ /* Iterate transports; match them with their bindaddr; create their
+ config_t. */
+ for (i=0;i<n_transports;i++) {
+ transport = smartlist_get(transports, i);
+ bindaddr_temp = smartlist_get(bindaddrs, i);
+
+ obfs_assert(strlen(bindaddr_temp) > strlen(transport)+1);
+
+ bindaddr = bindaddr_temp+strlen(transport)+1; /* +1 for the dash */
+
+ cfg = config_create_managed(proxy->is_server,
+ transport, bindaddr, proxy->vars.or_port);
+ if (cfg) /* if a config was created; put it in the config smartlist */
+ smartlist_add(proxy->configs, cfg);
+ else /* otherwise, spit a method error line */
+ print_method_error_line(transport, proxy, ST_LAUNCH_FAIL_SETUP);
+ }
+
+ /* open listeners */
+ ret = open_listeners_managed(proxy);
+
+ SMARTLIST_FOREACH(bindaddrs, char *, cp, free(cp));
+ smartlist_free(bindaddrs);
+ SMARTLIST_FOREACH(transports, char *, cp, free(cp));
+ smartlist_free(transports);
+
+ print_method_done_line(proxy); /* print {C,S}METHODS DONE */
+
+ return ret;
+}
+
+/**
+ Launch all client listeners that tor wants us to launch.
+*/
+static int
+launch_client_listeners(const managed_proxy_t *proxy)
+{
+ int ret=-1;
+ config_t *cfg = NULL;
+
+ /* list of transports */
+ smartlist_t *transports = smartlist_create();
+
+ smartlist_split_string(transports, proxy->vars.transports, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ SMARTLIST_FOREACH_BEGIN(transports, char *, transport) {
+ /* clients should find their own listen port */
+ cfg = config_create_managed(proxy->is_server,
+ transport, "127.0.0.1:0", proxy->vars.or_port);
+ if (cfg) /* if config was created; put it in the config smartlist */
+ smartlist_add(proxy->configs, cfg);
+ else
+ print_method_error_line(transport, proxy, ST_LAUNCH_FAIL_SETUP);
+ } SMARTLIST_FOREACH_END(transport);
+
+ /* open listeners */
+ ret = open_listeners_managed(proxy);
+
+ SMARTLIST_FOREACH(transports, char *, cp, free(cp));
+ smartlist_free(transports);
+
+ print_method_done_line(proxy); /* print {C,S}METHODS DONE */
+
+ return ret;
+}
+
+/**
+ Launch all listeners that tor wants us to launch.
+ Return 0 if at least a listener was launched, -1 otherwise.
+*/
+static inline int
+launch_listeners(const managed_proxy_t *proxy)
+{
+ if (proxy->is_server)
+ return launch_server_listeners(proxy);
+ else
+ return launch_client_listeners(proxy);
+}
+
+/**
+ Given:
+ * comma-separated list of "<transport>-<bindaddr>" strings in 'all_bindaddrs'.
+ * comma-separated list of "<transport>" strings in 'all_transports'.
+
+ Return:
+ * 0, if
+ - all <transport> strings in 'all_bindaddrs' match with <transport>
+ strings in 'all_transports' (order matters).
+ AND
+ - if all <bindaddr> strings in 'all_bindaddrs' are valid addrports.
+ * -1, otherwise.
+*/
+int
+validate_bindaddrs(const char *all_bindaddrs, const char *all_transports)
+{
+ int ret,i,n_bindaddrs,n_transports;
+ struct evutil_addrinfo *bindaddr_test;
+ char *bindaddr = NULL;
+ char *transport = NULL;
+
+ /* a list of "<proto>-<bindaddr>" strings */
+ smartlist_t *bindaddrs = smartlist_create();
+ /* a list holding (<proto>, <bindaddr>) for each 'bindaddrs' entry */
+ smartlist_t *split_bindaddr = NULL;
+ /* a list of "<proto>" strings */
+ smartlist_t *transports = smartlist_create();
+
+ smartlist_split_string(bindaddrs, all_bindaddrs, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ smartlist_split_string(transports, all_transports, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ n_bindaddrs = smartlist_len(bindaddrs);
+ n_transports = smartlist_len(transports);
+
+ if (n_bindaddrs != n_transports)
+ goto err;
+
+ for (i=0;i<n_bindaddrs;i++) {
+ bindaddr = smartlist_get(bindaddrs, i);
+ transport = smartlist_get(transports, i);
+
+ split_bindaddr = smartlist_create();
+ smartlist_split_string(split_bindaddr, bindaddr, "-",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ /* (<proto>, <bindaddr>) */
+ if (smartlist_len(split_bindaddr) != 2)
+ goto err;
+
+ /* "all <transport> strings in 'all_bindaddrs' match with <transport>
+ strings in 'all_transports' (order matters)." */
+ if (strcmp(smartlist_get(split_bindaddr,0), transport))
+ goto err;
+
+ /* "if all <bindaddr> strings in 'all_bindaddrs' are valid addrports." */
+ bindaddr_test = resolve_address_port(smartlist_get(split_bindaddr, 1),
+ 1, 1, NULL);
+ if (!bindaddr_test)
+ goto err;
+
+ evutil_freeaddrinfo(bindaddr_test);
+ SMARTLIST_FOREACH(split_bindaddr, char *, cp, free(cp));
+ smartlist_free(split_bindaddr);
+ split_bindaddr = NULL;
+ }
+
+ ret = 0;
+ goto done;
+
+ err:
+ ret = -1;
+
+ done:
+ SMARTLIST_FOREACH(bindaddrs, char *, cp, free(cp));
+ smartlist_free(bindaddrs);
+ SMARTLIST_FOREACH(transports, char *, cp, free(cp));
+ smartlist_free(transports);
+ if (split_bindaddr) {
+ SMARTLIST_FOREACH(split_bindaddr, char *, cp, free(cp));
+ smartlist_free(split_bindaddr);
+ }
+
+ return ret;
+}
+
+/**
+ Read the environment variables defined in the
+ 180-pluggable-transport.txt spec and fill 'proxy' accordingly.
+
+ Return 0 if all necessary environment variables were set properly.
+ Return -1 otherwise.
+*/
+static int
+handle_environment(managed_proxy_t *proxy)
+{
+ char *tmp;
+ enum env_parsing_status status=ST_ENV_SMOOTH;
+
+ tmp = getenv("TOR_PT_STATE_LOCATION");
+ if (!tmp) {
+ status = ST_ENV_FAIL_STATE_LOC;
+ goto err;
+ }
+ proxy->vars.state_loc = xstrdup(tmp);
+
+ tmp = getenv("TOR_PT_MANAGED_TRANSPORT_VER");
+ if (!tmp) {
+ status = ST_ENV_FAIL_CONF_PROTO_VER;
+ goto err;
+ }
+ proxy->vars.conf_proto_version = xstrdup(tmp);
+
+ tmp = getenv("TOR_PT_CLIENT_TRANSPORTS");
+ if (tmp) {
+ proxy->vars.transports = xstrdup(tmp);
+ proxy->is_server = 0;
+ } else {
+ proxy->is_server = 1;
+ }
+
+ if (proxy->is_server) {
+ tmp = getenv("TOR_PT_EXTENDED_SERVER_PORT");
+ if (!tmp) {
+ status = ST_ENV_FAIL_EXTENDED_PORT;
+ goto err;
+ }
+ proxy->vars.extended_port = xstrdup(tmp);
+
+ tmp = getenv("TOR_PT_ORPORT");
+ if (!tmp) {
+ status = ST_ENV_FAIL_ORPORT;
+ goto err;
+ }
+ proxy->vars.or_port = xstrdup(tmp);
+
+ tmp = getenv("TOR_PT_SERVER_BINDADDR");
+ if (tmp) {
+ proxy->vars.bindaddrs = xstrdup(tmp);
+ } else {
+ status = ST_ENV_FAIL_BINDADDR;
+ goto err;
+ }
+
+ tmp = getenv("TOR_PT_SERVER_TRANSPORTS");
+ if (!tmp) {
+ status = ST_ENV_FAIL_SERVER_TRANSPORTS;
+ goto err;
+ }
+ proxy->vars.transports = xstrdup(tmp);
+ }
+
+ return 0;
+
+ err:
+ print_protocol_line("%s %s\n", PROTO_ENV_ERROR,
+ get_env_parsing_error_message(status));
+
+ return -1;
+}
+
+/**
+ Given the name of a protocol we tried to launch in 'protocol'
+ the managed proxy parameters in 'proxy' and a listener launch
+ status in 'status', print a line of the form:
+ CMETHOD-ERROR <methodname> "<errormessage>"
+ to signify a listener launching failure.
+*/
+static inline void
+print_method_error_line(const char *protocol,
+ const managed_proxy_t *proxy,
+ enum launch_status status)
+{
+ print_protocol_line("%s %s %s\n",
+ proxy->is_server ?
+ PROTO_SMETHOD_ERROR : PROTO_CMETHOD_ERROR,
+ protocol,
+ get_launch_error_message(status));
+}
+
+/**
+ Print a CMETHOD/SMETHOD line saying that we launched 'transport'
+ with 'cfg' under 'proxy'.
+*/
+static void
+print_method_line(const char *transport, const managed_proxy_t *proxy,
+ config_t *cfg)
+{
+ struct evconnlistener *listener = get_evconnlistener_by_config(cfg);
+ obfs_assert(listener);
+
+ struct sockaddr_in saddr;
+ memset(&saddr,0,sizeof(struct sockaddr_in));
+ socklen_t slen = sizeof(saddr);
+
+ obfs_assert(!(getsockname(evconnlistener_get_fd(listener),
+ (struct sockaddr *)&saddr, &slen) < 0));
+
+ if (proxy->is_server) {
+ print_protocol_line("%s %s %s:%hu\n",
+ PROTO_SMETHOD,
+ transport,
+ inet_ntoa(saddr.sin_addr),
+ ntohs(saddr.sin_port));
+ } else {
+ print_protocol_line("%s %s %s %s:%hu\n",
+ PROTO_CMETHOD,
+ transport,
+ "socks5",
+ inet_ntoa(saddr.sin_addr),
+ ntohs(saddr.sin_port));
+
+ }
+}
+
+/**
+ Print the '{S,C}METHOD DONE' message.
+*/
+static inline void
+print_method_done_line(const managed_proxy_t *proxy)
+{
+ print_protocol_line("%s", proxy->is_server ?
+ PROTO_SMETHODS_DONE : PROTO_CMETHODS_DONE);
+}
+
+/**
+ Return true if 'name' matches the name of a supported managed proxy
+ configuration protocol.
+*/
+static inline int
+is_supported_conf_protocol(const char *name) {
+ int f;
+ for (f=0;f<n_supported_conf_protocols;f++) {
+ if (!strcmp(name,supported_conf_protocols[f]))
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ Finish the configuration protocol version negotiation by printing
+ whether we support any of the suggested configuration protocols in
+ stdout, according to the 180 spec.
+
+ Return:
+ * 0: if we actually found and selected a protocol.
+ * -1: if we couldn't find a common supported protocol or if we
+ couldn't even parse tor's supported protocol list.
+
+ XXX: in the future we should return the protocol version we
+ selected. let's keep it simple for now since we have just one
+ protocol version.
+*/
+static int
+conf_proto_version_negotiation(const managed_proxy_t *proxy)
+{
+ int r=-1;
+
+ smartlist_t *versions = smartlist_create();
+ smartlist_split_string(versions, proxy->vars.conf_proto_version, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ SMARTLIST_FOREACH_BEGIN(versions, char *, version) {
+ if (is_supported_conf_protocol(version)) {
+ print_protocol_line("%s %s\n", PROTO_NEG_SUCCESS, version);
+ r=0;
+ goto done;
+ }
+ } SMARTLIST_FOREACH_END(version);
+
+ /* we get here if we couldn't find a supported protocol */
+ print_protocol_line("%s", PROTO_NEG_FAIL);
+
+ done:
+ SMARTLIST_FOREACH(versions, char *, cp, free(cp));
+ smartlist_free(versions);
+
+ return r;
+}
+
+/**
+ Return a string containing the error we encountered while parsing
+ the environment variables, according to 'status'.
+*/
+static inline const char *
+get_env_parsing_error_message(enum env_parsing_status status)
+{
+ switch(status) {
+ case ST_ENV_SMOOTH: return "no error";
+ case ST_ENV_FAIL_STATE_LOC: return "failed on TOR_PT_STATE_LOCATION";
+ case ST_ENV_FAIL_CONF_PROTO_VER: return "failed on TOR_PT_MANAGED_TRANSPORT_VER";
+ case ST_ENV_FAIL_CLIENT_TRANSPORTS: return "failed on TOR_PT_CLIENT_TRANSPORTS";
+ case ST_ENV_FAIL_EXTENDED_PORT: return "failed on TOR_PT_EXTENDED_SERVER_PORT";
+ case ST_ENV_FAIL_ORPORT: return "failed on TOR_PT_ORPORT";
+ case ST_ENV_FAIL_BINDADDR: return "failed on TOR_PT_SERVER_BINDADDR";
+ case ST_ENV_FAIL_SERVER_TRANSPORTS: return "failed on TOR_PT_SERVER_TRANSPORTS";
+ default:
+ obfs_assert(0); return "UNKNOWN";
+ }
+}
+
+/**
+ Return a string containing the error we encountered while
+ launching a listener, according to 'status'.
+*/
+static inline const char *
+get_launch_error_message(enum launch_status status)
+{
+ switch (status) {
+ case ST_LAUNCH_SMOOTH: return "no error";
+ case ST_LAUNCH_FAIL_SETUP: return "could not setup protocol";
+ case ST_LAUNCH_FAIL_LSN: return "could not launch listener";
+ default:
+ obfs_assert(0); return "UNKNOWN";
+ }
+}
+
+/**
+ This function is used for obfsproxy to communicate with tor.
+ Practically a wrapper of printf, it prints 'format' and the
+ fflushes stdout so that the output is always immediately available
+ to tor.
+*/
+static void
+print_protocol_line(const char *format, ...)
+{
+ va_list ap;
+ va_start(ap,format);
+ vprintf(format, ap);
+ fflush(stdout);
+ va_end(ap);
+}
diff --git a/src/managed.h b/src/managed.h
new file mode 100644
index 0000000..2b14c2b
--- /dev/null
+++ b/src/managed.h
@@ -0,0 +1,16 @@
+/* Copyright 2011 Nick Mathewson, George Kadianakis
+ See LICENSE for other credits and copying information
+*/
+
+#ifndef MANAGED_H
+#define MANAGED_H
+
+int launch_managed_proxy();
+
+#ifdef MANAGED_PRIVATE
+
+int validate_bindaddrs(const char *all_bindaddrs, const char *all_transports);
+
+#endif /* MANAGED_PRIVATE */
+
+#endif
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits