diff options
-rw-r--r-- | scripts/maint/practracker/exceptions.txt | 23 | ||||
-rw-r--r-- | src/app/config/config.c | 1 | ||||
-rw-r--r-- | src/app/main/main.c | 1 | ||||
-rw-r--r-- | src/core/include.am | 4 | ||||
-rw-r--r-- | src/feature/control/control.c | 2819 | ||||
-rw-r--r-- | src/feature/control/control.h | 30 | ||||
-rw-r--r-- | src/feature/control/control_auth.c | 439 | ||||
-rw-r--r-- | src/feature/control/control_auth.h | 27 | ||||
-rw-r--r-- | src/feature/control/control_cmd.c | 2323 | ||||
-rw-r--r-- | src/feature/control/control_cmd.h | 48 | ||||
-rw-r--r-- | src/feature/control/control_fmt.c | 95 | ||||
-rw-r--r-- | src/feature/control/control_fmt.h | 5 | ||||
-rw-r--r-- | src/feature/control/control_getinfo.c | 1 | ||||
-rw-r--r-- | src/test/test_controller.c | 3 | ||||
-rw-r--r-- | src/test/test_pt.c | 1 | ||||
-rw-r--r-- | src/test/test_util.c | 1 |
16 files changed, 2965 insertions, 2856 deletions
diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt index 142fda5806..fe91e0c345 100644 --- a/scripts/maint/practracker/exceptions.txt +++ b/scripts/maint/practracker/exceptions.txt @@ -79,8 +79,8 @@ problem function-size /src/core/mainloop/connection.c:connection_handle_write_im problem function-size /src/core/mainloop/connection.c:assert_connection_ok() 143 problem function-size /src/app/config/confparse.c:config_assign_value() 205 problem function-size /src/app/config/confparse.c:config_get_assigned_option() 129 -problem file-size /src/app/config/config.c 8489 -problem include-count /src/app/config/config.c 85 +problem file-size /src/app/config/config.c 8490 +problem include-count /src/app/config/config.c 86 problem function-size /src/app/config/config.c:options_act_reversible() 296 problem function-size /src/app/config/config.c:options_act() 588 problem function-size /src/app/config/config.c:resolve_my_address() 192 @@ -96,7 +96,7 @@ problem function-size /src/app/config/config.c:parse_port_config() 452 problem function-size /src/app/config/config.c:parse_ports() 170 problem function-size /src/app/config/config.c:getinfo_helper_config() 116 problem function-size /src/app/main/ntmain.c:nt_service_install() 125 -problem include-count /src/app/main/main.c 84 +problem include-count /src/app/main/main.c 85 problem function-size /src/app/main/main.c:dumpstats() 102 problem function-size /src/app/main/main.c:tor_init() 136 problem function-size /src/app/main/main.c:sandbox_init_filter() 291 @@ -111,19 +111,20 @@ problem function-size /src/feature/dircommon/consdiff.c:gen_ed_diff() 204 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_auth.c:handle_control_authenticate() 188 +problem function-size /src/feature/control/control_auth.c:handle_control_authchallenge() 115 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 -problem function-size /src/feature/control/control.c:handle_control_hspost() 117 -problem function-size /src/feature/control/control.c:handle_control_add_onion() 293 -problem function-size /src/feature/control/control.c:add_onion_helper_keyarg() 125 +problem function-size /src/feature/control/control_cmd.c:handle_control_command() 104 +problem function-size /src/feature/control/control_cmd.c:handle_control_extendcircuit() 151 +problem function-size /src/feature/control/control_cmd.c:handle_control_hsfetch() 114 +problem function-size /src/feature/control/control_cmd.c:handle_control_hspost() 117 +problem function-size /src/feature/control/control_cmd.c:handle_control_add_onion() 293 +problem function-size /src/feature/control/control_cmd.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 include-count /src/feature/control/control_getinfo.c 52 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/app/config/config.c b/src/app/config/config.c index 1966910a81..07521ea0ae 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -86,6 +86,7 @@ #include "feature/client/entrynodes.h" #include "feature/client/transports.h" #include "feature/control/control.h" +#include "feature/control/control_auth.h" #include "feature/control/control_events.h" #include "feature/dirauth/bwauth.h" #include "feature/dirauth/guardfraction.h" diff --git a/src/app/main/main.c b/src/app/main/main.c index 56978f9bab..83eef0dc70 100644 --- a/src/app/main/main.c +++ b/src/app/main/main.c @@ -43,6 +43,7 @@ #include "feature/client/entrynodes.h" #include "feature/client/transports.h" #include "feature/control/control.h" +#include "feature/control/control_auth.h" #include "feature/control/control_events.h" #include "feature/dirauth/bwauth.h" #include "feature/dirauth/keypin.h" diff --git a/src/core/include.am b/src/core/include.am index a8ec360592..27954af5f5 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -70,7 +70,9 @@ LIBTOR_APP_A_SOURCES = \ src/feature/control/btrack_orconn_cevent.c \ src/feature/control/btrack_orconn_maps.c \ src/feature/control/control.c \ + src/feature/control/control_auth.c \ src/feature/control/control_bootstrap.c \ + src/feature/control/control_cmd.c \ src/feature/control/control_events.c \ src/feature/control/control_fmt.c \ src/feature/control/control_getinfo.c \ @@ -290,6 +292,8 @@ noinst_HEADERS += \ src/feature/control/btrack_orconn_maps.h \ src/feature/control/btrack_sys.h \ src/feature/control/control.h \ + src/feature/control/control_auth.h \ + src/feature/control/control_cmd.h \ src/feature/control/control_connection_st.h \ src/feature/control/control_events.h \ src/feature/control/control_fmt.h \ diff --git a/src/feature/control/control.c b/src/feature/control/control.c index 94643efde8..70d70bedac 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -33,7 +33,6 @@ **/ #define CONTROL_MODULE_PRIVATE -#define CONTROL_PRIVATE #define CONTROL_EVENTS_PRIVATE #define OCIRC_EVENT_PRIVATE @@ -63,6 +62,8 @@ #include "feature/client/dnsserv.h" #include "feature/client/entrynodes.h" #include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_cmd.h" #include "feature/control/control_events.h" #include "feature/control/control_fmt.h" #include "feature/control/control_getinfo.h" @@ -134,84 +135,6 @@ #include "lib/evloop/procmon.h" #include "lib/evloop/compat_libevent.h" -/** Yield true iff <b>s</b> is the state of a control_connection_t that has - * finished authentication and is accepting commands. */ -#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) - -/** If we're using cookie-type authentication, how long should our cookies be? - */ -#define AUTHENTICATION_COOKIE_LEN 32 - -/** If true, we've set authentication_cookie to a secret code and - * stored it to disk. */ -static int authentication_cookie_is_set = 0; -/** If authentication_cookie_is_set, a secret cookie that we've stored to disk - * and which we're using to authenticate controllers. (If the controller can - * read it off disk, it has permission to connect.) */ -static uint8_t *authentication_cookie = NULL; - -#define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ - "Tor safe cookie authentication server-to-controller hash" -#define SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT \ - "Tor safe cookie authentication controller-to-server hash" -#define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN - -/** The list of onion services that have been added via ADD_ONION that do not - * belong to any particular control connection. - */ -static smartlist_t *detached_onion_services = NULL; - -static void send_control_done(control_connection_t *conn); -static int handle_control_setconf(control_connection_t *conn, uint32_t len, - char *body); -static int handle_control_resetconf(control_connection_t *conn, uint32_t len, - char *body); -static int handle_control_getconf(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_loadconf(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_setevents(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_authenticate(control_connection_t *conn, - uint32_t len, - const char *body); -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 int handle_control_extendcircuit(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_setcircuitpurpose(control_connection_t *conn, - uint32_t len, const char *body); -static int handle_control_attachstream(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_postdescriptor(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_redirectstream(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_closestream(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_closecircuit(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_resolve(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_usefeature(control_connection_t *conn, - uint32_t len, - const char *body); -static int handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_hspost(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_add_onion(control_connection_t *conn, uint32_t len, - const char *body); -static int handle_control_del_onion(control_connection_t *conn, uint32_t len, - const char *body); - /** Convert a connection_t* to an control_connection_t*; assert if the cast is * invalid. */ control_connection_t * @@ -221,94 +144,6 @@ TO_CONTROL_CONN(connection_t *c) return DOWNCAST(control_connection_t, c); } -/** If the first <b>in_len_max</b> characters in <b>start</b> contain a - * double-quoted string with escaped characters, return the length of that - * string (as encoded, including quotes). Otherwise return -1. */ -static inline int -get_escaped_string_length(const char *start, size_t in_len_max, - int *chars_out) -{ - const char *cp, *end; - int chars = 0; - - if (*start != '\"') - return -1; - - cp = start+1; - end = start+in_len_max; - - /* Calculate length. */ - while (1) { - if (cp >= end) { - return -1; /* Too long. */ - } else if (*cp == '\\') { - if (++cp == end) - return -1; /* Can't escape EOS. */ - ++cp; - ++chars; - } else if (*cp == '\"') { - break; - } else { - ++cp; - ++chars; - } - } - if (chars_out) - *chars_out = chars; - return (int)(cp - start+1); -} - -/** As decode_escaped_string, but does not decode the string: copies the - * entire thing, including quotation marks. */ -static const char * -extract_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - int length = get_escaped_string_length(start, in_len_max, NULL); - if (length<0) - return NULL; - *out_len = length; - *out = tor_strndup(start, *out_len); - return start+length; -} - -/** Given a pointer to a string starting at <b>start</b> containing - * <b>in_len_max</b> characters, decode a string beginning with one double - * quote, containing any number of non-quote characters or characters escaped - * with a backslash, and ending with a final double quote. Place the resulting - * string (unquoted, unescaped) into a newly allocated string in *<b>out</b>; - * store its length in <b>out_len</b>. On success, return a pointer to the - * character immediately following the escaped string. On failure, return - * NULL. */ -static const char * -decode_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - const char *cp, *end; - char *outp; - int len, n_chars = 0; - - len = get_escaped_string_length(start, in_len_max, &n_chars); - if (len<0) - return NULL; - - end = start+len-1; /* Index of last quote. */ - tor_assert(*end == '\"'); - outp = *out = tor_malloc(len+1); - *out_len = n_chars; - - cp = start+1; - while (cp < end) { - if (*cp == '\\') - ++cp; - *outp++ = *cp++; - } - *outp = '\0'; - tor_assert((outp - *out) == (int)*out_len); - - return end+1; -} - /** Create and add a new controller connection on <b>sock</b>. If * <b>CC_LOCAL_FD_IS_OWNER</b> is set in <b>flags</b>, this Tor process should * exit when the connection closes. If <b>CC_LOCAL_FD_IS_AUTHENTICATED</b> @@ -396,588 +231,6 @@ control_ports_write_to_file(void) smartlist_free(lines); } -/** Send a "DONE" message down the control connection <b>conn</b>. */ -static void -send_control_done(control_connection_t *conn) -{ - connection_write_str_to_buf("250 OK\r\n", conn); -} - -/** Given a text circuit <b>id</b>, return the corresponding circuit. */ -static origin_circuit_t * -get_circ(const char *id) -{ - uint32_t n_id; - int ok; - n_id = (uint32_t) tor_parse_ulong(id, 10, 0, UINT32_MAX, &ok, NULL); - if (!ok) - return NULL; - return circuit_get_by_global_id(n_id); -} - -/** Given a text stream <b>id</b>, return the corresponding AP connection. */ -static entry_connection_t * -get_stream(const char *id) -{ - uint64_t n_id; - int ok; - connection_t *conn; - n_id = tor_parse_uint64(id, 10, 0, UINT64_MAX, &ok, NULL); - if (!ok) - return NULL; - conn = connection_get_by_global_id(n_id); - if (!conn || conn->type != CONN_TYPE_AP || conn->marked_for_close) - return NULL; - return TO_ENTRY_CONN(conn); -} - -/** Helper for setconf and resetconf. Acts like setconf, except - * it passes <b>use_defaults</b> on to options_trial_assign(). Modifies the - * contents of body. - */ -static int -control_setconf_helper(control_connection_t *conn, uint32_t len, char *body, - int use_defaults) -{ - setopt_err_t opt_err; - config_line_t *lines=NULL; - char *start = body; - char *errstring = NULL; - const unsigned flags = - CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0); - - char *config; - smartlist_t *entries = smartlist_new(); - - /* We have a string, "body", of the format '(key(=val|="val")?)' entries - * separated by space. break it into a list of configuration entries. */ - while (*body) { - char *eq = body; - char *key; - char *entry; - while (!TOR_ISSPACE(*eq) && *eq != '=') - ++eq; - key = tor_strndup(body, eq-body); - body = eq+1; - if (*eq == '=') { - char *val=NULL; - size_t val_len=0; - if (*body != '\"') { - char *val_start = body; - while (!TOR_ISSPACE(*body)) - body++; - val = tor_strndup(val_start, body-val_start); - val_len = strlen(val); - } else { - body = (char*)extract_escaped_string(body, (len - (body-start)), - &val, &val_len); - if (!body) { - connection_write_str_to_buf("551 Couldn't parse string\r\n", conn); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - tor_free(key); - return 0; - } - } - tor_asprintf(&entry, "%s %s", key, val); - tor_free(key); - tor_free(val); - } else { - entry = key; - } - smartlist_add(entries, entry); - while (TOR_ISSPACE(*body)) - ++body; - } - - smartlist_add_strdup(entries, ""); - config = smartlist_join_strings(entries, "\n", 0, NULL); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - - if (config_get_lines(config, &lines, 0) < 0) { - log_warn(LD_CONTROL,"Controller gave us config lines we can't parse."); - connection_write_str_to_buf("551 Couldn't parse configuration\r\n", - conn); - tor_free(config); - return 0; - } - tor_free(config); - - opt_err = options_trial_assign(lines, flags, &errstring); - { - const char *msg; - switch (opt_err) { - case SETOPT_ERR_MISC: - msg = "552 Unrecognized option"; - break; - case SETOPT_ERR_PARSE: - msg = "513 Unacceptable option value"; - break; - case SETOPT_ERR_TRANSITION: - msg = "553 Transition not allowed"; - break; - case SETOPT_ERR_SETTING: - default: - msg = "553 Unable to set option"; - break; - case SETOPT_OK: - config_free_lines(lines); - send_control_done(conn); - return 0; - } - log_warn(LD_CONTROL, - "Controller gave us config lines that didn't validate: %s", - errstring); - connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); - config_free_lines(lines); - tor_free(errstring); - return 0; - } -} - -/** Called when we receive a SETCONF message: parse the body and try - * to update our configuration. Reply with a DONE or ERROR message. - * Modifies the contents of body.*/ -static int -handle_control_setconf(control_connection_t *conn, uint32_t len, char *body) -{ - return control_setconf_helper(conn, len, body, 0); -} - -/** Called when we receive a RESETCONF message: parse the body and try - * to update our configuration. Reply with a DONE or ERROR message. - * Modifies the contents of body. */ -static int -handle_control_resetconf(control_connection_t *conn, uint32_t len, char *body) -{ - return control_setconf_helper(conn, len, body, 1); -} - -/** Called when we receive a GETCONF message. Parse the request, and - * reply with a CONFVALUE or an ERROR message */ -static int -handle_control_getconf(control_connection_t *conn, uint32_t body_len, - const char *body) -{ - smartlist_t *questions = smartlist_new(); - smartlist_t *answers = smartlist_new(); - smartlist_t *unrecognized = smartlist_new(); - char *msg = NULL; - size_t msg_len; - const or_options_t *options = get_options(); - int i, len; - - (void) body_len; /* body is NUL-terminated; so we can ignore len. */ - smartlist_split_string(questions, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { - if (!option_is_recognized(q)) { - smartlist_add(unrecognized, (char*) q); - } else { - config_line_t *answer = option_get_assignment(options,q); - if (!answer) { - const char *name = option_get_canonical_name(q); - smartlist_add_asprintf(answers, "250-%s\r\n", name); - } - - while (answer) { - config_line_t *next; - smartlist_add_asprintf(answers, "250-%s=%s\r\n", - answer->key, answer->value); - - next = answer->next; - tor_free(answer->key); - tor_free(answer->value); - tor_free(answer); - answer = next; - } - } - } SMARTLIST_FOREACH_END(q); - - if ((len = smartlist_len(unrecognized))) { - for (i=0; i < len-1; ++i) - connection_printf_to_buf(conn, - "552-Unrecognized configuration key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); - connection_printf_to_buf(conn, - "552 Unrecognized configuration key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, len-1)); - } else if ((len = smartlist_len(answers))) { - char *tmp = smartlist_get(answers, len-1); - tor_assert(strlen(tmp)>4); - tmp[3] = ' '; - msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_buf_add(msg, msg_len, TO_CONN(conn)); - } else { - connection_write_str_to_buf("250 OK\r\n", conn); - } - - SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); - smartlist_free(answers); - SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); - smartlist_free(questions); - smartlist_free(unrecognized); - - tor_free(msg); - - return 0; -} - -/** Called when we get a +LOADCONF message. */ -static int -handle_control_loadconf(control_connection_t *conn, uint32_t len, - const char *body) -{ - setopt_err_t retval; - char *errstring = NULL; - const char *msg = NULL; - (void) len; - - retval = options_init_from_string(NULL, body, CMD_RUN_TOR, NULL, &errstring); - - if (retval != SETOPT_OK) - log_warn(LD_CONTROL, - "Controller gave us config file that didn't validate: %s", - errstring); - - switch (retval) { - case SETOPT_ERR_PARSE: - msg = "552 Invalid config file"; - break; - case SETOPT_ERR_TRANSITION: - msg = "553 Transition not allowed"; - break; - case SETOPT_ERR_SETTING: - msg = "553 Unable to set option"; - break; - case SETOPT_ERR_MISC: - default: - msg = "550 Unable to load config"; - break; - case SETOPT_OK: - break; - } - if (msg) { - if (errstring) - connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); - else - connection_printf_to_buf(conn, "%s\r\n", msg); - } else { - send_control_done(conn); - } - tor_free(errstring); - return 0; -} - -/** Called when we get a SETEVENTS message: update conn->event_mask, - * and reply with DONE or ERROR. */ -static int -handle_control_setevents(control_connection_t *conn, uint32_t len, - const char *body) -{ - int event_code; - event_mask_t event_mask = 0; - smartlist_t *events = smartlist_new(); - - (void) len; - - smartlist_split_string(events, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(events, const char *, ev) - { - if (!strcasecmp(ev, "EXTENDED") || - !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { - log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " - "supported.", ev); - continue; - } else { - int i; - event_code = -1; - - for (i = 0; control_event_table[i].event_name != NULL; ++i) { - if (!strcasecmp(ev, control_event_table[i].event_name)) { - event_code = control_event_table[i].event_code; - break; - } - } - - if (event_code == -1) { - connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", - ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); - return 0; - } - } - event_mask |= (((event_mask_t)1) << event_code); - } - SMARTLIST_FOREACH_END(ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); - - conn->event_mask = event_mask; - - control_update_global_event_mask(); - send_control_done(conn); - return 0; -} - -/** Decode the hashed, base64'd passwords stored in <b>passwords</b>. - * Return a smartlist of acceptable passwords (unterminated strings of - * length S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on - * failure. - */ -smartlist_t * -decode_hashed_passwords(config_line_t *passwords) -{ - char decoded[64]; - config_line_t *cl; - smartlist_t *sl = smartlist_new(); - - tor_assert(passwords); - - for (cl = passwords; cl; cl = cl->next) { - const char *hashed = cl->value; - - if (!strcmpstart(hashed, "16:")) { - if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3)) - != S2K_RFC2440_SPECIFIER_LEN + DIGEST_LEN - || strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) { - goto err; - } - } else { - if (base64_decode(decoded, sizeof(decoded), hashed, strlen(hashed)) - != S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) { - goto err; - } - } - smartlist_add(sl, - tor_memdup(decoded, S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)); - } - - return sl; - - err: - SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp)); - smartlist_free(sl); - return NULL; -} - -/** Called when we get an AUTHENTICATE message. Check whether the - * authentication is valid, and if so, update the connection's state to - * OPEN. Reply with DONE or ERROR. - */ -static int -handle_control_authenticate(control_connection_t *conn, uint32_t len, - const char *body) -{ - int used_quoted_string = 0; - const or_options_t *options = get_options(); - const char *errstr = "Unknown error"; - char *password; - size_t password_len; - const char *cp; - int i; - int bad_cookie=0, bad_password=0; - smartlist_t *sl = NULL; - - if (!len) { - password = tor_strdup(""); - password_len = 0; - } else if (TOR_ISXDIGIT(body[0])) { - cp = body; - while (TOR_ISXDIGIT(*cp)) - ++cp; - i = (int)(cp - body); - tor_assert(i>0); - password_len = i/2; - password = tor_malloc(password_len + 1); - if (base16_decode(password, password_len+1, body, i) - != (int) password_len) { - connection_write_str_to_buf( - "551 Invalid hexadecimal encoding. Maybe you tried a plain text " - "password? If so, the standard requires that you put it in " - "double quotes.\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(password); - return 0; - } - } else { - if (!decode_escaped_string(body, len, &password, &password_len)) { - connection_write_str_to_buf("551 Invalid quoted string. You need " - "to put the password in double quotes.\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return 0; - } - used_quoted_string = 1; - } - - if (conn->safecookie_client_hash != NULL) { - /* The controller has chosen safe cookie authentication; the only - * acceptable authentication value is the controller-to-server - * response. */ - - tor_assert(authentication_cookie_is_set); - - if (password_len != DIGEST256_LEN) { - log_warn(LD_CONTROL, - "Got safe cookie authentication response with wrong length " - "(%d)", (int)password_len); - errstr = "Wrong length for safe cookie response."; - goto err; - } - - if (tor_memneq(conn->safecookie_client_hash, password, DIGEST256_LEN)) { - log_warn(LD_CONTROL, - "Got incorrect safe cookie authentication response"); - errstr = "Safe cookie response did not match expected value."; - goto err; - } - - tor_free(conn->safecookie_client_hash); - goto ok; - } - - if (!options->CookieAuthentication && !options->HashedControlPassword && - !options->HashedControlSessionPassword) { - /* if Tor doesn't demand any stronger authentication, then - * the controller can get in with anything. */ - goto ok; - } - - if (options->CookieAuthentication) { - int also_password = options->HashedControlPassword != NULL || - options->HashedControlSessionPassword != NULL; - if (password_len != AUTHENTICATION_COOKIE_LEN) { - if (!also_password) { - log_warn(LD_CONTROL, "Got authentication cookie with wrong length " - "(%d)", (int)password_len); - errstr = "Wrong length on authentication cookie."; - goto err; - } - bad_cookie = 1; - } else if (tor_memneq(authentication_cookie, password, password_len)) { - if (!also_password) { - log_warn(LD_CONTROL, "Got mismatched authentication cookie"); - errstr = "Authentication cookie did not match expected value."; - goto err; - } - bad_cookie = 1; - } else { - goto ok; - } - } - - if (options->HashedControlPassword || - options->HashedControlSessionPassword) { - int bad = 0; - smartlist_t *sl_tmp; - char received[DIGEST_LEN]; - int also_cookie = options->CookieAuthentication; - sl = smartlist_new(); - if (options->HashedControlPassword) { - sl_tmp = decode_hashed_passwords(options->HashedControlPassword); - if (!sl_tmp) - bad = 1; - else { - smartlist_add_all(sl, sl_tmp); - smartlist_free(sl_tmp); - } - } - if (options->HashedControlSessionPassword) { - sl_tmp = decode_hashed_passwords(options->HashedControlSessionPassword); - if (!sl_tmp) - bad = 1; - else { - smartlist_add_all(sl, sl_tmp); - smartlist_free(sl_tmp); - } - } - if (bad) { - if (!also_cookie) { - log_warn(LD_BUG, - "Couldn't decode HashedControlPassword: invalid base16"); - errstr="Couldn't decode HashedControlPassword value in configuration."; - goto err; - } - bad_password = 1; - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - sl = NULL; - } else { - SMARTLIST_FOREACH(sl, char *, expected, - { - secret_to_key_rfc2440(received,DIGEST_LEN, - password,password_len,expected); - if (tor_memeq(expected + S2K_RFC2440_SPECIFIER_LEN, - received, DIGEST_LEN)) - goto ok; - }); - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - sl = NULL; - - if (used_quoted_string) - errstr = "Password did not match HashedControlPassword value from " - "configuration"; - else - errstr = "Password did not match HashedControlPassword value from " - "configuration. Maybe you tried a plain text password? " - "If so, the standard requires that you put it in double quotes."; - bad_password = 1; - if (!also_cookie) - goto err; - } - } - - /** We only get here if both kinds of authentication failed. */ - tor_assert(bad_password && bad_cookie); - log_warn(LD_CONTROL, "Bad password or authentication cookie on controller."); - errstr = "Password did not match HashedControlPassword *or* authentication " - "cookie."; - - err: - tor_free(password); - connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", errstr); - connection_mark_for_close(TO_CONN(conn)); - if (sl) { /* clean up */ - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - } - return 0; - ok: - log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT - ")", conn->base_.s); - send_control_done(conn); - conn->base_.state = CONTROL_CONN_STATE_OPEN; - tor_free(password); - if (sl) { /* clean up */ - SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); - smartlist_free(sl); - } - return 0; -} - -/** Called when we get a SAVECONF command. Try to flush the current options to - * disk, and report success or failure. */ -static int -handle_control_saveconf(control_connection_t *conn, uint32_t len, - const char *body) -{ - (void) len; - - int force = !strcmpstart(body, "FORCE"); - const or_options_t *options = get_options(); - if ((!force && options->IncludeUsed) || options_save_current() < 0) { - connection_write_str_to_buf( - "551 Unable to write configuration to disk.\r\n", conn); - } else { - send_control_done(conn); - } - return 0; -} - const struct signal_name_t signal_table[] = { { SIGHUP, "RELOAD" }, { SIGHUP, "HUP" }, @@ -997,1927 +250,6 @@ const struct signal_name_t signal_table[] = { { 0, NULL }, }; -/** Called when we get a SIGNAL command. React to the provided signal, and - * report success or failure. (If the signal results in a shutdown, success - * may not be reported.) */ -static int -handle_control_signal(control_connection_t *conn, uint32_t len, - const char *body) -{ - int sig = -1; - int i; - int n = 0; - char *s; - - (void) len; - - while (body[n] && ! TOR_ISSPACE(body[n])) - ++n; - s = tor_strndup(body, n); - - for (i = 0; signal_table[i].signal_name != NULL; ++i) { - if (!strcasecmp(s, signal_table[i].signal_name)) { - sig = signal_table[i].sig; - break; - } - } - - if (sig < 0) - connection_printf_to_buf(conn, "552 Unrecognized signal code \"%s\"\r\n", - s); - tor_free(s); - if (sig < 0) - return 0; - - send_control_done(conn); - /* Flush the "done" first if the signal might make us shut down. */ - if (sig == SIGTERM || sig == SIGINT) - connection_flush(TO_CONN(conn)); - - activate_signal(sig); - - return 0; -} - -/** Called when we get a TAKEOWNERSHIP command. Mark this connection - * as an owning connection, so that we will exit if the connection - * closes. */ -static int -handle_control_takeownership(control_connection_t *conn, uint32_t len, - const char *body) -{ - (void)len; - (void)body; - - conn->is_owning_control_connection = 1; - - log_info(LD_CONTROL, "Control connection %d has taken ownership of this " - "Tor instance.", - (int)(conn->base_.s)); - - send_control_done(conn); - return 0; -} - -/** Called when we get a DROPOWNERSHIP command. Mark this connection - * as a non-owning connection, so that we will not exit if the connection - * closes. */ -static int -handle_control_dropownership(control_connection_t *conn, uint32_t len, - const char *body) -{ - (void)len; - (void)body; - - conn->is_owning_control_connection = 0; - - log_info(LD_CONTROL, "Control connection %d has dropped ownership of this " - "Tor instance.", - (int)(conn->base_.s)); - - send_control_done(conn); - return 0; -} - -/** Return true iff <b>addr</b> is unusable as a mapaddress target because of - * containing funny characters. */ -static int -address_is_invalid_mapaddress_target(const char *addr) -{ - if (!strcmpstart(addr, "*.")) - return address_is_invalid_destination(addr+2, 1); - else - return address_is_invalid_destination(addr, 1); -} - -/** Called when we get a MAPADDRESS command; try to bind all listed addresses, - * and report success or failure. */ -static int -handle_control_mapaddress(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *elts; - smartlist_t *lines; - smartlist_t *reply; - char *r; - size_t sz; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - lines = smartlist_new(); - elts = smartlist_new(); - reply = smartlist_new(); - smartlist_split_string(lines, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(lines, char *, line) { - tor_strlower(line); - smartlist_split_string(elts, line, "=", 0, 2); - if (smartlist_len(elts) == 2) { - const char *from = smartlist_get(elts,0); - const char *to = smartlist_get(elts,1); - if (address_is_invalid_mapaddress_target(to)) { - smartlist_add_asprintf(reply, - "512-syntax error: invalid address '%s'", to); - log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg", to); - } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0") || - !strcmp(from, "::")) { - const char type = - !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : - (!strcmp(from, "0.0.0.0") ? RESOLVED_TYPE_IPV4 : RESOLVED_TYPE_IPV6); - const char *address = addressmap_register_virtual_address( - type, tor_strdup(to)); - if (!address) { - smartlist_add_asprintf(reply, - "451-resource exhausted: skipping '%s'", line); - log_warn(LD_CONTROL, - "Unable to allocate address for '%s' in MapAddress msg", - safe_str_client(line)); - } else { - smartlist_add_asprintf(reply, "250-%s=%s", address, to); - } - } else { - const char *msg; - if (addressmap_register_auto(from, to, 1, - ADDRMAPSRC_CONTROLLER, &msg) < 0) { - smartlist_add_asprintf(reply, - "512-syntax error: invalid address mapping " - " '%s': %s", line, msg); - log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg: %s", - line, msg); - } else { - smartlist_add_asprintf(reply, "250-%s", line); - } - } - } else { - smartlist_add_asprintf(reply, "512-syntax error: mapping '%s' is " - "not of expected form 'foo=bar'.", line); - log_info(LD_CONTROL, "Skipping MapAddress '%s': wrong " - "number of items.", - safe_str_client(line)); - } - SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); - smartlist_clear(elts); - } SMARTLIST_FOREACH_END(line); - SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); - smartlist_free(lines); - smartlist_free(elts); - - if (smartlist_len(reply)) { - ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; - r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_buf_add(r, sz, TO_CONN(conn)); - tor_free(r); - } else { - const char *response = - "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_buf_add(response, strlen(response), TO_CONN(conn)); - } - - SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); - smartlist_free(reply); - return 0; -} - -/** Given a string, convert it to a circuit purpose. */ -static uint8_t -circuit_purpose_from_string(const char *string) -{ - if (!strcasecmpstart(string, "purpose=")) - string += strlen("purpose="); - - if (!strcasecmp(string, "general")) - return CIRCUIT_PURPOSE_C_GENERAL; - else if (!strcasecmp(string, "controller")) - return CIRCUIT_PURPOSE_CONTROLLER; - else - return CIRCUIT_PURPOSE_UNKNOWN; -} - -/** Return a newly allocated smartlist containing the arguments to the command - * waiting in <b>body</b>. If there are fewer than <b>min_args</b> arguments, - * or if <b>max_args</b> is nonnegative and there are more than - * <b>max_args</b> arguments, send a 512 error to the controller, using - * <b>command</b> as the command name in the error message. */ -static smartlist_t * -getargs_helper(const char *command, control_connection_t *conn, - const char *body, int min_args, int max_args) -{ - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(args) < min_args) { - connection_printf_to_buf(conn, "512 Missing argument to %s\r\n",command); - goto err; - } else if (max_args >= 0 && smartlist_len(args) > max_args) { - connection_printf_to_buf(conn, "512 Too many arguments to %s\r\n",command); - goto err; - } - return args; - err: - SMARTLIST_FOREACH(args, char *, s, tor_free(s)); - smartlist_free(args); - return NULL; -} - -/** Helper. Return the first element of <b>sl</b> at index <b>start_at</b> or - * higher that starts with <b>prefix</b>, case-insensitive. Return NULL if no - * such element exists. */ -static const char * -find_element_starting_with(smartlist_t *sl, int start_at, const char *prefix) -{ - int i; - for (i = start_at; i < smartlist_len(sl); ++i) { - const char *elt = smartlist_get(sl, i); - if (!strcasecmpstart(elt, prefix)) - return elt; - } - return NULL; -} - -/** Helper. Return true iff s is an argument that we should treat as a - * key-value pair. */ -static int -is_keyval_pair(const char *s) -{ - /* An argument is a key-value pair if it has an =, and it isn't of the form - * $fingeprint=name */ - return strchr(s, '=') && s[0] != '$'; -} - -/** Called when we get an EXTENDCIRCUIT message. Try to extend the listed - * circuit, and report success or failure. */ -static int -handle_control_extendcircuit(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *router_nicknames=NULL, *nodes=NULL; - origin_circuit_t *circ = NULL; - int zero_circ; - uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; - smartlist_t *args; - (void) len; - - router_nicknames = smartlist_new(); - - args = getargs_helper("EXTENDCIRCUIT", conn, body, 1, -1); - if (!args) - goto done; - - zero_circ = !strcmp("0", (char*)smartlist_get(args,0)); - - if (zero_circ) { - const char *purp = find_element_starting_with(args, 1, "PURPOSE="); - - if (purp) { - intended_purpose = circuit_purpose_from_string(purp); - if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - } - - if ((smartlist_len(args) == 1) || - (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { - // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" - circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); - if (!circ) { - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - } else { - connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", - (unsigned long)circ->global_identifier); - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - // "EXTENDCIRCUIT 0 router1,router2" || - // "EXTENDCIRCUIT 0 router1,router2 PURPOSE=foo" - } - - if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - - if (smartlist_len(args) < 2) { - connection_printf_to_buf(conn, - "512 syntax error: not enough arguments.\r\n"); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } - - smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0); - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - - nodes = smartlist_new(); - int first_node = zero_circ; - SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 0); - if (!node) { - connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); - goto done; - } - if (!node_has_preferred_descriptor(node, first_node)) { - connection_printf_to_buf(conn, "552 No descriptor for \"%s\"\r\n", n); - goto done; - } - smartlist_add(nodes, (void*)node); - first_node = 0; - } SMARTLIST_FOREACH_END(n); - if (!smartlist_len(nodes)) { - connection_write_str_to_buf("512 No router names provided\r\n", conn); - goto done; - } - - if (zero_circ) { - /* start a new circuit */ - circ = origin_circuit_init(intended_purpose, 0); - } - - /* now circ refers to something that is ready to be extended */ - first_node = zero_circ; - SMARTLIST_FOREACH(nodes, const node_t *, node, - { - extend_info_t *info = extend_info_from_node(node, first_node); - if (!info) { - tor_assert_nonfatal(first_node); - log_warn(LD_CONTROL, - "controller tried to connect to a node that lacks a suitable " - "descriptor, or which doesn't have any " - "addresses that are allowed by the firewall configuration; " - "circuit marked for closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - goto done; - } - circuit_append_new_exit(circ, info); - if (circ->build_state->desired_path_len > 1) { - circ->build_state->onehop_tunnel = 0; - } - extend_info_free(info); - first_node = 0; - }); - - /* now that we've populated the cpath, start extending */ - if (zero_circ) { - int err_reason = 0; - if ((err_reason = circuit_handle_first_hop(circ)) < 0) { - circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); - goto done; - } - } else { - if (circ->base_.state == CIRCUIT_STATE_OPEN || - circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) { - int err_reason = 0; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { - log_info(LD_CONTROL, - "send_next_onion_skin failed; circuit marked for closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); - connection_write_str_to_buf("551 Couldn't send onion skin\r\n", conn); - goto done; - } - } - } - - connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", - (unsigned long)circ->global_identifier); - if (zero_circ) /* send a 'launched' event, for completeness */ - circuit_event_status(circ, CIRC_EVENT_LAUNCHED, 0); - done: - SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); - smartlist_free(router_nicknames); - smartlist_free(nodes); - return 0; -} - -/** Called when we get a SETCIRCUITPURPOSE message. If we can find the - * circuit and it's a valid purpose, change it. */ -static int -handle_control_setcircuitpurpose(control_connection_t *conn, - uint32_t len, const char *body) -{ - origin_circuit_t *circ = NULL; - uint8_t new_purpose; - smartlist_t *args; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - args = getargs_helper("SETCIRCUITPURPOSE", conn, body, 2, -1); - if (!args) - goto done; - - if (!(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - goto done; - } - - { - const char *purp = find_element_starting_with(args,1,"PURPOSE="); - if (!purp) { - connection_write_str_to_buf("552 No purpose given\r\n", conn); - goto done; - } - new_purpose = circuit_purpose_from_string(purp); - if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); - goto done; - } - } - - circuit_change_purpose(TO_CIRCUIT(circ), new_purpose); - connection_write_str_to_buf("250 OK\r\n", conn); - - done: - if (args) { - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - } - return 0; -} - -/** Called when we get an ATTACHSTREAM message. Try to attach the requested - * stream, and report success or failure. */ -static int -handle_control_attachstream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn = NULL; - origin_circuit_t *circ = NULL; - int zero_circ; - smartlist_t *args; - crypt_path_t *cpath=NULL; - int hop=0, hop_line_ok=1; - (void) len; - - args = getargs_helper("ATTACHSTREAM", conn, body, 2, -1); - if (!args) - return 0; - - zero_circ = !strcmp("0", (char*)smartlist_get(args,1)); - - if (!(ap_conn = get_stream(smartlist_get(args, 0)))) { - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - } else if (!zero_circ && !(circ = get_circ(smartlist_get(args, 1)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 1)); - } else if (circ) { - const char *hopstring = find_element_starting_with(args,2,"HOP="); - if (hopstring) { - hopstring += strlen("HOP="); - hop = (int) tor_parse_ulong(hopstring, 10, 0, INT_MAX, - &hop_line_ok, NULL); - if (!hop_line_ok) { /* broken hop line */ - connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", hopstring); - } - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!ap_conn || (!zero_circ && !circ) || !hop_line_ok) - return 0; - - if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && - ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && - ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_RESOLVE_WAIT) { - connection_write_str_to_buf( - "555 Connection is not managed by controller.\r\n", - conn); - return 0; - } - - /* Do we need to detach it first? */ - if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT) { - edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); - circuit_t *tmpcirc = circuit_get_by_edge_conn(edge_conn); - connection_edge_end(edge_conn, END_STREAM_REASON_TIMEOUT); - /* Un-mark it as ending, since we're going to reuse it. */ - edge_conn->edge_has_sent_end = 0; - edge_conn->end_reason = 0; - if (tmpcirc) - circuit_detach_stream(tmpcirc, edge_conn); - CONNECTION_AP_EXPECT_NONPENDING(ap_conn); - TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; - } - - if (circ && (circ->base_.state != CIRCUIT_STATE_OPEN)) { - connection_write_str_to_buf( - "551 Can't attach stream to non-open origin circuit\r\n", - conn); - return 0; - } - /* Is this a single hop circuit? */ - if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { - connection_write_str_to_buf( - "551 Can't attach stream to this one-hop circuit.\r\n", conn); - return 0; - } - - if (circ && hop>0) { - /* find this hop in the circuit, and set cpath */ - cpath = circuit_get_cpath_hop(circ, hop); - if (!cpath) { - connection_printf_to_buf(conn, - "551 Circuit doesn't have %d hops.\r\n", hop); - return 0; - } - } - if (connection_ap_handshake_rewrite_and_attach(ap_conn, circ, cpath) < 0) { - connection_write_str_to_buf("551 Unable to attach stream\r\n", conn); - return 0; - } - send_control_done(conn); - return 0; -} - -/** Called when we get a POSTDESCRIPTOR message. Try to learn the provided - * descriptor, and report success or failure. */ -static int -handle_control_postdescriptor(control_connection_t *conn, uint32_t len, - const char *body) -{ - char *desc; - const char *msg=NULL; - uint8_t purpose = ROUTER_PURPOSE_GENERAL; - int cache = 0; /* eventually, we may switch this to 1 */ - - const char *cp = memchr(body, '\n', len); - - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; - } - ++cp; - - char *cmdline = tor_memdup_nulterm(body, cp-body); - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, cmdline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, char *, option) { - if (!strcasecmpstart(option, "purpose=")) { - option += strlen("purpose="); - purpose = router_purpose_from_string(option); - if (purpose == ROUTER_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", - option); - goto done; - } - } else if (!strcasecmpstart(option, "cache=")) { - option += strlen("cache="); - if (!strcasecmp(option, "no")) - cache = 0; - else if (!strcasecmp(option, "yes")) - cache = 1; - else { - connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n", - option); - goto done; - } - } else { /* unrecognized argument? */ - connection_printf_to_buf(conn, - "512 Unexpected argument \"%s\" to postdescriptor\r\n", option); - goto done; - } - } SMARTLIST_FOREACH_END(option); - - read_escaped_data(cp, len-(cp-body), &desc); - - switch (router_load_single_router(desc, purpose, cache, &msg)) { - case -1: - if (!msg) msg = "Could not parse descriptor"; - connection_printf_to_buf(conn, "554 %s\r\n", msg); - break; - case 0: - if (!msg) msg = "Descriptor not added"; - connection_printf_to_buf(conn, "251 %s\r\n",msg); - break; - case 1: - send_control_done(conn); - break; - } - - tor_free(desc); - done: - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - tor_free(cmdline); - return 0; -} - -/** Called when we receive a REDIRECTSTERAM command. Try to change the target - * address of the named AP stream, and report success or failure. */ -static int -handle_control_redirectstream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn = NULL; - char *new_addr = NULL; - uint16_t new_port = 0; - smartlist_t *args; - (void) len; - - args = getargs_helper("REDIRECTSTREAM", conn, body, 2, -1); - if (!args) - return 0; - - if (!(ap_conn = get_stream(smartlist_get(args, 0))) - || !ap_conn->socks_request) { - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - } else { - int ok = 1; - if (smartlist_len(args) > 2) { /* they included a port too */ - new_port = (uint16_t) tor_parse_ulong(smartlist_get(args, 2), - 10, 1, 65535, &ok, NULL); - } - if (!ok) { - connection_printf_to_buf(conn, "512 Cannot parse port \"%s\"\r\n", - (char*)smartlist_get(args, 2)); - } else { - new_addr = tor_strdup(smartlist_get(args, 1)); - } - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!new_addr) - return 0; - - strlcpy(ap_conn->socks_request->address, new_addr, - sizeof(ap_conn->socks_request->address)); - if (new_port) - ap_conn->socks_request->port = new_port; - tor_free(new_addr); - send_control_done(conn); - return 0; -} - -/** Called when we get a CLOSESTREAM command; try to close the named stream - * and report success or failure. */ -static int -handle_control_closestream(control_connection_t *conn, uint32_t len, - const char *body) -{ - entry_connection_t *ap_conn=NULL; - uint8_t reason=0; - smartlist_t *args; - int ok; - (void) len; - - args = getargs_helper("CLOSESTREAM", conn, body, 2, -1); - if (!args) - return 0; - - else if (!(ap_conn = get_stream(smartlist_get(args, 0)))) - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - else { - reason = (uint8_t) tor_parse_ulong(smartlist_get(args,1), 10, 0, 255, - &ok, NULL); - if (!ok) { - connection_printf_to_buf(conn, "552 Unrecognized reason \"%s\"\r\n", - (char*)smartlist_get(args, 1)); - ap_conn = NULL; - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!ap_conn) - return 0; - - connection_mark_unattached_ap(ap_conn, reason); - send_control_done(conn); - return 0; -} - -/** Called when we get a CLOSECIRCUIT command; try to close the named circuit - * and report success or failure. */ -static int -handle_control_closecircuit(control_connection_t *conn, uint32_t len, - const char *body) -{ - origin_circuit_t *circ = NULL; - int safe = 0; - smartlist_t *args; - (void) len; - - args = getargs_helper("CLOSECIRCUIT", conn, body, 1, -1); - if (!args) - return 0; - - if (!(circ=get_circ(smartlist_get(args, 0)))) - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - else { - int i; - for (i=1; i < smartlist_len(args); ++i) { - if (!strcasecmp(smartlist_get(args, i), "IfUnused")) - safe = 1; - else - log_info(LD_CONTROL, "Skipping unknown option %s", - (char*)smartlist_get(args,i)); - } - } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!circ) - return 0; - - if (!safe || !circ->p_streams) { - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); - } - - send_control_done(conn); - return 0; -} - -/** Called when we get a RESOLVE command: start trying to resolve - * the listed addresses. */ -static int -handle_control_resolve(control_connection_t *conn, uint32_t len, - const char *body) -{ - smartlist_t *args, *failed; - int is_reverse = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - - if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { - log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " - "isn't listening for ADDRMAP events. It probably won't see " - "the answer."); - } - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - { - const char *modearg = find_element_starting_with(args, 0, "mode="); - if (modearg && !strcasecmp(modearg, "mode=reverse")) - is_reverse = 1; - } - failed = smartlist_new(); - SMARTLIST_FOREACH(args, const char *, arg, { - if (!is_keyval_pair(arg)) { - if (dnsserv_launch_request(arg, is_reverse, conn)<0) - smartlist_add(failed, (char*)arg); - } - }); - - send_control_done(conn); - SMARTLIST_FOREACH(failed, const char *, arg, { - control_event_address_mapped(arg, arg, time(NULL), - "internal", 0); - }); - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - smartlist_free(failed); - return 0; -} - -/** Called when we get a PROTOCOLINFO command: send back a reply. */ -static int -handle_control_protocolinfo(control_connection_t *conn, uint32_t len, - const char *body) -{ - const char *bad_arg = NULL; - smartlist_t *args; - (void)len; - - conn->have_sent_protocolinfo = 1; - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH(args, const char *, arg, { - int ok; - tor_parse_long(arg, 10, 0, LONG_MAX, &ok, NULL); - if (!ok) { - bad_arg = arg; - break; - } - }); - if (bad_arg) { - connection_printf_to_buf(conn, "513 No such version %s\r\n", - escaped(bad_arg)); - /* Don't tolerate bad arguments when not authenticated. */ - if (!STATE_IS_OPEN(TO_CONN(conn)->state)) - connection_mark_for_close(TO_CONN(conn)); - goto done; - } else { - const or_options_t *options = get_options(); - int cookies = options->CookieAuthentication; - char *cfile = get_controller_cookie_file_name(); - char *abs_cfile; - char *esc_cfile; - char *methods; - abs_cfile = make_path_absolute(cfile); - esc_cfile = esc_for_log(abs_cfile); - { - int passwd = (options->HashedControlPassword != NULL || - options->HashedControlSessionPassword != NULL); - smartlist_t *mlist = smartlist_new(); - if (cookies) { - smartlist_add(mlist, (char*)"COOKIE"); - smartlist_add(mlist, (char*)"SAFECOOKIE"); - } - if (passwd) - smartlist_add(mlist, (char*)"HASHEDPASSWORD"); - if (!cookies && !passwd) - smartlist_add(mlist, (char*)"NULL"); - methods = smartlist_join_strings(mlist, ",", 0, NULL); - smartlist_free(mlist); - } - - connection_printf_to_buf(conn, - "250-PROTOCOLINFO 1\r\n" - "250-AUTH METHODS=%s%s%s\r\n" - "250-VERSION Tor=%s\r\n" - "250 OK\r\n", - methods, - cookies?" COOKIEFILE=":"", - cookies?esc_cfile:"", - escaped(VERSION)); - tor_free(methods); - tor_free(cfile); - tor_free(abs_cfile); - tor_free(esc_cfile); - } - done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Called when we get an AUTHCHALLENGE command. */ -static int -handle_control_authchallenge(control_connection_t *conn, uint32_t len, - const char *body) -{ - const char *cp = body; - char *client_nonce; - size_t client_nonce_len; - char server_hash[DIGEST256_LEN]; - char server_hash_encoded[HEX_DIGEST256_LEN+1]; - char server_nonce[SAFECOOKIE_SERVER_NONCE_LEN]; - char server_nonce_encoded[(2*SAFECOOKIE_SERVER_NONCE_LEN) + 1]; - - cp += strspn(cp, " \t\n\r"); - if (!strcasecmpstart(cp, "SAFECOOKIE")) { - cp += strlen("SAFECOOKIE"); - } else { - connection_write_str_to_buf("513 AUTHCHALLENGE only supports SAFECOOKIE " - "authentication\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - - if (!authentication_cookie_is_set) { - connection_write_str_to_buf("515 Cookie authentication is disabled\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - - cp += strspn(cp, " \t\n\r"); - if (*cp == '"') { - const char *newcp = - decode_escaped_string(cp, len - (cp - body), - &client_nonce, &client_nonce_len); - if (newcp == NULL) { - connection_write_str_to_buf("513 Invalid quoted client nonce\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - cp = newcp; - } else { - size_t client_nonce_encoded_len = strspn(cp, "0123456789ABCDEFabcdef"); - - client_nonce_len = client_nonce_encoded_len / 2; - client_nonce = tor_malloc_zero(client_nonce_len); - - if (base16_decode(client_nonce, client_nonce_len, - cp, client_nonce_encoded_len) - != (int) client_nonce_len) { - connection_write_str_to_buf("513 Invalid base16 client nonce\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(client_nonce); - return -1; - } - - cp += client_nonce_encoded_len; - } - - cp += strspn(cp, " \t\n\r"); - if (*cp != '\0' || - cp != body + len) { - connection_write_str_to_buf("513 Junk at end of AUTHCHALLENGE command\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(client_nonce); - return -1; - } - crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); - - /* Now compute and send the server-to-controller response, and the - * server's nonce. */ - tor_assert(authentication_cookie != NULL); - - { - size_t tmp_len = (AUTHENTICATION_COOKIE_LEN + - client_nonce_len + - SAFECOOKIE_SERVER_NONCE_LEN); - char *tmp = tor_malloc_zero(tmp_len); - char *client_hash = tor_malloc_zero(DIGEST256_LEN); - memcpy(tmp, authentication_cookie, AUTHENTICATION_COOKIE_LEN); - memcpy(tmp + AUTHENTICATION_COOKIE_LEN, client_nonce, client_nonce_len); - memcpy(tmp + AUTHENTICATION_COOKIE_LEN + client_nonce_len, - server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); - - crypto_hmac_sha256(server_hash, - SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT, - strlen(SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT), - tmp, - tmp_len); - - crypto_hmac_sha256(client_hash, - SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT, - strlen(SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT), - tmp, - tmp_len); - - conn->safecookie_client_hash = client_hash; - - tor_free(tmp); - } - - base16_encode(server_hash_encoded, sizeof(server_hash_encoded), - server_hash, sizeof(server_hash)); - base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), - server_nonce, sizeof(server_nonce)); - - connection_printf_to_buf(conn, - "250 AUTHCHALLENGE SERVERHASH=%s " - "SERVERNONCE=%s\r\n", - server_hash_encoded, - server_nonce_encoded); - - tor_free(client_nonce); - return 0; -} - -/** Called when we get a USEFEATURE command: parse the feature list, and - * set up the control_connection's options properly. */ -static int -handle_control_usefeature(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - int bad = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { - if (!strcasecmp(arg, "VERBOSE_NAMES")) - ; - else if (!strcasecmp(arg, "EXTENDED_EVENTS")) - ; - else { - connection_printf_to_buf(conn, "552 Unrecognized feature \"%s\"\r\n", - arg); - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(arg); - - if (!bad) { - send_control_done(conn); - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Implementation for the DROPGUARDS command. */ -static int -handle_control_dropguards(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - - static int have_warned = 0; - if (! have_warned) { - log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand " - "the risks before using it. It may be removed in a future " - "version of Tor."); - have_warned = 1; - } - - if (smartlist_len(args)) { - connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); - } else { - remove_all_entry_guards(); - send_control_done(conn); - } - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - return 0; -} - -/** Implementation for the HSFETCH command. */ -static int -handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body) -{ - int i; - char digest[DIGEST_LEN], *hsaddress = NULL, *arg1 = NULL, *desc_id = NULL; - smartlist_t *args = NULL, *hsdirs = NULL; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - static const char *hsfetch_command = "HSFETCH"; - static const char *v2_str = "v2-"; - const size_t v2_str_len = strlen(v2_str); - rend_data_t *rend_query = NULL; - ed25519_public_key_t v3_pk; - uint32_t version; - - /* Make sure we have at least one argument, the HSAddress. */ - args = getargs_helper(hsfetch_command, conn, body, 1, -1); - if (!args) { - goto exit; - } - - /* Extract the first argument (either HSAddress or DescID). */ - arg1 = smartlist_get(args, 0); - /* Test if it's an HS address without the .onion part. */ - if (rend_valid_v2_service_id(arg1)) { - hsaddress = arg1; - version = HS_VERSION_TWO; - } else if (strcmpstart(arg1, v2_str) == 0 && - rend_valid_descriptor_id(arg1 + v2_str_len) && - base32_decode(digest, sizeof(digest), arg1 + v2_str_len, - REND_DESC_ID_V2_LEN_BASE32) == - REND_DESC_ID_V2_LEN_BASE32) { - /* We have a well formed version 2 descriptor ID. Keep the decoded value - * of the id. */ - desc_id = digest; - version = HS_VERSION_TWO; - } else if (hs_address_is_valid(arg1)) { - hsaddress = arg1; - version = HS_VERSION_THREE; - hs_parse_address(hsaddress, &v3_pk, NULL, NULL); - } else { - connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n", - arg1); - goto done; - } - - static const char *opt_server = "SERVER="; - - /* Skip first argument because it's the HSAddress or DescID. */ - for (i = 1; i < smartlist_len(args); ++i) { - const char *arg = smartlist_get(args, i); - const node_t *node; - - if (!strcasecmpstart(arg, opt_server)) { - const char *server; - - server = arg + strlen(opt_server); - node = node_get_by_hex_id(server, 0); - if (!node) { - connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", - server); - goto done; - } - if (!hsdirs) { - /* Stores routerstatus_t object for each specified server. */ - hsdirs = smartlist_new(); - } - /* Valid server, add it to our local list. */ - smartlist_add(hsdirs, node->rs); - } else { - connection_printf_to_buf(conn, "513 Unexpected argument \"%s\"\r\n", - arg); - goto done; - } - } - - if (version == HS_VERSION_TWO) { - rend_query = rend_data_client_create(hsaddress, desc_id, NULL, - REND_NO_AUTH); - if (rend_query == NULL) { - connection_printf_to_buf(conn, "551 Error creating the HS query\r\n"); - goto done; - } - } - - /* Using a descriptor ID, we force the user to provide at least one - * hsdir server using the SERVER= option. */ - if (desc_id && (!hsdirs || !smartlist_len(hsdirs))) { - connection_printf_to_buf(conn, "512 %s option is required\r\n", - opt_server); - goto done; - } - - /* We are about to trigger HSDir fetch so send the OK now because after - * that 650 event(s) are possible so better to have the 250 OK before them - * to avoid out of order replies. */ - send_control_done(conn); - - /* Trigger the fetch using the built rend query and possibly a list of HS - * directory to use. This function ignores the client cache thus this will - * always send a fetch command. */ - if (version == HS_VERSION_TWO) { - rend_client_fetch_v2_desc(rend_query, hsdirs); - } else if (version == HS_VERSION_THREE) { - hs_control_hsfetch_command(&v3_pk, hsdirs); - } - - done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - /* Contains data pointer that we don't own thus no cleanup. */ - smartlist_free(hsdirs); - rend_data_free(rend_query); - exit: - return 0; -} - -/** Implementation for the HSPOST command. */ -static int -handle_control_hspost(control_connection_t *conn, - uint32_t len, - const char *body) -{ - static const char *opt_server = "SERVER="; - static const char *opt_hsaddress = "HSADDRESS="; - smartlist_t *hs_dirs = NULL; - const char *encoded_desc = body; - size_t encoded_desc_len = len; - const char *onion_address = NULL; - - char *cp = memchr(body, '\n', len); - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; - } - char *argline = tor_strndup(body, cp-body); - - smartlist_t *args = smartlist_new(); - - /* If any SERVER= or HSADDRESS= options were specified, try to parse - * the options line. */ - if (!strcasecmpstart(argline, opt_server) || - !strcasecmpstart(argline, opt_hsaddress)) { - /* encoded_desc begins after a newline character */ - cp = cp + 1; - encoded_desc = cp; - encoded_desc_len = len-(cp-body); - - smartlist_split_string(args, argline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { - if (!strcasecmpstart(arg, opt_server)) { - const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server, 0); - - if (!node || !node->rs) { - connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", - server); - goto done; - } - /* Valid server, add it to our local list. */ - if (!hs_dirs) - hs_dirs = smartlist_new(); - smartlist_add(hs_dirs, node->rs); - } else if (!strcasecmpstart(arg, opt_hsaddress)) { - const char *address = arg + strlen(opt_hsaddress); - if (!hs_address_is_valid(address)) { - connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); - goto done; - } - onion_address = address; - } else { - connection_printf_to_buf(conn, "512 Unexpected argument \"%s\"\r\n", - arg); - goto done; - } - } SMARTLIST_FOREACH_END(arg); - } - - /* Handle the v3 case. */ - if (onion_address) { - char *desc_str = NULL; - read_escaped_data(encoded_desc, encoded_desc_len, &desc_str); - if (hs_control_hspost_command(desc_str, onion_address, hs_dirs) < 0) { - connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); - } else { - send_control_done(conn); - } - tor_free(desc_str); - goto done; - } - - /* From this point on, it is only v2. */ - - /* Read the dot encoded descriptor, and parse it. */ - rend_encoded_v2_service_descriptor_t *desc = - tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); - read_escaped_data(encoded_desc, encoded_desc_len, &desc->desc_str); - - rend_service_descriptor_t *parsed = NULL; - char *intro_content = NULL; - size_t intro_size; - size_t encoded_size; - const char *next_desc; - if (!rend_parse_v2_service_descriptor(&parsed, desc->desc_id, &intro_content, - &intro_size, &encoded_size, - &next_desc, desc->desc_str, 1)) { - /* Post the descriptor. */ - char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - if (!rend_get_service_id(parsed->pk, serviceid)) { - smartlist_t *descs = smartlist_new(); - smartlist_add(descs, desc); - - /* We are about to trigger HS descriptor upload so send the OK now - * because after that 650 event(s) are possible so better to have the - * 250 OK before them to avoid out of order replies. */ - send_control_done(conn); - - /* Trigger the descriptor upload */ - directory_post_to_hs_dir(parsed, descs, hs_dirs, serviceid, 0); - smartlist_free(descs); - } - - rend_service_descriptor_free(parsed); - } else { - connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); - } - - tor_free(intro_content); - rend_encoded_v2_service_descriptor_free(desc); - done: - tor_free(argline); - smartlist_free(hs_dirs); /* Contents belong to the rend service code. */ - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - return 0; -} - -/* Helper function for ADD_ONION that adds an ephemeral service depending on - * the given hs_version. - * - * The secret key in pk depends on the hs_version. The ownership of the key - * used in pk is given to the HS subsystem so the caller must stop accessing - * it after. - * - * The port_cfgs is a list of service port. Ownership transferred to service. - * The max_streams refers to the MaxStreams= key. - * The max_streams_close_circuit refers to the MaxStreamsCloseCircuit key. - * The auth_type is the authentication type of the clients in auth_clients. - * The ownership of that list is transferred to the service. - * - * On success (RSAE_OKAY), the address_out points to a newly allocated string - * containing the onion address without the .onion part. On error, address_out - * is untouched. */ -static hs_service_add_ephemeral_status_t -add_onion_helper_add_service(int hs_version, - add_onion_secret_key_t *pk, - smartlist_t *port_cfgs, int max_streams, - int max_streams_close_circuit, int auth_type, - smartlist_t *auth_clients, char **address_out) -{ - hs_service_add_ephemeral_status_t ret; - - tor_assert(pk); - tor_assert(port_cfgs); - tor_assert(address_out); - - switch (hs_version) { - case HS_VERSION_TWO: - ret = rend_service_add_ephemeral(pk->v2, port_cfgs, max_streams, - max_streams_close_circuit, auth_type, - auth_clients, address_out); - break; - case HS_VERSION_THREE: - ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, - max_streams_close_circuit, address_out); - break; - default: - tor_assert_unreached(); - } - - 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 -handle_control_add_onion(control_connection_t *conn, - uint32_t len, - const char *body) -{ - smartlist_t *args; - int arg_len; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("ADD_ONION", conn, body, 2, -1); - if (!args) - return 0; - arg_len = smartlist_len(args); - - /* Parse all of the arguments that do not involve handling cryptographic - * material first, since there's no reason to touch that at all if any of - * the other arguments are malformed. - */ - smartlist_t *port_cfgs = smartlist_new(); - smartlist_t *auth_clients = NULL; - smartlist_t *auth_created_clients = NULL; - int discard_pk = 0; - int detach = 0; - int max_streams = 0; - int max_streams_close_circuit = 0; - rend_auth_type_t auth_type = REND_NO_AUTH; - /* Default to adding an anonymous hidden service if no flag is given */ - int non_anonymous = 0; - for (int i = 1; i < arg_len; i++) { - static const char *port_prefix = "Port="; - static const char *flags_prefix = "Flags="; - static const char *max_s_prefix = "MaxStreams="; - static const char *auth_prefix = "ClientAuth="; - - const char *arg = smartlist_get(args, (int)i); - if (!strcasecmpstart(arg, port_prefix)) { - /* "Port=VIRTPORT[,TARGET]". */ - const char *port_str = arg + strlen(port_prefix); - - rend_service_port_config_t *cfg = - rend_service_parse_port_config(port_str, ",", NULL); - if (!cfg) { - connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); - goto out; - } - smartlist_add(port_cfgs, cfg); - } else if (!strcasecmpstart(arg, max_s_prefix)) { - /* "MaxStreams=[0..65535]". */ - const char *max_s_str = arg + strlen(max_s_prefix); - int ok = 0; - max_streams = (int)tor_parse_long(max_s_str, 10, 0, 65535, &ok, NULL); - if (!ok) { - connection_printf_to_buf(conn, "512 Invalid MaxStreams\r\n"); - goto out; - } - } else if (!strcasecmpstart(arg, flags_prefix)) { - /* "Flags=Flag[,Flag]", where Flag can be: - * * 'DiscardPK' - If tor generates the keypair, do not include it in - * the response. - * * 'Detach' - Do not tie this onion service to any particular control - * connection. - * * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is - * exceeded. - * * 'BasicAuth' - Client authorization using the 'basic' method. - * * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this - * flag is present, tor must be in non-anonymous - * hidden service mode. If this flag is absent, - * tor must be in anonymous hidden service mode. - */ - static const char *discard_flag = "DiscardPK"; - static const char *detach_flag = "Detach"; - static const char *max_s_close_flag = "MaxStreamsCloseCircuit"; - static const char *basicauth_flag = "BasicAuth"; - static const char *non_anonymous_flag = "NonAnonymous"; - - smartlist_t *flags = smartlist_new(); - int bad = 0; - - smartlist_split_string(flags, arg + strlen(flags_prefix), ",", - SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(flags) < 1) { - connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); - bad = 1; - } - SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) - { - if (!strcasecmp(flag, discard_flag)) { - discard_pk = 1; - } else if (!strcasecmp(flag, detach_flag)) { - detach = 1; - } else if (!strcasecmp(flag, max_s_close_flag)) { - max_streams_close_circuit = 1; - } else if (!strcasecmp(flag, basicauth_flag)) { - auth_type = REND_BASIC_AUTH; - } else if (!strcasecmp(flag, non_anonymous_flag)) { - non_anonymous = 1; - } else { - connection_printf_to_buf(conn, - "512 Invalid 'Flags' argument: %s\r\n", - escaped(flag)); - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(flag); - SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); - smartlist_free(flags); - if (bad) - goto out; - } else if (!strcasecmpstart(arg, auth_prefix)) { - char *err_msg = NULL; - int created = 0; - rend_authorized_client_t *client = - add_onion_helper_clientauth(arg + strlen(auth_prefix), - &created, &err_msg); - if (!client) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } - goto out; - } - - if (auth_clients != NULL) { - int bad = 0; - SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) { - if (strcmp(ac->client_name, client->client_name) == 0) { - bad = 1; - break; - } - } SMARTLIST_FOREACH_END(ac); - if (bad) { - connection_printf_to_buf(conn, - "512 Duplicate name in ClientAuth\r\n"); - rend_authorized_client_free(client); - goto out; - } - } else { - auth_clients = smartlist_new(); - auth_created_clients = smartlist_new(); - } - smartlist_add(auth_clients, client); - if (created) { - smartlist_add(auth_created_clients, client); - } - } else { - connection_printf_to_buf(conn, "513 Invalid argument\r\n"); - goto out; - } - } - if (smartlist_len(port_cfgs) == 0) { - connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n"); - goto out; - } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) { - connection_printf_to_buf(conn, "512 No auth type specified\r\n"); - goto out; - } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) { - connection_printf_to_buf(conn, "512 No auth clients specified\r\n"); - goto out; - } else if ((auth_type == REND_BASIC_AUTH && - smartlist_len(auth_clients) > 512) || - (auth_type == REND_STEALTH_AUTH && - smartlist_len(auth_clients) > 16)) { - connection_printf_to_buf(conn, "512 Too many auth clients\r\n"); - goto out; - } else if (non_anonymous != rend_service_non_anonymous_mode_enabled( - get_options())) { - /* If we failed, and the non-anonymous flag is set, Tor must be in - * anonymous hidden service mode. - * The error message changes based on the current Tor config: - * 512 Tor is in anonymous hidden service mode - * 512 Tor is in non-anonymous hidden service mode - * (I've deliberately written them out in full here to aid searchability.) - */ - connection_printf_to_buf(conn, "512 Tor is in %sanonymous hidden service " - "mode\r\n", - non_anonymous ? "" : "non-"); - goto out; - } - - /* Parse the "keytype:keyblob" argument. */ - int hs_version = 0; - add_onion_secret_key_t pk = { NULL }; - const char *key_new_alg = NULL; - char *key_new_blob = NULL; - char *err_msg = NULL; - - if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, - &key_new_alg, &key_new_blob, &pk, &hs_version, - &err_msg) < 0) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } - goto out; - } - tor_assert(!err_msg); - - /* Hidden service version 3 don't have client authentication support so if - * ClientAuth was given, send back an error. */ - if (hs_version == HS_VERSION_THREE && auth_clients) { - connection_printf_to_buf(conn, "513 ClientAuth not supported\r\n"); - goto out; - } - - /* Create the HS, using private key pk, client authentication auth_type, - * the list of auth_clients, and port config port_cfg. - * rend_service_add_ephemeral() will take ownership of pk and port_cfg, - * regardless of success/failure. - */ - char *service_id = NULL; - int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, - max_streams, - max_streams_close_circuit, auth_type, - auth_clients, &service_id); - port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ - auth_clients = NULL; /* so is auth_clients */ - switch (ret) { - case RSAE_OKAY: - { - if (detach) { - if (!detached_onion_services) - detached_onion_services = smartlist_new(); - smartlist_add(detached_onion_services, service_id); - } else { - if (!conn->ephemeral_onion_services) - conn->ephemeral_onion_services = smartlist_new(); - smartlist_add(conn->ephemeral_onion_services, service_id); - } - - tor_assert(service_id); - connection_printf_to_buf(conn, "250-ServiceID=%s\r\n", service_id); - if (key_new_alg) { - tor_assert(key_new_blob); - connection_printf_to_buf(conn, "250-PrivateKey=%s:%s\r\n", - key_new_alg, key_new_blob); - } - if (auth_created_clients) { - SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, { - char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie, - auth_type); - tor_assert(encoded); - connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n", - ac->client_name, encoded); - memwipe(encoded, 0, strlen(encoded)); - tor_free(encoded); - }); - } - - connection_printf_to_buf(conn, "250 OK\r\n"); - break; - } - case RSAE_BADPRIVKEY: - connection_printf_to_buf(conn, "551 Failed to generate onion address\r\n"); - break; - case RSAE_ADDREXISTS: - connection_printf_to_buf(conn, "550 Onion address collision\r\n"); - break; - case RSAE_BADVIRTPORT: - connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); - break; - case RSAE_BADAUTH: - connection_printf_to_buf(conn, "512 Invalid client authorization\r\n"); - break; - case RSAE_INTERNAL: /* FALLSTHROUGH */ - default: - connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n"); - } - if (key_new_blob) { - memwipe(key_new_blob, 0, strlen(key_new_blob)); - tor_free(key_new_blob); - } - - out: - if (port_cfgs) { - SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, - rend_service_port_config_free(p)); - smartlist_free(port_cfgs); - } - - if (auth_clients) { - SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac, - rend_authorized_client_free(ac)); - smartlist_free(auth_clients); - } - if (auth_created_clients) { - // Do not free entries; they are the same as auth_clients - smartlist_free(auth_created_clients); - } - - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); - return 0; -} - -/** Helper function to handle parsing the KeyType:KeyBlob argument to the - * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated - * and the private key not discarded, the algorithm and serialized private key, - * or NULL and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned key_new_blob and err_msg. - * - * Note: The error messages returned are deliberately vague to avoid echoing - * key material. - */ -STATIC int -add_onion_helper_keyarg(const char *arg, int discard_pk, - const char **key_new_alg_out, char **key_new_blob_out, - add_onion_secret_key_t *decoded_key, int *hs_version, - char **err_msg_out) -{ - smartlist_t *key_args = smartlist_new(); - crypto_pk_t *pk = NULL; - const char *key_new_alg = NULL; - char *key_new_blob = NULL; - char *err_msg = NULL; - int ret = -1; - - smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(key_args) != 2) { - err_msg = tor_strdup("512 Invalid key type/blob\r\n"); - goto err; - } - - /* The format is "KeyType:KeyBlob". */ - static const char *key_type_new = "NEW"; - static const char *key_type_best = "BEST"; - static const char *key_type_rsa1024 = "RSA1024"; - static const char *key_type_ed25519_v3 = "ED25519-V3"; - - const char *key_type = smartlist_get(key_args, 0); - const char *key_blob = smartlist_get(key_args, 1); - - if (!strcasecmp(key_type_rsa1024, key_type)) { - /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ - pk = crypto_pk_base64_decode_private(key_blob, strlen(key_blob)); - if (!pk) { - err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); - goto err; - } - if (crypto_pk_num_bits(pk) != PK_BYTES*8) { - crypto_pk_free(pk); - err_msg = tor_strdup("512 Invalid RSA key size\r\n"); - goto err; - } - decoded_key->v2 = pk; - *hs_version = HS_VERSION_TWO; - } else if (!strcasecmp(key_type_ed25519_v3, key_type)) { - /* "ED25519-V3:<Base64 Blob>" - Loading a pre-existing ed25519 key. */ - ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); - if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, - strlen(key_blob)) != sizeof(sk->seckey)) { - tor_free(sk); - err_msg = tor_strdup("512 Failed to decode ED25519-V3 key\r\n"); - goto err; - } - decoded_key->v3 = sk; - *hs_version = HS_VERSION_THREE; - } else if (!strcasecmp(key_type_new, key_type)) { - /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ - if (!strcasecmp(key_type_rsa1024, key_blob) || - !strcasecmp(key_type_best, key_blob)) { - /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ - pk = crypto_pk_new(); - if (crypto_pk_generate_key(pk)) { - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_rsa1024); - goto err; - } - if (!discard_pk) { - if (crypto_pk_base64_encode_private(pk, &key_new_blob)) { - crypto_pk_free(pk); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_rsa1024); - goto err; - } - key_new_alg = key_type_rsa1024; - } - decoded_key->v2 = pk; - *hs_version = HS_VERSION_TWO; - } else if (!strcasecmp(key_type_ed25519_v3, key_blob)) { - ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); - if (ed25519_secret_key_generate(sk, 1) < 0) { - tor_free(sk); - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_ed25519_v3); - goto err; - } - if (!discard_pk) { - ssize_t len = base64_encode_size(sizeof(sk->seckey), 0) + 1; - key_new_blob = tor_malloc_zero(len); - if (base64_encode(key_new_blob, len, (const char *) sk->seckey, - sizeof(sk->seckey), 0) != (len - 1)) { - tor_free(sk); - tor_free(key_new_blob); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_ed25519_v3); - goto err; - } - key_new_alg = key_type_ed25519_v3; - } - decoded_key->v3 = sk; - *hs_version = HS_VERSION_THREE; - } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); - goto err; - } - } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); - goto err; - } - - /* Succeeded in loading or generating a private key. */ - ret = 0; - - err: - SMARTLIST_FOREACH(key_args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(key_args); - - if (err_msg_out) { - *err_msg_out = err_msg; - } else { - tor_free(err_msg); - } - *key_new_alg_out = key_new_alg; - *key_new_blob_out = key_new_blob; - - return ret; -} - -/** Helper function to handle parsing a ClientAuth argument to the - * ADD_ONION command. Return a new rend_authorized_client_t, or NULL - * and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned auth_client and err_msg. - * - * If 'created' is specified, it will be set to 1 when a new cookie has - * been generated. - */ -STATIC rend_authorized_client_t * -add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) -{ - int ok = 0; - - tor_assert(arg); - tor_assert(created); - tor_assert(err_msg); - *err_msg = NULL; - - smartlist_t *auth_args = smartlist_new(); - rend_authorized_client_t *client = - tor_malloc_zero(sizeof(rend_authorized_client_t)); - smartlist_split_string(auth_args, arg, ":", 0, 0); - if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) { - *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n"); - goto err; - } - client->client_name = tor_strdup(smartlist_get(auth_args, 0)); - if (smartlist_len(auth_args) == 2) { - char *decode_err_msg = NULL; - if (rend_auth_decode_cookie(smartlist_get(auth_args, 1), - client->descriptor_cookie, - NULL, &decode_err_msg) < 0) { - tor_assert(decode_err_msg); - tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg); - tor_free(decode_err_msg); - goto err; - } - *created = 0; - } else { - crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN); - *created = 1; - } - - if (!rend_valid_client_name(client->client_name)) { - *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n"); - goto err; - } - - ok = 1; - err: - SMARTLIST_FOREACH(auth_args, char *, item, tor_free(item)); - smartlist_free(auth_args); - if (!ok) { - rend_authorized_client_free(client); - client = NULL; - } - return client; -} - -/** Called when we get a DEL_ONION command; parse the body, and remove - * the existing ephemeral Onion Service. */ -static int -handle_control_del_onion(control_connection_t *conn, - uint32_t len, - const char *body) -{ - int hs_version = 0; - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("DEL_ONION", conn, body, 1, 1); - if (!args) - return 0; - - const char *service_id = smartlist_get(args, 0); - if (rend_valid_v2_service_id(service_id)) { - hs_version = HS_VERSION_TWO; - } else if (hs_address_is_valid(service_id)) { - hs_version = HS_VERSION_THREE; - } else { - connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); - goto out; - } - - /* Determine if the onion service belongs to this particular control - * connection, or if it is in the global list of detached services. If it - * is in neither, either the service ID is invalid in some way, or it - * explicitly belongs to a different control connection, and an error - * should be returned. - */ - smartlist_t *services[2] = { - conn->ephemeral_onion_services, - detached_onion_services - }; - smartlist_t *onion_services = NULL; - int idx = -1; - for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { - idx = smartlist_string_pos(services[i], service_id); - if (idx != -1) { - onion_services = services[i]; - break; - } - } - if (onion_services == NULL) { - connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); - } else { - int ret = -1; - switch (hs_version) { - case HS_VERSION_TWO: - ret = rend_service_del_ephemeral(service_id); - break; - case HS_VERSION_THREE: - ret = hs_service_del_ephemeral(service_id); - break; - default: - /* The ret value will be -1 thus hitting the warning below. This should - * never happen because of the check at the start of the function. */ - break; - } - if (ret < 0) { - /* This should *NEVER* fail, since the service is on either the - * per-control connection list, or the global one. - */ - log_warn(LD_BUG, "Failed to remove Onion Service %s.", - escaped(service_id)); - tor_fragile_assert(); - } - - /* Remove/scrub the service_id from the appropriate list. */ - char *cp = smartlist_get(onion_services, idx); - smartlist_del(onion_services, idx); - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - - send_control_done(conn); - } - - out: - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); - return 0; -} - /** Called when <b>conn</b> has no more bytes left on its outbuf. */ int connection_control_finished_flushing(control_connection_t *conn) @@ -3185,108 +517,14 @@ connection_control_process_inbuf(control_connection_t *conn) return 0; } - /* XXXX Why is this not implemented as a table like the GETINFO - * items are? Even handling the plus signs at the beginnings of - * commands wouldn't be very hard with proper macros. */ cmd_data_len = (uint32_t)data_len; - if (!strcasecmp(conn->incoming_cmd, "SETCONF")) { - if (handle_control_setconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESETCONF")) { - if (handle_control_resetconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETCONF")) { - if (handle_control_getconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+LOADCONF")) { - if (handle_control_loadconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETEVENTS")) { - if (handle_control_setevents(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHENTICATE")) { - if (handle_control_authenticate(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SAVECONF")) { - if (handle_control_saveconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SIGNAL")) { - if (handle_control_signal(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "TAKEOWNERSHIP")) { - if (handle_control_takeownership(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DROPOWNERSHIP")) { - if (handle_control_dropownership(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "MAPADDRESS")) { - if (handle_control_mapaddress(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETINFO")) { - if (handle_control_getinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "EXTENDCIRCUIT")) { - if (handle_control_extendcircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETCIRCUITPURPOSE")) { - if (handle_control_setcircuitpurpose(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETROUTERPURPOSE")) { - connection_write_str_to_buf("511 SETROUTERPURPOSE is obsolete.\r\n", conn); - } else if (!strcasecmp(conn->incoming_cmd, "ATTACHSTREAM")) { - if (handle_control_attachstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+POSTDESCRIPTOR")) { - if (handle_control_postdescriptor(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "REDIRECTSTREAM")) { - if (handle_control_redirectstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSESTREAM")) { - if (handle_control_closestream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSECIRCUIT")) { - if (handle_control_closecircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "USEFEATURE")) { - if (handle_control_usefeature(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESOLVE")) { - if (handle_control_resolve(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "PROTOCOLINFO")) { - if (handle_control_protocolinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { - if (handle_control_authchallenge(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { - if (handle_control_dropguards(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { - if (handle_control_hsfetch(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+HSPOST")) { - if (handle_control_hspost(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { - int ret = handle_control_add_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ - if (ret) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { - int ret = handle_control_del_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ - if (ret) - return -1; - } else { - connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", - conn->incoming_cmd); - } + if (handle_control_command(conn, cmd_data_len, args) < 0) + return -1; conn->incoming_cmd_cur_len = 0; goto again; } + /** Cached liveness for network liveness events and GETINFO */ @@ -3304,43 +542,6 @@ set_cached_network_liveness(int liveness) network_is_live = liveness; } -/** Helper: Return a newly allocated string containing a path to the - * file where we store our authentication cookie. */ -char * -get_controller_cookie_file_name(void) -{ - const or_options_t *options = get_options(); - if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { - return tor_strdup(options->CookieAuthFile); - } else { - return get_datadir_fname("control_auth_cookie"); - } -} - -/* Initialize the cookie-based authentication system of the - * ControlPort. If <b>enabled</b> is 0, then disable the cookie - * authentication system. */ -int -init_control_cookie_authentication(int enabled) -{ - char *fname = NULL; - int retval; - - if (!enabled) { - authentication_cookie_is_set = 0; - return 0; - } - - fname = get_controller_cookie_file_name(); - retval = init_cookie_authentication(fname, "", /* no header */ - AUTHENTICATION_COOKIE_LEN, - get_options()->CookieAuthFileGroupReadable, - &authentication_cookie, - &authentication_cookie_is_set); - tor_free(fname); - return retval; -} - /** A copy of the process specifier of Tor's owning controller, or * NULL if this Tor instance is not currently owned by a process. */ static char *owning_controller_process_spec = NULL; @@ -3428,14 +629,8 @@ node_describe_longname_by_id,(const char *id_digest)) void control_free_all(void) { + control_auth_free_all(); control_events_free_all(); - - if (authentication_cookie) /* Free the auth cookie */ - tor_free(authentication_cookie); - if (detached_onion_services) { /* Free the detached onion services */ - SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); - smartlist_free(detached_onion_services); - } + control_cmd_free_all(); control_event_bootstrap_reset(); - authentication_cookie_is_set = 0; } diff --git a/src/feature/control/control.h b/src/feature/control/control.h index 810fe67843..d20bc6fa2b 100644 --- a/src/feature/control/control.h +++ b/src/feature/control/control.h @@ -42,10 +42,6 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -int init_control_cookie_authentication(int enabled); -char *get_controller_cookie_file_name(void); -struct config_line_t; -smartlist_t *decode_hashed_passwords(struct config_line_t *passwords); void disable_control_logging(void); void enable_control_logging(void); @@ -55,40 +51,14 @@ const char *rend_auth_type_to_string(rend_auth_type_t auth_type); MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); void control_free_all(void); -#ifdef CONTROL_PRIVATE -#include "lib/crypt_ops/crypto_ed25519.h" - -/* ADD_ONION secret key to create an ephemeral service. The command supports - * multiple versions so this union stores the key and passes it to the HS - * subsystem depending on the requested version. */ -typedef union add_onion_secret_key_t { - /* Hidden service v2 secret key. */ - crypto_pk_t *v2; - /* Hidden service v3 secret key. */ - ed25519_secret_key_t *v3; -} add_onion_secret_key_t; - -STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, - const char **key_new_alg_out, - char **key_new_blob_out, - add_onion_secret_key_t *decoded_key, - int *hs_version, char **err_msg_out); - -STATIC rend_authorized_client_t * -add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out); - -#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_auth.c b/src/feature/control/control_auth.c new file mode 100644 index 0000000000..927115a308 --- /dev/null +++ b/src/feature/control/control_auth.c @@ -0,0 +1,439 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_auth.c + * \brief Authentication for Tor's control-socket interface. + **/ + +#include "core/or/or.h" +#include "app/config/config.h" +#include "core/mainloop/connection.h" +#include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_connection_st.h" +#include "feature/control/control_fmt.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.h" + +#include "lib/crypt_ops/crypto_s2k.h" + +/** If we're using cookie-type authentication, how long should our cookies be? + */ +#define AUTHENTICATION_COOKIE_LEN 32 + +/** If true, we've set authentication_cookie to a secret code and + * stored it to disk. */ +static int authentication_cookie_is_set = 0; +/** If authentication_cookie_is_set, a secret cookie that we've stored to disk + * and which we're using to authenticate controllers. (If the controller can + * read it off disk, it has permission to connect.) */ +static uint8_t *authentication_cookie = NULL; + +#define SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT \ + "Tor safe cookie authentication server-to-controller hash" +#define SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT \ + "Tor safe cookie authentication controller-to-server hash" +#define SAFECOOKIE_SERVER_NONCE_LEN DIGEST256_LEN + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_controller_cookie_file_name(void) +{ + const or_options_t *options = get_options(); + if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { + return tor_strdup(options->CookieAuthFile); + } else { + return get_datadir_fname("control_auth_cookie"); + } +} + +/* Initialize the cookie-based authentication system of the + * ControlPort. If <b>enabled</b> is 0, then disable the cookie + * authentication system. */ +int +init_control_cookie_authentication(int enabled) +{ + char *fname = NULL; + int retval; + + if (!enabled) { + authentication_cookie_is_set = 0; + return 0; + } + + fname = get_controller_cookie_file_name(); + retval = init_cookie_authentication(fname, "", /* no header */ + AUTHENTICATION_COOKIE_LEN, + get_options()->CookieAuthFileGroupReadable, + &authentication_cookie, + &authentication_cookie_is_set); + tor_free(fname); + return retval; +} + +/** Decode the hashed, base64'd passwords stored in <b>passwords</b>. + * Return a smartlist of acceptable passwords (unterminated strings of + * length S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) on success, or NULL on + * failure. + */ +smartlist_t * +decode_hashed_passwords(config_line_t *passwords) +{ + char decoded[64]; + config_line_t *cl; + smartlist_t *sl = smartlist_new(); + + tor_assert(passwords); + + for (cl = passwords; cl; cl = cl->next) { + const char *hashed = cl->value; + + if (!strcmpstart(hashed, "16:")) { + if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3)) + != S2K_RFC2440_SPECIFIER_LEN + DIGEST_LEN + || strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) { + goto err; + } + } else { + if (base64_decode(decoded, sizeof(decoded), hashed, strlen(hashed)) + != S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN) { + goto err; + } + } + smartlist_add(sl, + tor_memdup(decoded, S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)); + } + + return sl; + + err: + SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp)); + smartlist_free(sl); + return NULL; +} + +/** Called when we get an AUTHCHALLENGE command. */ +int +handle_control_authchallenge(control_connection_t *conn, uint32_t len, + const char *body) +{ + const char *cp = body; + char *client_nonce; + size_t client_nonce_len; + char server_hash[DIGEST256_LEN]; + char server_hash_encoded[HEX_DIGEST256_LEN+1]; + char server_nonce[SAFECOOKIE_SERVER_NONCE_LEN]; + char server_nonce_encoded[(2*SAFECOOKIE_SERVER_NONCE_LEN) + 1]; + + cp += strspn(cp, " \t\n\r"); + if (!strcasecmpstart(cp, "SAFECOOKIE")) { + cp += strlen("SAFECOOKIE"); + } else { + connection_write_str_to_buf("513 AUTHCHALLENGE only supports SAFECOOKIE " + "authentication\r\n", conn); + connection_mark_for_close(TO_CONN(conn)); + return -1; + } + + if (!authentication_cookie_is_set) { + connection_write_str_to_buf("515 Cookie authentication is disabled\r\n", + conn); + connection_mark_for_close(TO_CONN(conn)); + return -1; + } + + cp += strspn(cp, " \t\n\r"); + if (*cp == '"') { + const char *newcp = + decode_escaped_string(cp, len - (cp - body), + &client_nonce, &client_nonce_len); + if (newcp == NULL) { + connection_write_str_to_buf("513 Invalid quoted client nonce\r\n", + conn); + connection_mark_for_close(TO_CONN(conn)); + return -1; + } + cp = newcp; + } else { + size_t client_nonce_encoded_len = strspn(cp, "0123456789ABCDEFabcdef"); + + client_nonce_len = client_nonce_encoded_len / 2; + client_nonce = tor_malloc_zero(client_nonce_len); + + if (base16_decode(client_nonce, client_nonce_len, + cp, client_nonce_encoded_len) + != (int) client_nonce_len) { + connection_write_str_to_buf("513 Invalid base16 client nonce\r\n", + conn); + connection_mark_for_close(TO_CONN(conn)); + tor_free(client_nonce); + return -1; + } + + cp += client_nonce_encoded_len; + } + + cp += strspn(cp, " \t\n\r"); + if (*cp != '\0' || + cp != body + len) { + connection_write_str_to_buf("513 Junk at end of AUTHCHALLENGE command\r\n", + conn); + connection_mark_for_close(TO_CONN(conn)); + tor_free(client_nonce); + return -1; + } + crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); + + /* Now compute and send the server-to-controller response, and the + * server's nonce. */ + tor_assert(authentication_cookie != NULL); + + { + size_t tmp_len = (AUTHENTICATION_COOKIE_LEN + + client_nonce_len + + SAFECOOKIE_SERVER_NONCE_LEN); + char *tmp = tor_malloc_zero(tmp_len); + char *client_hash = tor_malloc_zero(DIGEST256_LEN); + memcpy(tmp, authentication_cookie, AUTHENTICATION_COOKIE_LEN); + memcpy(tmp + AUTHENTICATION_COOKIE_LEN, client_nonce, client_nonce_len); + memcpy(tmp + AUTHENTICATION_COOKIE_LEN + client_nonce_len, + server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT, + strlen(SAFECOOKIE_SERVER_TO_CONTROLLER_CONSTANT), + tmp, + tmp_len); + + crypto_hmac_sha256(client_hash, + SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT, + strlen(SAFECOOKIE_CONTROLLER_TO_SERVER_CONSTANT), + tmp, + tmp_len); + + conn->safecookie_client_hash = client_hash; + + tor_free(tmp); + } + + base16_encode(server_hash_encoded, sizeof(server_hash_encoded), + server_hash, sizeof(server_hash)); + base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), + server_nonce, sizeof(server_nonce)); + + connection_printf_to_buf(conn, + "250 AUTHCHALLENGE SERVERHASH=%s " + "SERVERNONCE=%s\r\n", + server_hash_encoded, + server_nonce_encoded); + + tor_free(client_nonce); + return 0; +} + +/** Called when we get an AUTHENTICATE message. Check whether the + * authentication is valid, and if so, update the connection's state to + * OPEN. Reply with DONE or ERROR. + */ +int +handle_control_authenticate(control_connection_t *conn, uint32_t len, + const char *body) +{ + int used_quoted_string = 0; + const or_options_t *options = get_options(); + const char *errstr = "Unknown error"; + char *password; + size_t password_len; + const char *cp; + int i; + int bad_cookie=0, bad_password=0; + smartlist_t *sl = NULL; + + if (!len) { + password = tor_strdup(""); + password_len = 0; + } else if (TOR_ISXDIGIT(body[0])) { + cp = body; + while (TOR_ISXDIGIT(*cp)) + ++cp; + i = (int)(cp - body); + tor_assert(i>0); + password_len = i/2; + password = tor_malloc(password_len + 1); + if (base16_decode(password, password_len+1, body, i) + != (int) password_len) { + connection_write_str_to_buf( + "551 Invalid hexadecimal encoding. Maybe you tried a plain text " + "password? If so, the standard requires that you put it in " + "double quotes.\r\n", conn); + connection_mark_for_close(TO_CONN(conn)); + tor_free(password); + return 0; + } + } else { + if (!decode_escaped_string(body, len, &password, &password_len)) { + connection_write_str_to_buf("551 Invalid quoted string. You need " + "to put the password in double quotes.\r\n", conn); + connection_mark_for_close(TO_CONN(conn)); + return 0; + } + used_quoted_string = 1; + } + + if (conn->safecookie_client_hash != NULL) { + /* The controller has chosen safe cookie authentication; the only + * acceptable authentication value is the controller-to-server + * response. */ + + tor_assert(authentication_cookie_is_set); + + if (password_len != DIGEST256_LEN) { + log_warn(LD_CONTROL, + "Got safe cookie authentication response with wrong length " + "(%d)", (int)password_len); + errstr = "Wrong length for safe cookie response."; + goto err; + } + + if (tor_memneq(conn->safecookie_client_hash, password, DIGEST256_LEN)) { + log_warn(LD_CONTROL, + "Got incorrect safe cookie authentication response"); + errstr = "Safe cookie response did not match expected value."; + goto err; + } + + tor_free(conn->safecookie_client_hash); + goto ok; + } + + if (!options->CookieAuthentication && !options->HashedControlPassword && + !options->HashedControlSessionPassword) { + /* if Tor doesn't demand any stronger authentication, then + * the controller can get in with anything. */ + goto ok; + } + + if (options->CookieAuthentication) { + int also_password = options->HashedControlPassword != NULL || + options->HashedControlSessionPassword != NULL; + if (password_len != AUTHENTICATION_COOKIE_LEN) { + if (!also_password) { + log_warn(LD_CONTROL, "Got authentication cookie with wrong length " + "(%d)", (int)password_len); + errstr = "Wrong length on authentication cookie."; + goto err; + } + bad_cookie = 1; + } else if (tor_memneq(authentication_cookie, password, password_len)) { + if (!also_password) { + log_warn(LD_CONTROL, "Got mismatched authentication cookie"); + errstr = "Authentication cookie did not match expected value."; + goto err; + } + bad_cookie = 1; + } else { + goto ok; + } + } + + if (options->HashedControlPassword || + options->HashedControlSessionPassword) { + int bad = 0; + smartlist_t *sl_tmp; + char received[DIGEST_LEN]; + int also_cookie = options->CookieAuthentication; + sl = smartlist_new(); + if (options->HashedControlPassword) { + sl_tmp = decode_hashed_passwords(options->HashedControlPassword); + if (!sl_tmp) + bad = 1; + else { + smartlist_add_all(sl, sl_tmp); + smartlist_free(sl_tmp); + } + } + if (options->HashedControlSessionPassword) { + sl_tmp = decode_hashed_passwords(options->HashedControlSessionPassword); + if (!sl_tmp) + bad = 1; + else { + smartlist_add_all(sl, sl_tmp); + smartlist_free(sl_tmp); + } + } + if (bad) { + if (!also_cookie) { + log_warn(LD_BUG, + "Couldn't decode HashedControlPassword: invalid base16"); + errstr="Couldn't decode HashedControlPassword value in configuration."; + goto err; + } + bad_password = 1; + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + sl = NULL; + } else { + SMARTLIST_FOREACH(sl, char *, expected, + { + secret_to_key_rfc2440(received,DIGEST_LEN, + password,password_len,expected); + if (tor_memeq(expected + S2K_RFC2440_SPECIFIER_LEN, + received, DIGEST_LEN)) + goto ok; + }); + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + sl = NULL; + + if (used_quoted_string) + errstr = "Password did not match HashedControlPassword value from " + "configuration"; + else + errstr = "Password did not match HashedControlPassword value from " + "configuration. Maybe you tried a plain text password? " + "If so, the standard requires that you put it in double quotes."; + bad_password = 1; + if (!also_cookie) + goto err; + } + } + + /** We only get here if both kinds of authentication failed. */ + tor_assert(bad_password && bad_cookie); + log_warn(LD_CONTROL, "Bad password or authentication cookie on controller."); + errstr = "Password did not match HashedControlPassword *or* authentication " + "cookie."; + + err: + tor_free(password); + connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", errstr); + connection_mark_for_close(TO_CONN(conn)); + if (sl) { /* clean up */ + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + } + return 0; + ok: + log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT + ")", conn->base_.s); + send_control_done(conn); + conn->base_.state = CONTROL_CONN_STATE_OPEN; + tor_free(password); + if (sl) { /* clean up */ + SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); + smartlist_free(sl); + } + return 0; +} + +void +control_auth_free_all(void) +{ + if (authentication_cookie) /* Free the auth cookie */ + tor_free(authentication_cookie); + authentication_cookie_is_set = 0; +} diff --git a/src/feature/control/control_auth.h b/src/feature/control/control_auth.h new file mode 100644 index 0000000000..f436482e4a --- /dev/null +++ b/src/feature/control/control_auth.h @@ -0,0 +1,27 @@ +/* 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_auth.h + * \brief Header file for control_auth.c. + **/ + +#ifndef TOR_CONTROL_AUTH_H +#define TOR_CONTROL_AUTH_H + +int init_control_cookie_authentication(int enabled); +char *get_controller_cookie_file_name(void); +struct config_line_t; +smartlist_t *decode_hashed_passwords(struct config_line_t *passwords); + +int handle_control_authchallenge(control_connection_t *conn, uint32_t len, + const char *body); +int handle_control_authenticate(control_connection_t *conn, + uint32_t cmd_data_len, + const char *args); +void control_auth_free_all(void); + +#endif /* !defined(TOR_CONTROL_AUTH_H) */ diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c new file mode 100644 index 0000000000..95cf0d561e --- /dev/null +++ b/src/feature/control/control_cmd.c @@ -0,0 +1,2323 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_cmd.c + * \brief Implement various commands for Tor's control-socket interface. + **/ + +#define CONTROL_MODULE_PRIVATE +#define CONTROL_CMD_PRIVATE +#define CONTROL_EVENTS_PRIVATE + +#include "core/or/or.h" +#include "app/config/config.h" +#include "app/config/confparse.h" +#include "app/main/main.h" +#include "core/mainloop/connection.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/circuituse.h" +#include "core/or/connection_edge.h" +#include "feature/client/addressmap.h" +#include "feature/client/dnsserv.h" +#include "feature/client/entrynodes.h" +#include "feature/control/control.h" +#include "feature/control/control_auth.h" +#include "feature/control/control_cmd.h" +#include "feature/control/control_events.h" +#include "feature/control/control_fmt.h" +#include "feature/control/control_getinfo.h" +#include "feature/hs/hs_control.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerinfo.h" +#include "feature/nodelist/routerlist.h" +#include "feature/rend/rendclient.h" +#include "feature/rend/rendcommon.h" +#include "feature/rend/rendparse.h" +#include "feature/rend/rendservice.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.h" + +#include "core/or/cpath_build_state_st.h" +#include "core/or/entry_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/nodelist/node_st.h" +#include "feature/nodelist/routerinfo_st.h" +#include "feature/rend/rend_authorized_client_st.h" +#include "feature/rend/rend_encoded_v2_service_descriptor_st.h" +#include "feature/rend/rend_service_descriptor_st.h" + +static int control_setconf_helper(control_connection_t *conn, uint32_t len, + char *body, + int use_defaults); + +/** Yield true iff <b>s</b> is the state of a control_connection_t that has + * finished authentication and is accepting commands. */ +#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) + +/** Called when we receive a SETCONF message: parse the body and try + * to update our configuration. Reply with a DONE or ERROR message. + * Modifies the contents of body.*/ +static int +handle_control_setconf(control_connection_t *conn, uint32_t len, char *body) +{ + return control_setconf_helper(conn, len, body, 0); +} + +/** Called when we receive a RESETCONF message: parse the body and try + * to update our configuration. Reply with a DONE or ERROR message. + * Modifies the contents of body. */ +static int +handle_control_resetconf(control_connection_t *conn, uint32_t len, char *body) +{ + return control_setconf_helper(conn, len, body, 1); +} + +/** Called when we receive a GETCONF message. Parse the request, and + * reply with a CONFVALUE or an ERROR message */ +static int +handle_control_getconf(control_connection_t *conn, uint32_t body_len, + const char *body) +{ + smartlist_t *questions = smartlist_new(); + smartlist_t *answers = smartlist_new(); + smartlist_t *unrecognized = smartlist_new(); + char *msg = NULL; + size_t msg_len; + const or_options_t *options = get_options(); + int i, len; + + (void) body_len; /* body is NUL-terminated; so we can ignore len. */ + smartlist_split_string(questions, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { + if (!option_is_recognized(q)) { + smartlist_add(unrecognized, (char*) q); + } else { + config_line_t *answer = option_get_assignment(options,q); + if (!answer) { + const char *name = option_get_canonical_name(q); + smartlist_add_asprintf(answers, "250-%s\r\n", name); + } + + while (answer) { + config_line_t *next; + smartlist_add_asprintf(answers, "250-%s=%s\r\n", + answer->key, answer->value); + + next = answer->next; + tor_free(answer->key); + tor_free(answer->value); + tor_free(answer); + answer = next; + } + } + } SMARTLIST_FOREACH_END(q); + + if ((len = smartlist_len(unrecognized))) { + for (i=0; i < len-1; ++i) + connection_printf_to_buf(conn, + "552-Unrecognized configuration key \"%s\"\r\n", + (char*)smartlist_get(unrecognized, i)); + connection_printf_to_buf(conn, + "552 Unrecognized configuration key \"%s\"\r\n", + (char*)smartlist_get(unrecognized, len-1)); + } else if ((len = smartlist_len(answers))) { + char *tmp = smartlist_get(answers, len-1); + tor_assert(strlen(tmp)>4); + tmp[3] = ' '; + msg = smartlist_join_strings(answers, "", 0, &msg_len); + connection_buf_add(msg, msg_len, TO_CONN(conn)); + } else { + connection_write_str_to_buf("250 OK\r\n", conn); + } + + SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); + smartlist_free(answers); + SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); + smartlist_free(questions); + smartlist_free(unrecognized); + + tor_free(msg); + + return 0; +} + +/** Called when we get a +LOADCONF message. */ +static int +handle_control_loadconf(control_connection_t *conn, uint32_t len, + const char *body) +{ + setopt_err_t retval; + char *errstring = NULL; + const char *msg = NULL; + (void) len; + + retval = options_init_from_string(NULL, body, CMD_RUN_TOR, NULL, &errstring); + + if (retval != SETOPT_OK) + log_warn(LD_CONTROL, + "Controller gave us config file that didn't validate: %s", + errstring); + + switch (retval) { + case SETOPT_ERR_PARSE: + msg = "552 Invalid config file"; + break; + case SETOPT_ERR_TRANSITION: + msg = "553 Transition not allowed"; + break; + case SETOPT_ERR_SETTING: + msg = "553 Unable to set option"; + break; + case SETOPT_ERR_MISC: + default: + msg = "550 Unable to load config"; + break; + case SETOPT_OK: + break; + } + if (msg) { + if (errstring) + connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); + else + connection_printf_to_buf(conn, "%s\r\n", msg); + } else { + send_control_done(conn); + } + tor_free(errstring); + return 0; +} + +/** Called when we get a SETEVENTS message: update conn->event_mask, + * and reply with DONE or ERROR. */ +static int +handle_control_setevents(control_connection_t *conn, uint32_t len, + const char *body) +{ + int event_code; + event_mask_t event_mask = 0; + smartlist_t *events = smartlist_new(); + + (void) len; + + smartlist_split_string(events, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(events, const char *, ev) + { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); + continue; + } else { + int i; + event_code = -1; + + for (i = 0; control_event_table[i].event_name != NULL; ++i) { + if (!strcasecmp(ev, control_event_table[i].event_name)) { + event_code = control_event_table[i].event_code; + break; + } + } + + if (event_code == -1) { + connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", + ev); + SMARTLIST_FOREACH(events, char *, e, tor_free(e)); + smartlist_free(events); + return 0; + } + } + event_mask |= (((event_mask_t)1) << event_code); + } + SMARTLIST_FOREACH_END(ev); + SMARTLIST_FOREACH(events, char *, e, tor_free(e)); + smartlist_free(events); + + conn->event_mask = event_mask; + + control_update_global_event_mask(); + send_control_done(conn); + return 0; +} + +/** Called when we get a SAVECONF command. Try to flush the current options to + * disk, and report success or failure. */ +static int +handle_control_saveconf(control_connection_t *conn, uint32_t len, + const char *body) +{ + (void) len; + + int force = !strcmpstart(body, "FORCE"); + const or_options_t *options = get_options(); + if ((!force && options->IncludeUsed) || options_save_current() < 0) { + connection_write_str_to_buf( + "551 Unable to write configuration to disk.\r\n", conn); + } else { + send_control_done(conn); + } + return 0; +} + +/** Called when we get a SIGNAL command. React to the provided signal, and + * report success or failure. (If the signal results in a shutdown, success + * may not be reported.) */ +static int +handle_control_signal(control_connection_t *conn, uint32_t len, + const char *body) +{ + int sig = -1; + int i; + int n = 0; + char *s; + + (void) len; + + while (body[n] && ! TOR_ISSPACE(body[n])) + ++n; + s = tor_strndup(body, n); + + for (i = 0; signal_table[i].signal_name != NULL; ++i) { + if (!strcasecmp(s, signal_table[i].signal_name)) { + sig = signal_table[i].sig; + break; + } + } + + if (sig < 0) + connection_printf_to_buf(conn, "552 Unrecognized signal code \"%s\"\r\n", + s); + tor_free(s); + if (sig < 0) + return 0; + + send_control_done(conn); + /* Flush the "done" first if the signal might make us shut down. */ + if (sig == SIGTERM || sig == SIGINT) + connection_flush(TO_CONN(conn)); + + activate_signal(sig); + + return 0; +} + +/** Called when we get a TAKEOWNERSHIP command. Mark this connection + * as an owning connection, so that we will exit if the connection + * closes. */ +static int +handle_control_takeownership(control_connection_t *conn, uint32_t len, + const char *body) +{ + (void)len; + (void)body; + + conn->is_owning_control_connection = 1; + + log_info(LD_CONTROL, "Control connection %d has taken ownership of this " + "Tor instance.", + (int)(conn->base_.s)); + + send_control_done(conn); + return 0; +} + +/** Called when we get a DROPOWNERSHIP command. Mark this connection + * as a non-owning connection, so that we will not exit if the connection + * closes. */ +static int +handle_control_dropownership(control_connection_t *conn, uint32_t len, + const char *body) +{ + (void)len; + (void)body; + + conn->is_owning_control_connection = 0; + + log_info(LD_CONTROL, "Control connection %d has dropped ownership of this " + "Tor instance.", + (int)(conn->base_.s)); + + send_control_done(conn); + return 0; +} + +/** Given a text circuit <b>id</b>, return the corresponding circuit. */ +static origin_circuit_t * +get_circ(const char *id) +{ + uint32_t n_id; + int ok; + n_id = (uint32_t) tor_parse_ulong(id, 10, 0, UINT32_MAX, &ok, NULL); + if (!ok) + return NULL; + return circuit_get_by_global_id(n_id); +} + +/** Given a text stream <b>id</b>, return the corresponding AP connection. */ +static entry_connection_t * +get_stream(const char *id) +{ + uint64_t n_id; + int ok; + connection_t *conn; + n_id = tor_parse_uint64(id, 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) + return NULL; + conn = connection_get_by_global_id(n_id); + if (!conn || conn->type != CONN_TYPE_AP || conn->marked_for_close) + return NULL; + return TO_ENTRY_CONN(conn); +} + +/** Helper for setconf and resetconf. Acts like setconf, except + * it passes <b>use_defaults</b> on to options_trial_assign(). Modifies the + * contents of body. + */ +static int +control_setconf_helper(control_connection_t *conn, uint32_t len, char *body, + int use_defaults) +{ + setopt_err_t opt_err; + config_line_t *lines=NULL; + char *start = body; + char *errstring = NULL; + const unsigned flags = + CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0); + + char *config; + smartlist_t *entries = smartlist_new(); + + /* We have a string, "body", of the format '(key(=val|="val")?)' entries + * separated by space. break it into a list of configuration entries. */ + while (*body) { + char *eq = body; + char *key; + char *entry; + while (!TOR_ISSPACE(*eq) && *eq != '=') + ++eq; + key = tor_strndup(body, eq-body); + body = eq+1; + if (*eq == '=') { + char *val=NULL; + size_t val_len=0; + if (*body != '\"') { + char *val_start = body; + while (!TOR_ISSPACE(*body)) + body++; + val = tor_strndup(val_start, body-val_start); + val_len = strlen(val); + } else { + body = (char*)extract_escaped_string(body, (len - (body-start)), + &val, &val_len); + if (!body) { + connection_write_str_to_buf("551 Couldn't parse string\r\n", conn); + SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); + smartlist_free(entries); + tor_free(key); + return 0; + } + } + tor_asprintf(&entry, "%s %s", key, val); + tor_free(key); + tor_free(val); + } else { + entry = key; + } + smartlist_add(entries, entry); + while (TOR_ISSPACE(*body)) + ++body; + } + + smartlist_add_strdup(entries, ""); + config = smartlist_join_strings(entries, "\n", 0, NULL); + SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); + smartlist_free(entries); + + if (config_get_lines(config, &lines, 0) < 0) { + log_warn(LD_CONTROL,"Controller gave us config lines we can't parse."); + connection_write_str_to_buf("551 Couldn't parse configuration\r\n", + conn); + tor_free(config); + return 0; + } + tor_free(config); + + opt_err = options_trial_assign(lines, flags, &errstring); + { + const char *msg; + switch (opt_err) { + case SETOPT_ERR_MISC: + msg = "552 Unrecognized option"; + break; + case SETOPT_ERR_PARSE: + msg = "513 Unacceptable option value"; + break; + case SETOPT_ERR_TRANSITION: + msg = "553 Transition not allowed"; + break; + case SETOPT_ERR_SETTING: + default: + msg = "553 Unable to set option"; + break; + case SETOPT_OK: + config_free_lines(lines); + send_control_done(conn); + return 0; + } + log_warn(LD_CONTROL, + "Controller gave us config lines that didn't validate: %s", + errstring); + connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring); + config_free_lines(lines); + tor_free(errstring); + return 0; + } +} + +/** Return true iff <b>addr</b> is unusable as a mapaddress target because of + * containing funny characters. */ +static int +address_is_invalid_mapaddress_target(const char *addr) +{ + if (!strcmpstart(addr, "*.")) + return address_is_invalid_destination(addr+2, 1); + else + return address_is_invalid_destination(addr, 1); +} + +/** Called when we get a MAPADDRESS command; try to bind all listed addresses, + * and report success or failure. */ +static int +handle_control_mapaddress(control_connection_t *conn, uint32_t len, + const char *body) +{ + smartlist_t *elts; + smartlist_t *lines; + smartlist_t *reply; + char *r; + size_t sz; + (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ + + lines = smartlist_new(); + elts = smartlist_new(); + reply = smartlist_new(); + smartlist_split_string(lines, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(lines, char *, line) { + tor_strlower(line); + smartlist_split_string(elts, line, "=", 0, 2); + if (smartlist_len(elts) == 2) { + const char *from = smartlist_get(elts,0); + const char *to = smartlist_get(elts,1); + if (address_is_invalid_mapaddress_target(to)) { + smartlist_add_asprintf(reply, + "512-syntax error: invalid address '%s'", to); + log_warn(LD_CONTROL, + "Skipping invalid argument '%s' in MapAddress msg", to); + } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0") || + !strcmp(from, "::")) { + const char type = + !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : + (!strcmp(from, "0.0.0.0") ? RESOLVED_TYPE_IPV4 : RESOLVED_TYPE_IPV6); + const char *address = addressmap_register_virtual_address( + type, tor_strdup(to)); + if (!address) { + smartlist_add_asprintf(reply, + "451-resource exhausted: skipping '%s'", line); + log_warn(LD_CONTROL, + "Unable to allocate address for '%s' in MapAddress msg", + safe_str_client(line)); + } else { + smartlist_add_asprintf(reply, "250-%s=%s", address, to); + } + } else { + const char *msg; + if (addressmap_register_auto(from, to, 1, + ADDRMAPSRC_CONTROLLER, &msg) < 0) { + smartlist_add_asprintf(reply, + "512-syntax error: invalid address mapping " + " '%s': %s", line, msg); + log_warn(LD_CONTROL, + "Skipping invalid argument '%s' in MapAddress msg: %s", + line, msg); + } else { + smartlist_add_asprintf(reply, "250-%s", line); + } + } + } else { + smartlist_add_asprintf(reply, "512-syntax error: mapping '%s' is " + "not of expected form 'foo=bar'.", line); + log_info(LD_CONTROL, "Skipping MapAddress '%s': wrong " + "number of items.", + safe_str_client(line)); + } + SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); + smartlist_clear(elts); + } SMARTLIST_FOREACH_END(line); + SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); + smartlist_free(lines); + smartlist_free(elts); + + if (smartlist_len(reply)) { + ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; + r = smartlist_join_strings(reply, "\r\n", 1, &sz); + connection_buf_add(r, sz, TO_CONN(conn)); + tor_free(r); + } else { + const char *response = + "512 syntax error: not enough arguments to mapaddress.\r\n"; + connection_buf_add(response, strlen(response), TO_CONN(conn)); + } + + SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); + smartlist_free(reply); + return 0; +} + +/** Given a string, convert it to a circuit purpose. */ +static uint8_t +circuit_purpose_from_string(const char *string) +{ + if (!strcasecmpstart(string, "purpose=")) + string += strlen("purpose="); + + if (!strcasecmp(string, "general")) + return CIRCUIT_PURPOSE_C_GENERAL; + else if (!strcasecmp(string, "controller")) + return CIRCUIT_PURPOSE_CONTROLLER; + else + return CIRCUIT_PURPOSE_UNKNOWN; +} + +/** Return a newly allocated smartlist containing the arguments to the command + * waiting in <b>body</b>. If there are fewer than <b>min_args</b> arguments, + * or if <b>max_args</b> is nonnegative and there are more than + * <b>max_args</b> arguments, send a 512 error to the controller, using + * <b>command</b> as the command name in the error message. */ +static smartlist_t * +getargs_helper(const char *command, control_connection_t *conn, + const char *body, int min_args, int max_args) +{ + smartlist_t *args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(args) < min_args) { + connection_printf_to_buf(conn, "512 Missing argument to %s\r\n",command); + goto err; + } else if (max_args >= 0 && smartlist_len(args) > max_args) { + connection_printf_to_buf(conn, "512 Too many arguments to %s\r\n",command); + goto err; + } + return args; + err: + SMARTLIST_FOREACH(args, char *, s, tor_free(s)); + smartlist_free(args); + return NULL; +} + +/** Helper. Return the first element of <b>sl</b> at index <b>start_at</b> or + * higher that starts with <b>prefix</b>, case-insensitive. Return NULL if no + * such element exists. */ +static const char * +find_element_starting_with(smartlist_t *sl, int start_at, const char *prefix) +{ + int i; + for (i = start_at; i < smartlist_len(sl); ++i) { + const char *elt = smartlist_get(sl, i); + if (!strcasecmpstart(elt, prefix)) + return elt; + } + return NULL; +} + +/** Helper. Return true iff s is an argument that we should treat as a + * key-value pair. */ +static int +is_keyval_pair(const char *s) +{ + /* An argument is a key-value pair if it has an =, and it isn't of the form + * $fingeprint=name */ + return strchr(s, '=') && s[0] != '$'; +} + +/** Called when we get an EXTENDCIRCUIT message. Try to extend the listed + * circuit, and report success or failure. */ +static int +handle_control_extendcircuit(control_connection_t *conn, uint32_t len, + const char *body) +{ + smartlist_t *router_nicknames=NULL, *nodes=NULL; + origin_circuit_t *circ = NULL; + int zero_circ; + uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; + smartlist_t *args; + (void) len; + + router_nicknames = smartlist_new(); + + args = getargs_helper("EXTENDCIRCUIT", conn, body, 1, -1); + if (!args) + goto done; + + zero_circ = !strcmp("0", (char*)smartlist_get(args,0)); + + if (zero_circ) { + const char *purp = find_element_starting_with(args, 1, "PURPOSE="); + + if (purp) { + intended_purpose = circuit_purpose_from_string(purp); + if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + goto done; + } + } + + if ((smartlist_len(args) == 1) || + (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { + // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" + circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); + if (!circ) { + connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); + } else { + connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", + (unsigned long)circ->global_identifier); + } + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + goto done; + } + // "EXTENDCIRCUIT 0 router1,router2" || + // "EXTENDCIRCUIT 0 router1,router2 PURPOSE=foo" + } + + if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + goto done; + } + + if (smartlist_len(args) < 2) { + connection_printf_to_buf(conn, + "512 syntax error: not enough arguments.\r\n"); + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + goto done; + } + + smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0); + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + + nodes = smartlist_new(); + int first_node = zero_circ; + SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { + const node_t *node = node_get_by_nickname(n, 0); + if (!node) { + connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); + goto done; + } + if (!node_has_preferred_descriptor(node, first_node)) { + connection_printf_to_buf(conn, "552 No descriptor for \"%s\"\r\n", n); + goto done; + } + smartlist_add(nodes, (void*)node); + first_node = 0; + } SMARTLIST_FOREACH_END(n); + if (!smartlist_len(nodes)) { + connection_write_str_to_buf("512 No router names provided\r\n", conn); + goto done; + } + + if (zero_circ) { + /* start a new circuit */ + circ = origin_circuit_init(intended_purpose, 0); + } + + /* now circ refers to something that is ready to be extended */ + first_node = zero_circ; + SMARTLIST_FOREACH(nodes, const node_t *, node, + { + extend_info_t *info = extend_info_from_node(node, first_node); + if (!info) { + tor_assert_nonfatal(first_node); + log_warn(LD_CONTROL, + "controller tried to connect to a node that lacks a suitable " + "descriptor, or which doesn't have any " + "addresses that are allowed by the firewall configuration; " + "circuit marked for closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); + connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); + goto done; + } + circuit_append_new_exit(circ, info); + if (circ->build_state->desired_path_len > 1) { + circ->build_state->onehop_tunnel = 0; + } + extend_info_free(info); + first_node = 0; + }); + + /* now that we've populated the cpath, start extending */ + if (zero_circ) { + int err_reason = 0; + if ((err_reason = circuit_handle_first_hop(circ)) < 0) { + circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); + connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); + goto done; + } + } else { + if (circ->base_.state == CIRCUIT_STATE_OPEN || + circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) { + int err_reason = 0; + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); + if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { + log_info(LD_CONTROL, + "send_next_onion_skin failed; circuit marked for closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); + connection_write_str_to_buf("551 Couldn't send onion skin\r\n", conn); + goto done; + } + } + } + + connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", + (unsigned long)circ->global_identifier); + if (zero_circ) /* send a 'launched' event, for completeness */ + circuit_event_status(circ, CIRC_EVENT_LAUNCHED, 0); + done: + SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); + smartlist_free(router_nicknames); + smartlist_free(nodes); + return 0; +} + +/** Called when we get a SETCIRCUITPURPOSE message. If we can find the + * circuit and it's a valid purpose, change it. */ +static int +handle_control_setcircuitpurpose(control_connection_t *conn, + uint32_t len, const char *body) +{ + origin_circuit_t *circ = NULL; + uint8_t new_purpose; + smartlist_t *args; + (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ + + args = getargs_helper("SETCIRCUITPURPOSE", conn, body, 2, -1); + if (!args) + goto done; + + if (!(circ = get_circ(smartlist_get(args,0)))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + goto done; + } + + { + const char *purp = find_element_starting_with(args,1,"PURPOSE="); + if (!purp) { + connection_write_str_to_buf("552 No purpose given\r\n", conn); + goto done; + } + new_purpose = circuit_purpose_from_string(purp); + if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); + goto done; + } + } + + circuit_change_purpose(TO_CIRCUIT(circ), new_purpose); + connection_write_str_to_buf("250 OK\r\n", conn); + + done: + if (args) { + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + } + return 0; +} + +/** Called when we get an ATTACHSTREAM message. Try to attach the requested + * stream, and report success or failure. */ +static int +handle_control_attachstream(control_connection_t *conn, uint32_t len, + const char *body) +{ + entry_connection_t *ap_conn = NULL; + origin_circuit_t *circ = NULL; + int zero_circ; + smartlist_t *args; + crypt_path_t *cpath=NULL; + int hop=0, hop_line_ok=1; + (void) len; + + args = getargs_helper("ATTACHSTREAM", conn, body, 2, -1); + if (!args) + return 0; + + zero_circ = !strcmp("0", (char*)smartlist_get(args,1)); + + if (!(ap_conn = get_stream(smartlist_get(args, 0)))) { + connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + } else if (!zero_circ && !(circ = get_circ(smartlist_get(args, 1)))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", + (char*)smartlist_get(args, 1)); + } else if (circ) { + const char *hopstring = find_element_starting_with(args,2,"HOP="); + if (hopstring) { + hopstring += strlen("HOP="); + hop = (int) tor_parse_ulong(hopstring, 10, 0, INT_MAX, + &hop_line_ok, NULL); + if (!hop_line_ok) { /* broken hop line */ + connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", hopstring); + } + } + } + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + if (!ap_conn || (!zero_circ && !circ) || !hop_line_ok) + return 0; + + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_RESOLVE_WAIT) { + connection_write_str_to_buf( + "555 Connection is not managed by controller.\r\n", + conn); + return 0; + } + + /* Do we need to detach it first? */ + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + circuit_t *tmpcirc = circuit_get_by_edge_conn(edge_conn); + connection_edge_end(edge_conn, END_STREAM_REASON_TIMEOUT); + /* Un-mark it as ending, since we're going to reuse it. */ + edge_conn->edge_has_sent_end = 0; + edge_conn->end_reason = 0; + if (tmpcirc) + circuit_detach_stream(tmpcirc, edge_conn); + CONNECTION_AP_EXPECT_NONPENDING(ap_conn); + TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; + } + + if (circ && (circ->base_.state != CIRCUIT_STATE_OPEN)) { + connection_write_str_to_buf( + "551 Can't attach stream to non-open origin circuit\r\n", + conn); + return 0; + } + /* Is this a single hop circuit? */ + if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { + connection_write_str_to_buf( + "551 Can't attach stream to this one-hop circuit.\r\n", conn); + return 0; + } + + if (circ && hop>0) { + /* find this hop in the circuit, and set cpath */ + cpath = circuit_get_cpath_hop(circ, hop); + if (!cpath) { + connection_printf_to_buf(conn, + "551 Circuit doesn't have %d hops.\r\n", hop); + return 0; + } + } + if (connection_ap_handshake_rewrite_and_attach(ap_conn, circ, cpath) < 0) { + connection_write_str_to_buf("551 Unable to attach stream\r\n", conn); + return 0; + } + send_control_done(conn); + return 0; +} + +/** Called when we get a POSTDESCRIPTOR message. Try to learn the provided + * descriptor, and report success or failure. */ +static int +handle_control_postdescriptor(control_connection_t *conn, uint32_t len, + const char *body) +{ + char *desc; + const char *msg=NULL; + uint8_t purpose = ROUTER_PURPOSE_GENERAL; + int cache = 0; /* eventually, we may switch this to 1 */ + + const char *cp = memchr(body, '\n', len); + + if (cp == NULL) { + connection_printf_to_buf(conn, "251 Empty body\r\n"); + return 0; + } + ++cp; + + char *cmdline = tor_memdup_nulterm(body, cp-body); + smartlist_t *args = smartlist_new(); + smartlist_split_string(args, cmdline, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(args, char *, option) { + if (!strcasecmpstart(option, "purpose=")) { + option += strlen("purpose="); + purpose = router_purpose_from_string(option); + if (purpose == ROUTER_PURPOSE_UNKNOWN) { + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", + option); + goto done; + } + } else if (!strcasecmpstart(option, "cache=")) { + option += strlen("cache="); + if (!strcasecmp(option, "no")) + cache = 0; + else if (!strcasecmp(option, "yes")) + cache = 1; + else { + connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n", + option); + goto done; + } + } else { /* unrecognized argument? */ + connection_printf_to_buf(conn, + "512 Unexpected argument \"%s\" to postdescriptor\r\n", option); + goto done; + } + } SMARTLIST_FOREACH_END(option); + + read_escaped_data(cp, len-(cp-body), &desc); + + switch (router_load_single_router(desc, purpose, cache, &msg)) { + case -1: + if (!msg) msg = "Could not parse descriptor"; + connection_printf_to_buf(conn, "554 %s\r\n", msg); + break; + case 0: + if (!msg) msg = "Descriptor not added"; + connection_printf_to_buf(conn, "251 %s\r\n",msg); + break; + case 1: + send_control_done(conn); + break; + } + + tor_free(desc); + done: + SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); + smartlist_free(args); + tor_free(cmdline); + return 0; +} + +/** Called when we receive a REDIRECTSTERAM command. Try to change the target + * address of the named AP stream, and report success or failure. */ +static int +handle_control_redirectstream(control_connection_t *conn, uint32_t len, + const char *body) +{ + entry_connection_t *ap_conn = NULL; + char *new_addr = NULL; + uint16_t new_port = 0; + smartlist_t *args; + (void) len; + + args = getargs_helper("REDIRECTSTREAM", conn, body, 2, -1); + if (!args) + return 0; + + if (!(ap_conn = get_stream(smartlist_get(args, 0))) + || !ap_conn->socks_request) { + connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + } else { + int ok = 1; + if (smartlist_len(args) > 2) { /* they included a port too */ + new_port = (uint16_t) tor_parse_ulong(smartlist_get(args, 2), + 10, 1, 65535, &ok, NULL); + } + if (!ok) { + connection_printf_to_buf(conn, "512 Cannot parse port \"%s\"\r\n", + (char*)smartlist_get(args, 2)); + } else { + new_addr = tor_strdup(smartlist_get(args, 1)); + } + } + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + if (!new_addr) + return 0; + + strlcpy(ap_conn->socks_request->address, new_addr, + sizeof(ap_conn->socks_request->address)); + if (new_port) + ap_conn->socks_request->port = new_port; + tor_free(new_addr); + send_control_done(conn); + return 0; +} + +/** Called when we get a CLOSESTREAM command; try to close the named stream + * and report success or failure. */ +static int +handle_control_closestream(control_connection_t *conn, uint32_t len, + const char *body) +{ + entry_connection_t *ap_conn=NULL; + uint8_t reason=0; + smartlist_t *args; + int ok; + (void) len; + + args = getargs_helper("CLOSESTREAM", conn, body, 2, -1); + if (!args) + return 0; + + else if (!(ap_conn = get_stream(smartlist_get(args, 0)))) + connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + else { + reason = (uint8_t) tor_parse_ulong(smartlist_get(args,1), 10, 0, 255, + &ok, NULL); + if (!ok) { + connection_printf_to_buf(conn, "552 Unrecognized reason \"%s\"\r\n", + (char*)smartlist_get(args, 1)); + ap_conn = NULL; + } + } + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + if (!ap_conn) + return 0; + + connection_mark_unattached_ap(ap_conn, reason); + send_control_done(conn); + return 0; +} + +/** Called when we get a CLOSECIRCUIT command; try to close the named circuit + * and report success or failure. */ +static int +handle_control_closecircuit(control_connection_t *conn, uint32_t len, + const char *body) +{ + origin_circuit_t *circ = NULL; + int safe = 0; + smartlist_t *args; + (void) len; + + args = getargs_helper("CLOSECIRCUIT", conn, body, 1, -1); + if (!args) + return 0; + + if (!(circ=get_circ(smartlist_get(args, 0)))) + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", + (char*)smartlist_get(args, 0)); + else { + int i; + for (i=1; i < smartlist_len(args); ++i) { + if (!strcasecmp(smartlist_get(args, i), "IfUnused")) + safe = 1; + else + log_info(LD_CONTROL, "Skipping unknown option %s", + (char*)smartlist_get(args,i)); + } + } + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + if (!circ) + return 0; + + if (!safe || !circ->p_streams) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); + } + + send_control_done(conn); + return 0; +} + +/** Called when we get a RESOLVE command: start trying to resolve + * the listed addresses. */ +static int +handle_control_resolve(control_connection_t *conn, uint32_t len, + const char *body) +{ + smartlist_t *args, *failed; + int is_reverse = 0; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + + if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { + log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " + "isn't listening for ADDRMAP events. It probably won't see " + "the answer."); + } + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + { + const char *modearg = find_element_starting_with(args, 0, "mode="); + if (modearg && !strcasecmp(modearg, "mode=reverse")) + is_reverse = 1; + } + failed = smartlist_new(); + SMARTLIST_FOREACH(args, const char *, arg, { + if (!is_keyval_pair(arg)) { + if (dnsserv_launch_request(arg, is_reverse, conn)<0) + smartlist_add(failed, (char*)arg); + } + }); + + send_control_done(conn); + SMARTLIST_FOREACH(failed, const char *, arg, { + control_event_address_mapped(arg, arg, time(NULL), + "internal", 0); + }); + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + smartlist_free(failed); + return 0; +} + +/** Called when we get a PROTOCOLINFO command: send back a reply. */ +static int +handle_control_protocolinfo(control_connection_t *conn, uint32_t len, + const char *body) +{ + const char *bad_arg = NULL; + smartlist_t *args; + (void)len; + + conn->have_sent_protocolinfo = 1; + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH(args, const char *, arg, { + int ok; + tor_parse_long(arg, 10, 0, LONG_MAX, &ok, NULL); + if (!ok) { + bad_arg = arg; + break; + } + }); + if (bad_arg) { + connection_printf_to_buf(conn, "513 No such version %s\r\n", + escaped(bad_arg)); + /* Don't tolerate bad arguments when not authenticated. */ + if (!STATE_IS_OPEN(TO_CONN(conn)->state)) + connection_mark_for_close(TO_CONN(conn)); + goto done; + } else { + const or_options_t *options = get_options(); + int cookies = options->CookieAuthentication; + char *cfile = get_controller_cookie_file_name(); + char *abs_cfile; + char *esc_cfile; + char *methods; + abs_cfile = make_path_absolute(cfile); + esc_cfile = esc_for_log(abs_cfile); + { + int passwd = (options->HashedControlPassword != NULL || + options->HashedControlSessionPassword != NULL); + smartlist_t *mlist = smartlist_new(); + if (cookies) { + smartlist_add(mlist, (char*)"COOKIE"); + smartlist_add(mlist, (char*)"SAFECOOKIE"); + } + if (passwd) + smartlist_add(mlist, (char*)"HASHEDPASSWORD"); + if (!cookies && !passwd) + smartlist_add(mlist, (char*)"NULL"); + methods = smartlist_join_strings(mlist, ",", 0, NULL); + smartlist_free(mlist); + } + + connection_printf_to_buf(conn, + "250-PROTOCOLINFO 1\r\n" + "250-AUTH METHODS=%s%s%s\r\n" + "250-VERSION Tor=%s\r\n" + "250 OK\r\n", + methods, + cookies?" COOKIEFILE=":"", + cookies?esc_cfile:"", + escaped(VERSION)); + tor_free(methods); + tor_free(cfile); + tor_free(abs_cfile); + tor_free(esc_cfile); + } + done: + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return 0; +} + +/** Called when we get a USEFEATURE command: parse the feature list, and + * set up the control_connection's options properly. */ +static int +handle_control_usefeature(control_connection_t *conn, + uint32_t len, + const char *body) +{ + smartlist_t *args; + int bad = 0; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { + if (!strcasecmp(arg, "VERBOSE_NAMES")) + ; + else if (!strcasecmp(arg, "EXTENDED_EVENTS")) + ; + else { + connection_printf_to_buf(conn, "552 Unrecognized feature \"%s\"\r\n", + arg); + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(arg); + + if (!bad) { + send_control_done(conn); + } + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return 0; +} + +/** Implementation for the DROPGUARDS command. */ +static int +handle_control_dropguards(control_connection_t *conn, + uint32_t len, + const char *body) +{ + smartlist_t *args; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = smartlist_new(); + smartlist_split_string(args, body, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + + static int have_warned = 0; + if (! have_warned) { + log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand " + "the risks before using it. It may be removed in a future " + "version of Tor."); + have_warned = 1; + } + + if (smartlist_len(args)) { + connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); + } else { + remove_all_entry_guards(); + send_control_done(conn); + } + + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + return 0; +} + +/** Implementation for the HSFETCH command. */ +static int +handle_control_hsfetch(control_connection_t *conn, uint32_t len, + const char *body) +{ + int i; + char digest[DIGEST_LEN], *hsaddress = NULL, *arg1 = NULL, *desc_id = NULL; + smartlist_t *args = NULL, *hsdirs = NULL; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + static const char *hsfetch_command = "HSFETCH"; + static const char *v2_str = "v2-"; + const size_t v2_str_len = strlen(v2_str); + rend_data_t *rend_query = NULL; + ed25519_public_key_t v3_pk; + uint32_t version; + + /* Make sure we have at least one argument, the HSAddress. */ + args = getargs_helper(hsfetch_command, conn, body, 1, -1); + if (!args) { + goto exit; + } + + /* Extract the first argument (either HSAddress or DescID). */ + arg1 = smartlist_get(args, 0); + /* Test if it's an HS address without the .onion part. */ + if (rend_valid_v2_service_id(arg1)) { + hsaddress = arg1; + version = HS_VERSION_TWO; + } else if (strcmpstart(arg1, v2_str) == 0 && + rend_valid_descriptor_id(arg1 + v2_str_len) && + base32_decode(digest, sizeof(digest), arg1 + v2_str_len, + REND_DESC_ID_V2_LEN_BASE32) == + REND_DESC_ID_V2_LEN_BASE32) { + /* We have a well formed version 2 descriptor ID. Keep the decoded value + * of the id. */ + desc_id = digest; + version = HS_VERSION_TWO; + } else if (hs_address_is_valid(arg1)) { + hsaddress = arg1; + version = HS_VERSION_THREE; + hs_parse_address(hsaddress, &v3_pk, NULL, NULL); + } else { + connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n", + arg1); + goto done; + } + + static const char *opt_server = "SERVER="; + + /* Skip first argument because it's the HSAddress or DescID. */ + for (i = 1; i < smartlist_len(args); ++i) { + const char *arg = smartlist_get(args, i); + const node_t *node; + + if (!strcasecmpstart(arg, opt_server)) { + const char *server; + + server = arg + strlen(opt_server); + node = node_get_by_hex_id(server, 0); + if (!node) { + connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", + server); + goto done; + } + if (!hsdirs) { + /* Stores routerstatus_t object for each specified server. */ + hsdirs = smartlist_new(); + } + /* Valid server, add it to our local list. */ + smartlist_add(hsdirs, node->rs); + } else { + connection_printf_to_buf(conn, "513 Unexpected argument \"%s\"\r\n", + arg); + goto done; + } + } + + if (version == HS_VERSION_TWO) { + rend_query = rend_data_client_create(hsaddress, desc_id, NULL, + REND_NO_AUTH); + if (rend_query == NULL) { + connection_printf_to_buf(conn, "551 Error creating the HS query\r\n"); + goto done; + } + } + + /* Using a descriptor ID, we force the user to provide at least one + * hsdir server using the SERVER= option. */ + if (desc_id && (!hsdirs || !smartlist_len(hsdirs))) { + connection_printf_to_buf(conn, "512 %s option is required\r\n", + opt_server); + goto done; + } + + /* We are about to trigger HSDir fetch so send the OK now because after + * that 650 event(s) are possible so better to have the 250 OK before them + * to avoid out of order replies. */ + send_control_done(conn); + + /* Trigger the fetch using the built rend query and possibly a list of HS + * directory to use. This function ignores the client cache thus this will + * always send a fetch command. */ + if (version == HS_VERSION_TWO) { + rend_client_fetch_v2_desc(rend_query, hsdirs); + } else if (version == HS_VERSION_THREE) { + hs_control_hsfetch_command(&v3_pk, hsdirs); + } + + done: + SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); + smartlist_free(args); + /* Contains data pointer that we don't own thus no cleanup. */ + smartlist_free(hsdirs); + rend_data_free(rend_query); + exit: + return 0; +} + +/** Implementation for the HSPOST command. */ +static int +handle_control_hspost(control_connection_t *conn, + uint32_t len, + const char *body) +{ + static const char *opt_server = "SERVER="; + static const char *opt_hsaddress = "HSADDRESS="; + smartlist_t *hs_dirs = NULL; + const char *encoded_desc = body; + size_t encoded_desc_len = len; + const char *onion_address = NULL; + + char *cp = memchr(body, '\n', len); + if (cp == NULL) { + connection_printf_to_buf(conn, "251 Empty body\r\n"); + return 0; + } + char *argline = tor_strndup(body, cp-body); + + smartlist_t *args = smartlist_new(); + + /* If any SERVER= or HSADDRESS= options were specified, try to parse + * the options line. */ + if (!strcasecmpstart(argline, opt_server) || + !strcasecmpstart(argline, opt_hsaddress)) { + /* encoded_desc begins after a newline character */ + cp = cp + 1; + encoded_desc = cp; + encoded_desc_len = len-(cp-body); + + smartlist_split_string(args, argline, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { + if (!strcasecmpstart(arg, opt_server)) { + const char *server = arg + strlen(opt_server); + const node_t *node = node_get_by_hex_id(server, 0); + + if (!node || !node->rs) { + connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", + server); + goto done; + } + /* Valid server, add it to our local list. */ + if (!hs_dirs) + hs_dirs = smartlist_new(); + smartlist_add(hs_dirs, node->rs); + } else if (!strcasecmpstart(arg, opt_hsaddress)) { + const char *address = arg + strlen(opt_hsaddress); + if (!hs_address_is_valid(address)) { + connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); + goto done; + } + onion_address = address; + } else { + connection_printf_to_buf(conn, "512 Unexpected argument \"%s\"\r\n", + arg); + goto done; + } + } SMARTLIST_FOREACH_END(arg); + } + + /* Handle the v3 case. */ + if (onion_address) { + char *desc_str = NULL; + read_escaped_data(encoded_desc, encoded_desc_len, &desc_str); + if (hs_control_hspost_command(desc_str, onion_address, hs_dirs) < 0) { + connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); + } else { + send_control_done(conn); + } + tor_free(desc_str); + goto done; + } + + /* From this point on, it is only v2. */ + + /* Read the dot encoded descriptor, and parse it. */ + rend_encoded_v2_service_descriptor_t *desc = + tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); + read_escaped_data(encoded_desc, encoded_desc_len, &desc->desc_str); + + rend_service_descriptor_t *parsed = NULL; + char *intro_content = NULL; + size_t intro_size; + size_t encoded_size; + const char *next_desc; + if (!rend_parse_v2_service_descriptor(&parsed, desc->desc_id, &intro_content, + &intro_size, &encoded_size, + &next_desc, desc->desc_str, 1)) { + /* Post the descriptor. */ + char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; + if (!rend_get_service_id(parsed->pk, serviceid)) { + smartlist_t *descs = smartlist_new(); + smartlist_add(descs, desc); + + /* We are about to trigger HS descriptor upload so send the OK now + * because after that 650 event(s) are possible so better to have the + * 250 OK before them to avoid out of order replies. */ + send_control_done(conn); + + /* Trigger the descriptor upload */ + directory_post_to_hs_dir(parsed, descs, hs_dirs, serviceid, 0); + smartlist_free(descs); + } + + rend_service_descriptor_free(parsed); + } else { + connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); + } + + tor_free(intro_content); + rend_encoded_v2_service_descriptor_free(desc); + done: + tor_free(argline); + smartlist_free(hs_dirs); /* Contents belong to the rend service code. */ + SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); + smartlist_free(args); + return 0; +} + +/* Helper function for ADD_ONION that adds an ephemeral service depending on + * the given hs_version. + * + * The secret key in pk depends on the hs_version. The ownership of the key + * used in pk is given to the HS subsystem so the caller must stop accessing + * it after. + * + * The port_cfgs is a list of service port. Ownership transferred to service. + * The max_streams refers to the MaxStreams= key. + * The max_streams_close_circuit refers to the MaxStreamsCloseCircuit key. + * The auth_type is the authentication type of the clients in auth_clients. + * The ownership of that list is transferred to the service. + * + * On success (RSAE_OKAY), the address_out points to a newly allocated string + * containing the onion address without the .onion part. On error, address_out + * is untouched. */ +static hs_service_add_ephemeral_status_t +add_onion_helper_add_service(int hs_version, + add_onion_secret_key_t *pk, + smartlist_t *port_cfgs, int max_streams, + int max_streams_close_circuit, int auth_type, + smartlist_t *auth_clients, char **address_out) +{ + hs_service_add_ephemeral_status_t ret; + + tor_assert(pk); + tor_assert(port_cfgs); + tor_assert(address_out); + + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_add_ephemeral(pk->v2, port_cfgs, max_streams, + max_streams_close_circuit, auth_type, + auth_clients, address_out); + break; + case HS_VERSION_THREE: + ret = hs_service_add_ephemeral(pk->v3, port_cfgs, max_streams, + max_streams_close_circuit, address_out); + break; + default: + tor_assert_unreached(); + } + + return ret; +} + +/** The list of onion services that have been added via ADD_ONION that do not + * belong to any particular control connection. + */ +static smartlist_t *detached_onion_services = NULL; + +/** + * Return a list of detached onion services, or NULL if none exist. + **/ +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 +handle_control_add_onion(control_connection_t *conn, + uint32_t len, + const char *body) +{ + smartlist_t *args; + int arg_len; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = getargs_helper("ADD_ONION", conn, body, 2, -1); + if (!args) + return 0; + arg_len = smartlist_len(args); + + /* Parse all of the arguments that do not involve handling cryptographic + * material first, since there's no reason to touch that at all if any of + * the other arguments are malformed. + */ + smartlist_t *port_cfgs = smartlist_new(); + smartlist_t *auth_clients = NULL; + smartlist_t *auth_created_clients = NULL; + int discard_pk = 0; + int detach = 0; + int max_streams = 0; + int max_streams_close_circuit = 0; + rend_auth_type_t auth_type = REND_NO_AUTH; + /* Default to adding an anonymous hidden service if no flag is given */ + int non_anonymous = 0; + for (int i = 1; i < arg_len; i++) { + static const char *port_prefix = "Port="; + static const char *flags_prefix = "Flags="; + static const char *max_s_prefix = "MaxStreams="; + static const char *auth_prefix = "ClientAuth="; + + const char *arg = smartlist_get(args, (int)i); + if (!strcasecmpstart(arg, port_prefix)) { + /* "Port=VIRTPORT[,TARGET]". */ + const char *port_str = arg + strlen(port_prefix); + + rend_service_port_config_t *cfg = + rend_service_parse_port_config(port_str, ",", NULL); + if (!cfg) { + connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); + goto out; + } + smartlist_add(port_cfgs, cfg); + } else if (!strcasecmpstart(arg, max_s_prefix)) { + /* "MaxStreams=[0..65535]". */ + const char *max_s_str = arg + strlen(max_s_prefix); + int ok = 0; + max_streams = (int)tor_parse_long(max_s_str, 10, 0, 65535, &ok, NULL); + if (!ok) { + connection_printf_to_buf(conn, "512 Invalid MaxStreams\r\n"); + goto out; + } + } else if (!strcasecmpstart(arg, flags_prefix)) { + /* "Flags=Flag[,Flag]", where Flag can be: + * * 'DiscardPK' - If tor generates the keypair, do not include it in + * the response. + * * 'Detach' - Do not tie this onion service to any particular control + * connection. + * * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is + * exceeded. + * * 'BasicAuth' - Client authorization using the 'basic' method. + * * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this + * flag is present, tor must be in non-anonymous + * hidden service mode. If this flag is absent, + * tor must be in anonymous hidden service mode. + */ + static const char *discard_flag = "DiscardPK"; + static const char *detach_flag = "Detach"; + static const char *max_s_close_flag = "MaxStreamsCloseCircuit"; + static const char *basicauth_flag = "BasicAuth"; + static const char *non_anonymous_flag = "NonAnonymous"; + + smartlist_t *flags = smartlist_new(); + int bad = 0; + + smartlist_split_string(flags, arg + strlen(flags_prefix), ",", + SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(flags) < 1) { + connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); + bad = 1; + } + SMARTLIST_FOREACH_BEGIN(flags, const char *, flag) + { + if (!strcasecmp(flag, discard_flag)) { + discard_pk = 1; + } else if (!strcasecmp(flag, detach_flag)) { + detach = 1; + } else if (!strcasecmp(flag, max_s_close_flag)) { + max_streams_close_circuit = 1; + } else if (!strcasecmp(flag, basicauth_flag)) { + auth_type = REND_BASIC_AUTH; + } else if (!strcasecmp(flag, non_anonymous_flag)) { + non_anonymous = 1; + } else { + connection_printf_to_buf(conn, + "512 Invalid 'Flags' argument: %s\r\n", + escaped(flag)); + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(flag); + SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp)); + smartlist_free(flags); + if (bad) + goto out; + } else if (!strcasecmpstart(arg, auth_prefix)) { + char *err_msg = NULL; + int created = 0; + rend_authorized_client_t *client = + add_onion_helper_clientauth(arg + strlen(auth_prefix), + &created, &err_msg); + if (!client) { + if (err_msg) { + connection_write_str_to_buf(err_msg, conn); + tor_free(err_msg); + } + goto out; + } + + if (auth_clients != NULL) { + int bad = 0; + SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) { + if (strcmp(ac->client_name, client->client_name) == 0) { + bad = 1; + break; + } + } SMARTLIST_FOREACH_END(ac); + if (bad) { + connection_printf_to_buf(conn, + "512 Duplicate name in ClientAuth\r\n"); + rend_authorized_client_free(client); + goto out; + } + } else { + auth_clients = smartlist_new(); + auth_created_clients = smartlist_new(); + } + smartlist_add(auth_clients, client); + if (created) { + smartlist_add(auth_created_clients, client); + } + } else { + connection_printf_to_buf(conn, "513 Invalid argument\r\n"); + goto out; + } + } + if (smartlist_len(port_cfgs) == 0) { + connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n"); + goto out; + } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) { + connection_printf_to_buf(conn, "512 No auth type specified\r\n"); + goto out; + } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) { + connection_printf_to_buf(conn, "512 No auth clients specified\r\n"); + goto out; + } else if ((auth_type == REND_BASIC_AUTH && + smartlist_len(auth_clients) > 512) || + (auth_type == REND_STEALTH_AUTH && + smartlist_len(auth_clients) > 16)) { + connection_printf_to_buf(conn, "512 Too many auth clients\r\n"); + goto out; + } else if (non_anonymous != rend_service_non_anonymous_mode_enabled( + get_options())) { + /* If we failed, and the non-anonymous flag is set, Tor must be in + * anonymous hidden service mode. + * The error message changes based on the current Tor config: + * 512 Tor is in anonymous hidden service mode + * 512 Tor is in non-anonymous hidden service mode + * (I've deliberately written them out in full here to aid searchability.) + */ + connection_printf_to_buf(conn, "512 Tor is in %sanonymous hidden service " + "mode\r\n", + non_anonymous ? "" : "non-"); + goto out; + } + + /* Parse the "keytype:keyblob" argument. */ + int hs_version = 0; + add_onion_secret_key_t pk = { NULL }; + const char *key_new_alg = NULL; + char *key_new_blob = NULL; + char *err_msg = NULL; + + if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, + &key_new_alg, &key_new_blob, &pk, &hs_version, + &err_msg) < 0) { + if (err_msg) { + connection_write_str_to_buf(err_msg, conn); + tor_free(err_msg); + } + goto out; + } + tor_assert(!err_msg); + + /* Hidden service version 3 don't have client authentication support so if + * ClientAuth was given, send back an error. */ + if (hs_version == HS_VERSION_THREE && auth_clients) { + connection_printf_to_buf(conn, "513 ClientAuth not supported\r\n"); + goto out; + } + + /* Create the HS, using private key pk, client authentication auth_type, + * the list of auth_clients, and port config port_cfg. + * rend_service_add_ephemeral() will take ownership of pk and port_cfg, + * regardless of success/failure. + */ + char *service_id = NULL; + int ret = add_onion_helper_add_service(hs_version, &pk, port_cfgs, + max_streams, + max_streams_close_circuit, auth_type, + auth_clients, &service_id); + port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */ + auth_clients = NULL; /* so is auth_clients */ + switch (ret) { + case RSAE_OKAY: + { + if (detach) { + if (!detached_onion_services) + detached_onion_services = smartlist_new(); + smartlist_add(detached_onion_services, service_id); + } else { + if (!conn->ephemeral_onion_services) + conn->ephemeral_onion_services = smartlist_new(); + smartlist_add(conn->ephemeral_onion_services, service_id); + } + + tor_assert(service_id); + connection_printf_to_buf(conn, "250-ServiceID=%s\r\n", service_id); + if (key_new_alg) { + tor_assert(key_new_blob); + connection_printf_to_buf(conn, "250-PrivateKey=%s:%s\r\n", + key_new_alg, key_new_blob); + } + if (auth_created_clients) { + SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, { + char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie, + auth_type); + tor_assert(encoded); + connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n", + ac->client_name, encoded); + memwipe(encoded, 0, strlen(encoded)); + tor_free(encoded); + }); + } + + connection_printf_to_buf(conn, "250 OK\r\n"); + break; + } + case RSAE_BADPRIVKEY: + connection_printf_to_buf(conn, "551 Failed to generate onion address\r\n"); + break; + case RSAE_ADDREXISTS: + connection_printf_to_buf(conn, "550 Onion address collision\r\n"); + break; + case RSAE_BADVIRTPORT: + connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); + break; + case RSAE_BADAUTH: + connection_printf_to_buf(conn, "512 Invalid client authorization\r\n"); + break; + case RSAE_INTERNAL: /* FALLSTHROUGH */ + default: + connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n"); + } + if (key_new_blob) { + memwipe(key_new_blob, 0, strlen(key_new_blob)); + tor_free(key_new_blob); + } + + out: + if (port_cfgs) { + SMARTLIST_FOREACH(port_cfgs, rend_service_port_config_t*, p, + rend_service_port_config_free(p)); + smartlist_free(port_cfgs); + } + + if (auth_clients) { + SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac, + rend_authorized_client_free(ac)); + smartlist_free(auth_clients); + } + if (auth_created_clients) { + // Do not free entries; they are the same as auth_clients + smartlist_free(auth_created_clients); + } + + SMARTLIST_FOREACH(args, char *, cp, { + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + }); + smartlist_free(args); + return 0; +} + +/** Helper function to handle parsing the KeyType:KeyBlob argument to the + * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated + * and the private key not discarded, the algorithm and serialized private key, + * or NULL and an optional control protocol error message on failure. The + * caller is responsible for freeing the returned key_new_blob and err_msg. + * + * Note: The error messages returned are deliberately vague to avoid echoing + * key material. + */ +STATIC int +add_onion_helper_keyarg(const char *arg, int discard_pk, + const char **key_new_alg_out, char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, int *hs_version, + char **err_msg_out) +{ + smartlist_t *key_args = smartlist_new(); + crypto_pk_t *pk = NULL; + const char *key_new_alg = NULL; + char *key_new_blob = NULL; + char *err_msg = NULL; + int ret = -1; + + smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(key_args) != 2) { + err_msg = tor_strdup("512 Invalid key type/blob\r\n"); + goto err; + } + + /* The format is "KeyType:KeyBlob". */ + static const char *key_type_new = "NEW"; + static const char *key_type_best = "BEST"; + static const char *key_type_rsa1024 = "RSA1024"; + static const char *key_type_ed25519_v3 = "ED25519-V3"; + + const char *key_type = smartlist_get(key_args, 0); + const char *key_blob = smartlist_get(key_args, 1); + + if (!strcasecmp(key_type_rsa1024, key_type)) { + /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ + pk = crypto_pk_base64_decode_private(key_blob, strlen(key_blob)); + if (!pk) { + err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); + goto err; + } + if (crypto_pk_num_bits(pk) != PK_BYTES*8) { + crypto_pk_free(pk); + err_msg = tor_strdup("512 Invalid RSA key size\r\n"); + goto err; + } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_type)) { + /* "ED25519-V3:<Base64 Blob>" - Loading a pre-existing ed25519 key. */ + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, + strlen(key_blob)) != sizeof(sk->seckey)) { + tor_free(sk); + err_msg = tor_strdup("512 Failed to decode ED25519-V3 key\r\n"); + goto err; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; + } else if (!strcasecmp(key_type_new, key_type)) { + /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ + if (!strcasecmp(key_type_rsa1024, key_blob) || + !strcasecmp(key_type_best, key_blob)) { + /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ + pk = crypto_pk_new(); + if (crypto_pk_generate_key(pk)) { + tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", + key_type_rsa1024); + goto err; + } + if (!discard_pk) { + if (crypto_pk_base64_encode_private(pk, &key_new_blob)) { + crypto_pk_free(pk); + tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", + key_type_rsa1024); + goto err; + } + key_new_alg = key_type_rsa1024; + } + decoded_key->v2 = pk; + *hs_version = HS_VERSION_TWO; + } else if (!strcasecmp(key_type_ed25519_v3, key_blob)) { + ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); + if (ed25519_secret_key_generate(sk, 1) < 0) { + tor_free(sk); + tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", + key_type_ed25519_v3); + goto err; + } + if (!discard_pk) { + ssize_t len = base64_encode_size(sizeof(sk->seckey), 0) + 1; + key_new_blob = tor_malloc_zero(len); + if (base64_encode(key_new_blob, len, (const char *) sk->seckey, + sizeof(sk->seckey), 0) != (len - 1)) { + tor_free(sk); + tor_free(key_new_blob); + tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", + key_type_ed25519_v3); + goto err; + } + key_new_alg = key_type_ed25519_v3; + } + decoded_key->v3 = sk; + *hs_version = HS_VERSION_THREE; + } else { + err_msg = tor_strdup("513 Invalid key type\r\n"); + goto err; + } + } else { + err_msg = tor_strdup("513 Invalid key type\r\n"); + goto err; + } + + /* Succeeded in loading or generating a private key. */ + ret = 0; + + err: + SMARTLIST_FOREACH(key_args, char *, cp, { + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + }); + smartlist_free(key_args); + + if (err_msg_out) { + *err_msg_out = err_msg; + } else { + tor_free(err_msg); + } + *key_new_alg_out = key_new_alg; + *key_new_blob_out = key_new_blob; + + return ret; +} + +/** Helper function to handle parsing a ClientAuth argument to the + * ADD_ONION command. Return a new rend_authorized_client_t, or NULL + * and an optional control protocol error message on failure. The + * caller is responsible for freeing the returned auth_client and err_msg. + * + * If 'created' is specified, it will be set to 1 when a new cookie has + * been generated. + */ +STATIC rend_authorized_client_t * +add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) +{ + int ok = 0; + + tor_assert(arg); + tor_assert(created); + tor_assert(err_msg); + *err_msg = NULL; + + smartlist_t *auth_args = smartlist_new(); + rend_authorized_client_t *client = + tor_malloc_zero(sizeof(rend_authorized_client_t)); + smartlist_split_string(auth_args, arg, ":", 0, 0); + if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) { + *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n"); + goto err; + } + client->client_name = tor_strdup(smartlist_get(auth_args, 0)); + if (smartlist_len(auth_args) == 2) { + char *decode_err_msg = NULL; + if (rend_auth_decode_cookie(smartlist_get(auth_args, 1), + client->descriptor_cookie, + NULL, &decode_err_msg) < 0) { + tor_assert(decode_err_msg); + tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg); + tor_free(decode_err_msg); + goto err; + } + *created = 0; + } else { + crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN); + *created = 1; + } + + if (!rend_valid_client_name(client->client_name)) { + *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n"); + goto err; + } + + ok = 1; + err: + SMARTLIST_FOREACH(auth_args, char *, item, tor_free(item)); + smartlist_free(auth_args); + if (!ok) { + rend_authorized_client_free(client); + client = NULL; + } + return client; +} + +/** Called when we get a DEL_ONION command; parse the body, and remove + * the existing ephemeral Onion Service. */ +static int +handle_control_del_onion(control_connection_t *conn, + uint32_t len, + const char *body) +{ + int hs_version = 0; + smartlist_t *args; + (void) len; /* body is nul-terminated; it's safe to ignore the length */ + args = getargs_helper("DEL_ONION", conn, body, 1, 1); + if (!args) + return 0; + + const char *service_id = smartlist_get(args, 0); + if (rend_valid_v2_service_id(service_id)) { + hs_version = HS_VERSION_TWO; + } else if (hs_address_is_valid(service_id)) { + hs_version = HS_VERSION_THREE; + } else { + connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); + goto out; + } + + /* Determine if the onion service belongs to this particular control + * connection, or if it is in the global list of detached services. If it + * is in neither, either the service ID is invalid in some way, or it + * explicitly belongs to a different control connection, and an error + * should be returned. + */ + smartlist_t *services[2] = { + conn->ephemeral_onion_services, + detached_onion_services + }; + smartlist_t *onion_services = NULL; + int idx = -1; + for (size_t i = 0; i < ARRAY_LENGTH(services); i++) { + idx = smartlist_string_pos(services[i], service_id); + if (idx != -1) { + onion_services = services[i]; + break; + } + } + if (onion_services == NULL) { + connection_printf_to_buf(conn, "552 Unknown Onion Service id\r\n"); + } else { + int ret = -1; + switch (hs_version) { + case HS_VERSION_TWO: + ret = rend_service_del_ephemeral(service_id); + break; + case HS_VERSION_THREE: + ret = hs_service_del_ephemeral(service_id); + break; + default: + /* The ret value will be -1 thus hitting the warning below. This should + * never happen because of the check at the start of the function. */ + break; + } + if (ret < 0) { + /* This should *NEVER* fail, since the service is on either the + * per-control connection list, or the global one. + */ + log_warn(LD_BUG, "Failed to remove Onion Service %s.", + escaped(service_id)); + tor_fragile_assert(); + } + + /* Remove/scrub the service_id from the appropriate list. */ + char *cp = smartlist_get(onion_services, idx); + smartlist_del(onion_services, idx); + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + + send_control_done(conn); + } + + out: + SMARTLIST_FOREACH(args, char *, cp, { + memwipe(cp, 0, strlen(cp)); + tor_free(cp); + }); + smartlist_free(args); + return 0; +} + +int +handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + /* XXXX Why is this not implemented as a table like the GETINFO + * items are? Even handling the plus signs at the beginnings of + * commands wouldn't be very hard with proper macros. */ + + if (!strcasecmp(conn->incoming_cmd, "SETCONF")) { + if (handle_control_setconf(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "RESETCONF")) { + if (handle_control_resetconf(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "GETCONF")) { + if (handle_control_getconf(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "+LOADCONF")) { + if (handle_control_loadconf(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "SETEVENTS")) { + if (handle_control_setevents(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "AUTHENTICATE")) { + if (handle_control_authenticate(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "SAVECONF")) { + if (handle_control_saveconf(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "SIGNAL")) { + if (handle_control_signal(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "TAKEOWNERSHIP")) { + if (handle_control_takeownership(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "DROPOWNERSHIP")) { + if (handle_control_dropownership(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "MAPADDRESS")) { + if (handle_control_mapaddress(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "GETINFO")) { + if (handle_control_getinfo(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "EXTENDCIRCUIT")) { + if (handle_control_extendcircuit(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "SETCIRCUITPURPOSE")) { + if (handle_control_setcircuitpurpose(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "SETROUTERPURPOSE")) { + connection_write_str_to_buf("511 SETROUTERPURPOSE is obsolete.\r\n", conn); + } else if (!strcasecmp(conn->incoming_cmd, "ATTACHSTREAM")) { + if (handle_control_attachstream(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "+POSTDESCRIPTOR")) { + if (handle_control_postdescriptor(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "REDIRECTSTREAM")) { + if (handle_control_redirectstream(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "CLOSESTREAM")) { + if (handle_control_closestream(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "CLOSECIRCUIT")) { + if (handle_control_closecircuit(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "USEFEATURE")) { + if (handle_control_usefeature(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "RESOLVE")) { + if (handle_control_resolve(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "PROTOCOLINFO")) { + if (handle_control_protocolinfo(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { + if (handle_control_authchallenge(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { + if (handle_control_dropguards(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { + if (handle_control_hsfetch(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "+HSPOST")) { + if (handle_control_hspost(conn, cmd_data_len, args)) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { + int ret = handle_control_add_onion(conn, cmd_data_len, args); + memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ + if (ret) + return -1; + } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { + int ret = handle_control_del_onion(conn, cmd_data_len, args); + memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ + if (ret) + return -1; + } else { + connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", + conn->incoming_cmd); + } + + return 0; +} + +void +control_cmd_free_all(void) +{ + if (detached_onion_services) { /* Free the detached onion services */ + SMARTLIST_FOREACH(detached_onion_services, char *, cp, tor_free(cp)); + smartlist_free(detached_onion_services); + } +} diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h new file mode 100644 index 0000000000..a417e10da3 --- /dev/null +++ b/src/feature/control/control_cmd.h @@ -0,0 +1,48 @@ +/* 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_cmd.h + * \brief Header file for control_cmd.c. + **/ + +#ifndef TOR_CONTROL_CMD_H +#define TOR_CONTROL_CMD_H + +int handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args); +void control_cmd_free_all(void); + +#ifdef CONTROL_CMD_PRIVATE +#include "lib/crypt_ops/crypto_ed25519.h" + +/* ADD_ONION secret key to create an ephemeral service. The command supports + * multiple versions so this union stores the key and passes it to the HS + * subsystem depending on the requested version. */ +typedef union add_onion_secret_key_t { + /* Hidden service v2 secret key. */ + crypto_pk_t *v2; + /* Hidden service v3 secret key. */ + ed25519_secret_key_t *v3; +} add_onion_secret_key_t; + +STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, + const char **key_new_alg_out, + char **key_new_blob_out, + add_onion_secret_key_t *decoded_key, + int *hs_version, char **err_msg_out); + +STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg, + int *created, char **err_msg_out); + +#endif /* defined(CONTROL_CMD_PRIVATE) */ + +#ifdef CONTROL_MODULE_PRIVATE +smartlist_t * get_detached_onion_services(void); +#endif /* defined(CONTROL_MODULE_PRIVATE) */ + +#endif /* !defined(TOR_CONTROL_CMD_H) */ diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c index c87a224ac5..290ea61450 100644 --- a/src/feature/control/control_fmt.c +++ b/src/feature/control/control_fmt.c @@ -297,3 +297,98 @@ read_escaped_data(const char *data, size_t len, char **out) *outp = '\0'; return outp - *out; } + +/** Send a "DONE" message down the control connection <b>conn</b>. */ +void +send_control_done(control_connection_t *conn) +{ + connection_write_str_to_buf("250 OK\r\n", conn); +} + +/** If the first <b>in_len_max</b> characters in <b>start</b> contain a + * double-quoted string with escaped characters, return the length of that + * string (as encoded, including quotes). Otherwise return -1. */ +static inline int +get_escaped_string_length(const char *start, size_t in_len_max, + int *chars_out) +{ + const char *cp, *end; + int chars = 0; + + if (*start != '\"') + return -1; + + cp = start+1; + end = start+in_len_max; + + /* Calculate length. */ + while (1) { + if (cp >= end) { + return -1; /* Too long. */ + } else if (*cp == '\\') { + if (++cp == end) + return -1; /* Can't escape EOS. */ + ++cp; + ++chars; + } else if (*cp == '\"') { + break; + } else { + ++cp; + ++chars; + } + } + if (chars_out) + *chars_out = chars; + return (int)(cp - start+1); +} + +/** As decode_escaped_string, but does not decode the string: copies the + * entire thing, including quotation marks. */ +const char * +extract_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len) +{ + int length = get_escaped_string_length(start, in_len_max, NULL); + if (length<0) + return NULL; + *out_len = length; + *out = tor_strndup(start, *out_len); + return start+length; +} + +/** Given a pointer to a string starting at <b>start</b> containing + * <b>in_len_max</b> characters, decode a string beginning with one double + * quote, containing any number of non-quote characters or characters escaped + * with a backslash, and ending with a final double quote. Place the resulting + * string (unquoted, unescaped) into a newly allocated string in *<b>out</b>; + * store its length in <b>out_len</b>. On success, return a pointer to the + * character immediately following the escaped string. On failure, return + * NULL. */ +const char * +decode_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len) +{ + const char *cp, *end; + char *outp; + int len, n_chars = 0; + + len = get_escaped_string_length(start, in_len_max, &n_chars); + if (len<0) + return NULL; + + end = start+len-1; /* Index of last quote. */ + tor_assert(*end == '\"'); + outp = *out = tor_malloc(len+1); + *out_len = n_chars; + + cp = start+1; + while (cp < end) { + if (*cp == '\\') + ++cp; + *outp++ = *cp++; + } + *outp = '\0'; + tor_assert((outp - *out) == (int)*out_len); + + return end+1; +} diff --git a/src/feature/control/control_fmt.h b/src/feature/control/control_fmt.h index 1b4534cc3b..e7ab6608a4 100644 --- a/src/feature/control/control_fmt.h +++ b/src/feature/control/control_fmt.h @@ -25,5 +25,10 @@ char *circuit_describe_status_for_controller(origin_circuit_t *circ); size_t write_escaped_data(const char *data, size_t len, char **out); size_t read_escaped_data(const char *data, size_t len, char **out); +const char *extract_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len); +const char *decode_escaped_string(const char *start, size_t in_len_max, + char **out, size_t *out_len); +void send_control_done(control_connection_t *conn); #endif /* !defined(TOR_CONTROL_FMT_H) */ diff --git a/src/feature/control/control_getinfo.c b/src/feature/control/control_getinfo.c index 67a6e27316..a7a85f2fdf 100644 --- a/src/feature/control/control_getinfo.c +++ b/src/feature/control/control_getinfo.c @@ -24,6 +24,7 @@ #include "feature/client/bridges.h" #include "feature/client/entrynodes.h" #include "feature/control/control.h" +#include "feature/control/control_cmd.h" #include "feature/control/control_events.h" #include "feature/control/control_fmt.h" #include "feature/control/control_getinfo.h" diff --git a/src/test/test_controller.c b/src/test/test_controller.c index 82922d2028..f3af6d2ec0 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -1,12 +1,13 @@ /* Copyright (c) 2015-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#define CONTROL_PRIVATE +#define CONTROL_CMD_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_cmd.h" #include "feature/control/control_getinfo.h" #include "feature/client/entrynodes.h" #include "feature/hs/hs_common.h" diff --git a/src/test/test_pt.c b/src/test/test_pt.c index 612006906d..87e3ba356c 100644 --- a/src/test/test_pt.c +++ b/src/test/test_pt.c @@ -7,7 +7,6 @@ #define PT_PRIVATE #define UTIL_PRIVATE #define STATEFILE_PRIVATE -//#define CONTROL_PRIVATE #define CONTROL_EVENTS_PRIVATE #define PROCESS_PRIVATE #include "core/or/or.h" diff --git a/src/test/test_util.c b/src/test/test_util.c index 4875a7c943..b4e702e1d7 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -6,7 +6,6 @@ #include "orconfig.h" #define COMPAT_PRIVATE #define COMPAT_TIME_PRIVATE -#define CONTROL_PRIVATE #define UTIL_PRIVATE #define UTIL_MALLOC_PRIVATE #define SOCKET_PRIVATE |