commit d8b3ec865de2738144bd6bbf9f6355662e64eb25 Author: Nick Mathewson nickm@torproject.org Date: Mon Apr 8 10:23:21 2019 -0400
Update more controller commands, now that we have kvline support --- src/feature/control/control_cmd.c | 549 +++++++++++++++++--------------------- 1 file changed, 242 insertions(+), 307 deletions(-)
diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c index 4f67e3163..184855535 100644 --- a/src/feature/control/control_cmd.c +++ b/src/feature/control/control_cmd.c @@ -184,6 +184,17 @@ control_cmd_parse_args(const char *command, return result; }
+/** + * Return true iff <b>lines</b> contains <b>flags</b> as a no-value + * (keyword-only) entry. + **/ +static bool +config_lines_contain_flag(const config_line_t *lines, const char *flag) +{ + const config_line_t *line = config_line_find_case(lines, flag); + return line && !strcmp(line->value, ""); +} + /** 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.*/ @@ -370,15 +381,19 @@ handle_control_setevents(control_connection_t *conn, return 0; }
+static const control_cmd_syntax_t saveconf_syntax = { + .max_args = 0, + .accept_keywords = true, + .kvline_flags=KV_OMIT_VALS, +}; + /** 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) +handle_control_saveconf(control_connection_t *conn, + const control_cmd_args_t *args) { - (void) len; - - int force = !strcmpstart(body, "FORCE"); + bool force = config_lines_contain_flag(args->kwargs, "FORCE"); const or_options_t *options = get_options(); if ((!force && options->IncludeUsed) || options_save_current() < 0) { connection_write_str_to_buf( @@ -620,30 +635,27 @@ address_is_invalid_mapaddress_target(const char *addr) return address_is_invalid_destination(addr, 1); }
+static const control_cmd_syntax_t mapaddress_syntax = { + .max_args=0, + .accept_keywords=true, +}; + /** 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) +handle_control_mapaddress(control_connection_t *conn, + const control_cmd_args_t *args) { - 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); + const config_line_t *line; + for (line = args->kwargs; line; line = line->next) { + const char *from = line->key; + const char *to = line->value; + { if (address_is_invalid_mapaddress_target(to)) { smartlist_add_asprintf(reply, "512-syntax error: invalid address '%s'", to); @@ -658,10 +670,10 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, type, tor_strdup(to)); if (!address) { smartlist_add_asprintf(reply, - "451-resource exhausted: skipping '%s'", line); + "451-resource exhausted: skipping '%s=%s'", from,to); log_warn(LD_CONTROL, "Unable to allocate address for '%s' in MapAddress msg", - safe_str_client(line)); + safe_str_client(to)); } else { smartlist_add_asprintf(reply, "250-%s=%s", address, to); } @@ -671,27 +683,16 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, ADDRMAPSRC_CONTROLLER, &msg) < 0) { smartlist_add_asprintf(reply, "512-syntax error: invalid address mapping " - " '%s': %s", line, msg); + " '%s=%s': %s", from, to, msg); log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg: %s", - line, msg); + "Skipping invalid argument '%s=%s' in MapAddress msg: %s", + from, to, msg); } else { - smartlist_add_asprintf(reply, "250-%s", line); + smartlist_add_asprintf(reply, "250-%s=%s", from, to); } } - } 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] = ' '; @@ -931,36 +932,36 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, return 0; }
+static const control_cmd_syntax_t setcircuitpurpose_syntax = { + .max_args=1, + .accept_keywords=true, +}; + /** 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) + const control_cmd_args_t *args) { 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. */ + const char *circ_id = smartlist_get(args->args,0);
- 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)); + if (!(circ = get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit "%s"\r\n", circ_id); goto done; }
{ - const char *purp = find_element_starting_with(args,1,"PURPOSE="); + const config_line_t *purp = config_line_find_case(args->kwargs, "PURPOSE"); if (!purp) { connection_write_str_to_buf("552 No purpose given\r\n", conn); goto done; } - new_purpose = circuit_purpose_from_string(purp); + new_purpose = circuit_purpose_from_string(purp->value); if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose "%s"\r\n", purp); + connection_printf_to_buf(conn, "552 Unknown purpose "%s"\r\n", + purp->value); goto done; } } @@ -969,54 +970,50 @@ handle_control_setcircuitpurpose(control_connection_t *conn, 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; }
+static const char *attachstream_keywords[] = { + "HOP", NULL +}; +static const control_cmd_syntax_t attachstream_syntax = { + .min_args=2, .max_args=2, + .accept_keywords=true, + .allowed_keywords=attachstream_keywords +}; + /** 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) +handle_control_attachstream(control_connection_t *conn, + const control_cmd_args_t *args) { 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; + const char *stream_id = smartlist_get(args->args, 0); + const char *circ_id = smartlist_get(args->args, 1); + int zero_circ = !strcmp(circ_id, "0"); + const config_line_t *hoparg = config_line_find_case(args->kwargs, "HOP");
- args = getargs_helper("ATTACHSTREAM", conn, body, 2, -1); - if (!args) + if (!(ap_conn = get_stream(stream_id))) { + connection_printf_to_buf(conn, "552 Unknown stream "%s"\r\n", stream_id); + return 0; + } else if (!zero_circ && !(circ = get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit "%s"\r\n", circ_id); 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, + if (hoparg) { + hop = (int) tor_parse_ulong(hoparg->value, 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); + connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", + hoparg->value); + return 0; } } } - 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 && @@ -1071,59 +1068,49 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, return 0; }
+static const char *postdescriptor_keywords[] = { + "cache", "purpose", NULL, +}; + +static const control_cmd_syntax_t postdescriptor_syntax = { + .max_args = 0, + .accept_keywords = true, + .allowed_keywords = postdescriptor_keywords, + .want_object = true, +}; + /** 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) +handle_control_postdescriptor(control_connection_t *conn, + const control_cmd_args_t *args) { - char *desc; const char *msg=NULL; uint8_t purpose = ROUTER_PURPOSE_GENERAL; int cache = 0; /* eventually, we may switch this to 1 */ + const config_line_t *line;
- const char *cp = memchr(body, '\n', len); - - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; + line = config_line_find_case(args->kwargs, "purpose"); + if (line) { + purpose = router_purpose_from_string(line->value); + connection_printf_to_buf(conn, "552 Unknown purpose "%s"\r\n", + line->value); + goto done; } - ++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); + line = config_line_find_case(args->kwargs, "cache"); + if (line) { + if (!strcasecmp(line->value, "no")) + cache = 0; + else if (!strcasecmp(line->value, "yes")) + cache = 1; + else { + connection_printf_to_buf(conn, "552 Unknown cache request "%s"\r\n", + line->value); goto done; } - } SMARTLIST_FOREACH_END(option); - - read_escaped_data(cp, len-(cp-body), &desc); + }
- switch (router_load_single_router(desc, purpose, cache, &msg)) { + switch (router_load_single_router(args->object, purpose, cache, &msg)) { case -1: if (!msg) msg = "Could not parse descriptor"; connection_printf_to_buf(conn, "554 %s\r\n", msg); @@ -1137,11 +1124,7 @@ handle_control_postdescriptor(control_connection_t *conn, uint32_t len, break; }
- tor_free(desc); done: - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - tor_free(cmdline); return 0; }
@@ -1230,38 +1213,29 @@ handle_control_closestream(control_connection_t *conn, return 0; }
+static const control_cmd_syntax_t closecircuit_syntax = { + .min_args=1, .max_args=1, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, + // XXXX we might want to exclude unrecognized flags, but for now we + // XXXX just ignore them for backward compatibility. +}; + /** 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) +handle_control_closecircuit(control_connection_t *conn, + const control_cmd_args_t *args) { + const char *circ_id = smartlist_get(args->args, 0); origin_circuit_t *circ = NULL; - int safe = 0; - smartlist_t *args; - (void) len;
- args = getargs_helper("CLOSECIRCUIT", conn, body, 1, -1); - if (!args) + if (!(circ=get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit "%s"\r\n", circ_id); 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; + + bool safe = config_lines_contain_flag(args->kwargs, "IfUnused");
if (!safe || !circ->p_streams) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); @@ -1271,36 +1245,43 @@ handle_control_closecircuit(control_connection_t *conn, uint32_t len, return 0; }
+static const control_cmd_syntax_t resolve_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, +}; + /** 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) +handle_control_resolve(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *args, *failed; + smartlist_t *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")) + const config_line_t *modearg = config_line_find_case(args->kwargs, "mode"); + if (modearg && !strcasecmp(modearg->value, "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); - } - }); + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strlen(line->value)) { + const char *addr = line->key; + if (dnsserv_launch_request(addr, is_reverse, conn)<0) + smartlist_add(failed, (char*)addr); + } else { + // XXXX arguably we should reject unrecognized keyword arguments, + // XXXX but the old implementation didn't do that. + } + }
send_control_done(conn); SMARTLIST_FOREACH(failed, const char *, arg, { @@ -1308,8 +1289,6 @@ handle_control_resolve(control_connection_t *conn, uint32_t len, "internal", 0); });
- SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); smartlist_free(failed); return 0; } @@ -1443,30 +1422,33 @@ handle_control_dropguards(control_connection_t *conn, return 0; }
+static const char *hsfetch_keywords[] = { + "SERVER", NULL, +}; +static const control_cmd_syntax_t hsfetch_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = hsfetch_keywords, + .want_object = true, +}; + /** Implementation for the HSFETCH command. */ static int -handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_hsfetch(control_connection_t *conn, + const control_cmd_args_t *args) + { - 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"; + char digest[DIGEST_LEN], *desc_id = NULL; + smartlist_t *hsdirs = NULL; 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; - } + const char *hsaddress = NULL;
/* Extract the first argument (either HSAddress or DescID). */ - arg1 = smartlist_get(args, 0); + const char *arg1 = smartlist_get(args->args, 0); /* Test if it's an HS address without the .onion part. */ if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; @@ -1490,18 +1472,11 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, 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; + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strcasecmp(line->key, "SERVER")) { + const char *server = line->value;
- if (!strcasecmpstart(arg, opt_server)) { - const char *server; - - server = arg + strlen(opt_server); - node = node_get_by_hex_id(server, 0); + const node_t *node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server "%s" not found\r\n", server); @@ -1514,9 +1489,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* 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; + tor_assert_nonfatal_unreached(); } }
@@ -1532,9 +1505,8 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* 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; + connection_printf_to_buf(conn, "512 SERVER option is required\r\n"); + goto done; }
/* We are about to trigger HSDir fetch so send the OK now because after @@ -1552,96 +1524,75 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, }
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; }
+static const char *hspost_keywords[] = { + "SERVER", "HSADDRESS", NULL +}; +static const control_cmd_syntax_t hspost_syntax = { + .min_args = 0, .max_args = 0, + .accept_keywords = true, + .want_object = true, + .allowed_keywords = hspost_keywords +}; + /** Implementation for the HSPOST command. */ static int handle_control_hspost(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *args) { - 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 *encoded_desc = args->object; + size_t encoded_desc_len = args->object_len; const char *onion_address = NULL; + const config_line_t *line;
- 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(); + for (line = args->kwargs; line; line = line->next) { + if (!strcasecmpstart(line->key, "SERVER")) { + const char *server = line->value; + const node_t *node = node_get_by_hex_id(server, 0);
- /* 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); + if (!node || !node->rs) { + connection_printf_to_buf(conn, "552 Server "%s" not found\r\n", + server); goto done; } - } SMARTLIST_FOREACH_END(arg); + /* Valid server, add it to our local list. */ + if (!hs_dirs) + hs_dirs = smartlist_new(); + smartlist_add(hs_dirs, node->rs); + } else if (!strcasecmpstart(line->key, "HSADDRESS")) { + const char *address = line->value; + if (!hs_address_is_valid(address)) { + connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); + goto done; + } + onion_address = address; + } else { + tor_assert_nonfatal_unreached(); + } }
/* 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) { + if (hs_control_hspost_command(encoded_desc, 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. */ + /* 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); + desc->desc_str = tor_memdup_nulterm(encoded_desc, encoded_desc_len);
rend_service_descriptor_t *parsed = NULL; char *intro_content = NULL; @@ -1675,10 +1626,7 @@ handle_control_hspost(control_connection_t *conn, 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; }
@@ -1742,21 +1690,21 @@ get_detached_onion_services(void) return detached_onion_services; }
+static const char *add_onion_keywords[] = { + "Port", "Flags", "MaxStreams", "ClientAuth", NULL +}; +static const control_cmd_syntax_t add_onion_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = add_onion_keywords +}; + /** 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) + const control_cmd_args_t *args) { - 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. @@ -1769,36 +1717,28 @@ handle_control_add_onion(control_connection_t *conn, 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); + const config_line_t *arg;
+ for (arg = args->kwargs; arg; arg = arg->next) { + if (!strcasecmp(arg->key, "Port")) { + /* "Port=VIRTPORT[,TARGET]". */ rend_service_port_config_t *cfg = - rend_service_parse_port_config(port_str, ",", NULL); + rend_service_parse_port_config(arg->value, ",", 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)) { + } else if (!strcasecmp(arg->key, "MaxStreams")) { /* "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); + max_streams = (int)tor_parse_long(arg->value, 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)) { + } else if (!strcasecmp(arg->key, "Flags")) { /* "Flags=Flag[,Flag]", where Flag can be: * * 'DiscardPK' - If tor generates the keypair, do not include it in * the response. @@ -1821,8 +1761,7 @@ handle_control_add_onion(control_connection_t *conn, smartlist_t *flags = smartlist_new(); int bad = 0;
- smartlist_split_string(flags, arg + strlen(flags_prefix), ",", - SPLIT_IGNORE_BLANK, 0); + smartlist_split_string(flags, arg->value, ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); bad = 1; @@ -1851,11 +1790,12 @@ handle_control_add_onion(control_connection_t *conn, smartlist_free(flags); if (bad) goto out; - } else if (!strcasecmpstart(arg, auth_prefix)) { + + } else if (!strcasecmp(arg->key, "ClientAuth")) { char *err_msg = NULL; int created = 0; rend_authorized_client_t *client = - add_onion_helper_clientauth(arg + strlen(auth_prefix), + add_onion_helper_clientauth(arg->value, &created, &err_msg); if (!client) { if (err_msg) { @@ -1888,7 +1828,7 @@ handle_control_add_onion(control_connection_t *conn, smartlist_add(auth_created_clients, client); } } else { - connection_printf_to_buf(conn, "513 Invalid argument\r\n"); + tor_assert_nonfatal_unreached(); goto out; } } @@ -1929,7 +1869,8 @@ handle_control_add_onion(control_connection_t *conn, char *key_new_blob = NULL; char *err_msg = NULL;
- if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, + const char *onionkey = smartlist_get(args->args, 0); + if (add_onion_helper_keyarg(onionkey, discard_pk, &key_new_alg, &key_new_blob, &pk, &hs_version, &err_msg) < 0) { if (err_msg) { @@ -2031,12 +1972,6 @@ handle_control_add_onion(control_connection_t *conn, // 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; }
@@ -2471,28 +2406,28 @@ static const control_cmd_def_t CONTROL_COMMANDS[] = MULTLINE_PARSED(loadconf, 0), ONE_LINE_PARSED(setevents, 0), ONE_LINE(authenticate, legacy, CMD_FL_WIPE), - ONE_LINE(saveconf, legacy, 0), + ONE_LINE_PARSED(saveconf, 0), ONE_LINE_PARSED(signal, 0), ONE_LINE_PARSED(takeownership, 0), ONE_LINE_PARSED(dropownership, 0), - ONE_LINE(mapaddress, legacy, 0), + ONE_LINE_PARSED(mapaddress, 0), ONE_LINE_PARSED(getinfo, 0), ONE_LINE(extendcircuit, legacy, 0), - ONE_LINE(setcircuitpurpose, legacy, 0), + ONE_LINE_PARSED(setcircuitpurpose, 0), OBSOLETE(setrouterpurpose), - ONE_LINE(attachstream, legacy, 0), - MULTLINE(postdescriptor, legacy, 0), + ONE_LINE_PARSED(attachstream, 0), + MULTLINE_PARSED(postdescriptor, 0), ONE_LINE_PARSED(redirectstream, 0), ONE_LINE_PARSED(closestream, 0), - ONE_LINE(closecircuit, legacy, 0), + ONE_LINE_PARSED(closecircuit, 0), ONE_LINE_PARSED(usefeature, 0), - ONE_LINE(resolve, legacy, 0), + ONE_LINE_PARSED(resolve, 0), ONE_LINE_PARSED(protocolinfo, 0), ONE_LINE(authchallenge, legacy, CMD_FL_WIPE), ONE_LINE_PARSED(dropguards, 0), - ONE_LINE(hsfetch, legacy, 0), - MULTLINE(hspost, legacy, 0), - ONE_LINE(add_onion, legacy, CMD_FL_WIPE), + ONE_LINE_PARSED(hsfetch, 0), + MULTLINE_PARSED(hspost, 0), + ONE_LINE_PARSED(add_onion, CMD_FL_WIPE), ONE_LINE_PARSED(del_onion, CMD_FL_WIPE), };