\n" "It appears you have configured your web browser to use Tor's control port" " as an HTTP proxy.\n" "This is not correct: Tor's default SOCKS proxy port is 9050.\n" "Please configure your client accordingly.\n" "
\n" "\n" "See " "https://www.torproject.org/documentation.html for more " "information.\n" "\n" "
\n" "\n" "\n"; /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ int connection_control_process_inbuf(control_connection_t *conn) { size_t data_len; uint32_t cmd_data_len; int cmd_len; char *args; tor_assert(conn); tor_assert(conn->base_.state == CONTROL_CONN_STATE_OPEN || conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH); if (!conn->incoming_cmd) { conn->incoming_cmd = tor_malloc(1024); conn->incoming_cmd_len = 1024; conn->incoming_cmd_cur_len = 0; } if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && peek_connection_has_control0_command(TO_CONN(conn))) { /* Detect v0 commands and send a "no more v0" message. */ size_t body_len; char buf[128]; set_uint16(buf+2, htons(0x0000)); /* type == error */ set_uint16(buf+4, htons(0x0001)); /* code == internal error */ strlcpy(buf+6, "The v0 control protocol is not supported by Tor 0.1.2.17 " "and later; upgrade your controller.", sizeof(buf)-6); body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); return 0; } /* If the user has the HTTP proxy port and the control port confused. */ if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && peek_connection_has_http_command(TO_CONN(conn))) { connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); connection_mark_and_flush(TO_CONN(conn)); return 0; } again: while (1) { size_t last_idx; int r; /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; r = connection_fetch_from_buf_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) /* Line not all here yet. Wait. */ return 0; else if (r == -1) { if (data_len + conn->incoming_cmd_cur_len > MAX_COMMAND_LINE_LENGTH) { connection_write_str_to_buf("500 Line too long.\r\n", conn); connection_stop_reading(TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); } while (conn->incoming_cmd_len < data_len+conn->incoming_cmd_cur_len) conn->incoming_cmd_len *= 2; conn->incoming_cmd = tor_realloc(conn->incoming_cmd, conn->incoming_cmd_len); } } while (r != 1); tor_assert(data_len); last_idx = conn->incoming_cmd_cur_len; conn->incoming_cmd_cur_len += (int)data_len; /* We have appended a line to incoming_cmd. Is the command done? */ if (last_idx == 0 && *conn->incoming_cmd != '+') /* One line command, didn't start with '+'. */ break; /* XXXX this code duplication is kind of dumb. */ if (last_idx+3 == conn->incoming_cmd_cur_len && tor_memeq(conn->incoming_cmd + last_idx, ".\r\n", 3)) { /* Just appended ".\r\n"; we're done. Remove it. */ conn->incoming_cmd[last_idx] = '\0'; conn->incoming_cmd_cur_len -= 3; break; } else if (last_idx+2 == conn->incoming_cmd_cur_len && tor_memeq(conn->incoming_cmd + last_idx, ".\n", 2)) { /* Just appended ".\n"; we're done. Remove it. */ conn->incoming_cmd[last_idx] = '\0'; conn->incoming_cmd_cur_len -= 2; break; } /* Otherwise, read another line. */ } data_len = conn->incoming_cmd_cur_len; /* Okay, we now have a command sitting on conn->incoming_cmd. See if we * recognize it. */ cmd_len = 0; while ((size_t)cmd_len < data_len && !TOR_ISSPACE(conn->incoming_cmd[cmd_len])) ++cmd_len; conn->incoming_cmd[cmd_len]='\0'; args = conn->incoming_cmd+cmd_len+1; tor_assert(data_len>(size_t)cmd_len); data_len -= (cmd_len+1); /* skip the command and NUL we added after it */ while (TOR_ISSPACE(*args)) { ++args; --data_len; } /* If the connection is already closing, ignore further commands */ if (TO_CONN(conn)->marked_for_close) { return 0; } /* Otherwise, Quit is always valid. */ if (!strcasecmp(conn->incoming_cmd, "QUIT")) { connection_write_str_to_buf("250 closing connection\r\n", conn); connection_mark_and_flush(TO_CONN(conn)); return 0; } if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && !is_valid_initial_command(conn, conn->incoming_cmd)) { connection_write_str_to_buf("514 Authentication required.\r\n", conn); connection_mark_for_close(TO_CONN(conn)); return 0; } if (data_len >= UINT32_MAX) { connection_write_str_to_buf("500 A 4GB command? Nice try.\r\n", conn); connection_mark_for_close(TO_CONN(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, "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); } conn->incoming_cmd_cur_len = 0; goto again; } /** Something major has happened to circuit circ: tell any * interested control connections. */ int control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp, int reason_code) { const char *status; char reasons[64] = ""; if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS)) return 0; tor_assert(circ); switch (tp) { case CIRC_EVENT_LAUNCHED: status = "LAUNCHED"; break; case CIRC_EVENT_BUILT: status = "BUILT"; break; case CIRC_EVENT_EXTENDED: status = "EXTENDED"; break; case CIRC_EVENT_FAILED: status = "FAILED"; break; case CIRC_EVENT_CLOSED: status = "CLOSED"; break; default: log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); tor_fragile_assert(); return 0; } if (tp == CIRC_EVENT_FAILED || tp == CIRC_EVENT_CLOSED) { const char *reason_str = circuit_end_reason_to_control_string(reason_code); char unk_reason_buf[16]; if (!reason_str) { tor_snprintf(unk_reason_buf, 16, "UNKNOWN_%d", reason_code); reason_str = unk_reason_buf; } if (reason_code > 0 && reason_code & END_CIRC_REASON_FLAG_REMOTE) { tor_snprintf(reasons, sizeof(reasons), " REASON=DESTROYED REMOTE_REASON=%s", reason_str); } else { tor_snprintf(reasons, sizeof(reasons), " REASON=%s", reason_str); } } { char *circdesc = circuit_describe_status_for_controller(circ); const char *sp = strlen(circdesc) ? " " : ""; send_control_event(EVENT_CIRCUIT_STATUS, "650 CIRC %lu %s%s%s%s\r\n", (unsigned long)circ->global_identifier, status, sp, circdesc, reasons); tor_free(circdesc); } return 0; } /** Something minor has happened to circuit circ: tell any * interested control connections. */ static int control_event_circuit_status_minor(origin_circuit_t *circ, circuit_status_minor_event_t e, int purpose, const struct timeval *tv) { const char *event_desc; char event_tail[160] = ""; if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS_MINOR)) return 0; tor_assert(circ); switch (e) { case CIRC_MINOR_EVENT_PURPOSE_CHANGED: event_desc = "PURPOSE_CHANGED"; { /* event_tail can currently be up to 68 chars long */ const char *hs_state_str = circuit_purpose_to_controller_hs_state_string(purpose); tor_snprintf(event_tail, sizeof(event_tail), " OLD_PURPOSE=%s%s%s", circuit_purpose_to_controller_string(purpose), (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", (hs_state_str != NULL) ? hs_state_str : ""); } break; case CIRC_MINOR_EVENT_CANNIBALIZED: event_desc = "CANNIBALIZED"; { /* event_tail can currently be up to 130 chars long */ const char *hs_state_str = circuit_purpose_to_controller_hs_state_string(purpose); const struct timeval *old_timestamp_began = tv; char tbuf[ISO_TIME_USEC_LEN+1]; format_iso_time_nospace_usec(tbuf, old_timestamp_began); tor_snprintf(event_tail, sizeof(event_tail), " OLD_PURPOSE=%s%s%s OLD_TIME_CREATED=%s", circuit_purpose_to_controller_string(purpose), (hs_state_str != NULL) ? " OLD_HS_STATE=" : "", (hs_state_str != NULL) ? hs_state_str : "", tbuf); } break; default: log_warn(LD_BUG, "Unrecognized status code %d", (int)e); tor_fragile_assert(); return 0; } { char *circdesc = circuit_describe_status_for_controller(circ); const char *sp = strlen(circdesc) ? " " : ""; send_control_event(EVENT_CIRCUIT_STATUS_MINOR, "650 CIRC_MINOR %lu %s%s%s%s\r\n", (unsigned long)circ->global_identifier, event_desc, sp, circdesc, event_tail); tor_free(circdesc); } return 0; } /** * circ has changed its purpose from old_purpose: tell any * interested controllers. */ int control_event_circuit_purpose_changed(origin_circuit_t *circ, int old_purpose) { return control_event_circuit_status_minor(circ, CIRC_MINOR_EVENT_PURPOSE_CHANGED, old_purpose, NULL); } /** * circ has changed its purpose from old_purpose, and its * created-time from old_tv_created: tell any interested controllers. */ int control_event_circuit_cannibalized(origin_circuit_t *circ, int old_purpose, const struct timeval *old_tv_created) { return control_event_circuit_status_minor(circ, CIRC_MINOR_EVENT_CANNIBALIZED, old_purpose, old_tv_created); } /** Given an AP connection conn and a len-character buffer * buf, determine the address:port combination requested on * conn, and write it to buf. Return 0 on success, -1 on * failure. */ static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len) { char buf2[256]; if (conn->chosen_exit_name) if (tor_snprintf(buf2, sizeof(buf2), ".%s.exit", conn->chosen_exit_name)<0) return -1; if (!conn->socks_request) return -1; if (tor_snprintf(buf, len, "%s%s%s:%d", conn->socks_request->address, conn->chosen_exit_name ? buf2 : "", !conn->chosen_exit_name && connection_edge_is_rendezvous_stream( ENTRY_TO_EDGE_CONN(conn)) ? ".onion" : "", conn->socks_request->port)<0) return -1; return 0; } /** Something has happened to the stream associated with AP connection * conn: tell any interested control connections. */ int control_event_stream_status(entry_connection_t *conn, stream_status_event_t tp, int reason_code) { char reason_buf[64]; char addrport_buf[64]; const char *status; circuit_t *circ; origin_circuit_t *origin_circ = NULL; char buf[256]; const char *purpose = ""; tor_assert(conn->socks_request); if (!EVENT_IS_INTERESTING(EVENT_STREAM_STATUS)) return 0; if (tp == STREAM_EVENT_CLOSED && (reason_code & END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED)) return 0; write_stream_target_to_buf(conn, buf, sizeof(buf)); reason_buf[0] = '\0'; switch (tp) { case STREAM_EVENT_SENT_CONNECT: status = "SENTCONNECT"; break; case STREAM_EVENT_SENT_RESOLVE: status = "SENTRESOLVE"; break; case STREAM_EVENT_SUCCEEDED: status = "SUCCEEDED"; break; case STREAM_EVENT_FAILED: status = "FAILED"; break; case STREAM_EVENT_CLOSED: status = "CLOSED"; break; case STREAM_EVENT_NEW: status = "NEW"; break; case STREAM_EVENT_NEW_RESOLVE: status = "NEWRESOLVE"; break; case STREAM_EVENT_FAILED_RETRIABLE: status = "DETACHED"; break; case STREAM_EVENT_REMAP: status = "REMAP"; break; default: log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); return 0; } if (reason_code && (tp == STREAM_EVENT_FAILED || tp == STREAM_EVENT_CLOSED || tp == STREAM_EVENT_FAILED_RETRIABLE)) { const char *reason_str = stream_end_reason_to_control_string(reason_code); char *r = NULL; if (!reason_str) { tor_asprintf(&r, " UNKNOWN_%d", reason_code); reason_str = r; } if (reason_code & END_STREAM_REASON_FLAG_REMOTE) tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=END REMOTE_REASON=%s", reason_str); else tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=%s", reason_str); tor_free(r); } else if (reason_code && tp == STREAM_EVENT_REMAP) { switch (reason_code) { case REMAP_STREAM_SOURCE_CACHE: strlcpy(reason_buf, " SOURCE=CACHE", sizeof(reason_buf)); break; case REMAP_STREAM_SOURCE_EXIT: strlcpy(reason_buf, " SOURCE=EXIT", sizeof(reason_buf)); break; default: tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=UNKNOWN_%d", reason_code); /* XXX do we want SOURCE=UNKNOWN_%d above instead? -RD */ break; } } if (tp == STREAM_EVENT_NEW || tp == STREAM_EVENT_NEW_RESOLVE) { /* * When the control conn is an AF_UNIX socket and we have no address, * it gets set to "(Tor_internal)"; see dnsserv_launch_request() in * dnsserv.c. */ if (strcmp(ENTRY_TO_CONN(conn)->address, "(Tor_internal)") != 0) { tor_snprintf(addrport_buf,sizeof(addrport_buf), " SOURCE_ADDR=%s:%d", ENTRY_TO_CONN(conn)->address, ENTRY_TO_CONN(conn)->port); } else { /* * else leave it blank so control on AF_UNIX doesn't need to make * something up. */ addrport_buf[0] = '\0'; } } else { addrport_buf[0] = '\0'; } if (tp == STREAM_EVENT_NEW_RESOLVE) { purpose = " PURPOSE=DNS_REQUEST"; } else if (tp == STREAM_EVENT_NEW) { if (conn->use_begindir) { connection_t *linked = ENTRY_TO_CONN(conn)->linked_conn; int linked_dir_purpose = -1; if (linked && linked->type == CONN_TYPE_DIR) linked_dir_purpose = linked->purpose; if (DIR_PURPOSE_IS_UPLOAD(linked_dir_purpose)) purpose = " PURPOSE=DIR_UPLOAD"; else purpose = " PURPOSE=DIR_FETCH"; } else purpose = " PURPOSE=USER"; } circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); if (circ && CIRCUIT_IS_ORIGIN(circ)) origin_circ = TO_ORIGIN_CIRCUIT(circ); send_control_event(EVENT_STREAM_STATUS, "650 STREAM "U64_FORMAT" %s %lu %s%s%s%s\r\n", U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), status, origin_circ? (unsigned long)origin_circ->global_identifier : 0ul, buf, reason_buf, addrport_buf, purpose); /* XXX need to specify its intended exit, etc? */ return 0; } /** Figure out the best name for the target router of an OR connection * conn, and write it into the len-character buffer * name. */ static void orconn_target_get_name(char *name, size_t len, or_connection_t *conn) { const node_t *node = node_get_by_id(conn->identity_digest); if (node) { tor_assert(len > MAX_VERBOSE_NICKNAME_LEN); node_get_verbose_nickname(node, name); } else if (! tor_digest_is_zero(conn->identity_digest)) { name[0] = '$'; base16_encode(name+1, len-1, conn->identity_digest, DIGEST_LEN); } else { tor_snprintf(name, len, "%s:%d", conn->base_.address, conn->base_.port); } } /** Called when the status of an OR connection conn changes: tell any * interested control connections. tp is the new status for the * connection. If conn has just closed or failed, then reason * may be the reason why. */ int control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, int reason) { int ncircs = 0; const char *status; char name[128]; char ncircs_buf[32] = {0}; /* > 8 + log10(2^32)=10 + 2 */ if (!EVENT_IS_INTERESTING(EVENT_OR_CONN_STATUS)) return 0; switch (tp) { case OR_CONN_EVENT_LAUNCHED: status = "LAUNCHED"; break; case OR_CONN_EVENT_CONNECTED: status = "CONNECTED"; break; case OR_CONN_EVENT_FAILED: status = "FAILED"; break; case OR_CONN_EVENT_CLOSED: status = "CLOSED"; break; case OR_CONN_EVENT_NEW: status = "NEW"; break; default: log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); return 0; } if (conn->chan) { ncircs = circuit_count_pending_on_channel(TLS_CHAN_TO_BASE(conn->chan)); } else { ncircs = 0; } ncircs += connection_or_get_num_circuits(conn); if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { tor_snprintf(ncircs_buf, sizeof(ncircs_buf), " NCIRCS=%d", ncircs); } orconn_target_get_name(name, sizeof(name), conn); send_control_event(EVENT_OR_CONN_STATUS, "650 ORCONN %s %s%s%s%s ID="U64_FORMAT"\r\n", name, status, reason ? " REASON=" : "", orconn_end_reason_to_control_string(reason), ncircs_buf, U64_PRINTF_ARG(conn->base_.global_identifier)); return 0; } /** * Print out STREAM_BW event for a single conn */ int control_event_stream_bandwidth(edge_connection_t *edge_conn) { circuit_t *circ; origin_circuit_t *ocirc; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; send_control_event(EVENT_STREAM_BANDWIDTH_USED, "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); circ = circuit_get_by_edge_conn(edge_conn); if (circ && CIRCUIT_IS_ORIGIN(circ)) { ocirc = TO_ORIGIN_CIRCUIT(circ); ocirc->n_read_circ_bw += edge_conn->n_read; ocirc->n_written_circ_bw += edge_conn->n_written; } edge_conn->n_written = edge_conn->n_read = 0; } return 0; } /** A second or more has elapsed: tell any interested control * connections how much bandwidth streams have used. */ int control_event_stream_bandwidth_used(void) { if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { smartlist_t *conns = get_connection_array(); edge_connection_t *edge_conn; SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type != CONN_TYPE_AP) continue; edge_conn = TO_EDGE_CONN(conn); if (!edge_conn->n_read && !edge_conn->n_written) continue; send_control_event(EVENT_STREAM_BANDWIDTH_USED, "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); edge_conn->n_written = edge_conn->n_read = 0; } SMARTLIST_FOREACH_END(conn); } return 0; } /** A second or more has elapsed: tell any interested control connections * how much bandwidth origin circuits have used. */ int control_event_circ_bandwidth_used(void) { origin_circuit_t *ocirc; if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) return 0; SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!CIRCUIT_IS_ORIGIN(circ)) continue; ocirc = TO_ORIGIN_CIRCUIT(circ); if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) continue; send_control_event(EVENT_CIRC_BANDWIDTH_USED, "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", ocirc->global_identifier, (unsigned long)ocirc->n_read_circ_bw, (unsigned long)ocirc->n_written_circ_bw); ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; } SMARTLIST_FOREACH_END(circ); return 0; } /** Print out CONN_BW event for a single OR/DIR/EXIT conn and reset * bandwidth counters. */ int control_event_conn_bandwidth(connection_t *conn) { const char *conn_type_str; if (!get_options()->TestingEnableConnBwEvent || !EVENT_IS_INTERESTING(EVENT_CONN_BW)) return 0; if (!conn->n_read_conn_bw && !conn->n_written_conn_bw) return 0; switch (conn->type) { case CONN_TYPE_OR: conn_type_str = "OR"; break; case CONN_TYPE_DIR: conn_type_str = "DIR"; break; case CONN_TYPE_EXIT: conn_type_str = "EXIT"; break; default: return 0; } send_control_event(EVENT_CONN_BW, "650 CONN_BW ID="U64_FORMAT" TYPE=%s " "READ=%lu WRITTEN=%lu\r\n", U64_PRINTF_ARG(conn->global_identifier), conn_type_str, (unsigned long)conn->n_read_conn_bw, (unsigned long)conn->n_written_conn_bw); conn->n_written_conn_bw = conn->n_read_conn_bw = 0; return 0; } /** A second or more has elapsed: tell any interested control * connections how much bandwidth connections have used. */ int control_event_conn_bandwidth_used(void) { if (get_options()->TestingEnableConnBwEvent && EVENT_IS_INTERESTING(EVENT_CONN_BW)) { SMARTLIST_FOREACH(get_connection_array(), connection_t *, conn, control_event_conn_bandwidth(conn)); } return 0; } /** Helper: iterate over cell statistics of circ and sum up added * cells, removed cells, and waiting times by cell command and direction. * Store results in cell_stats. Free cell statistics of the * circuit afterwards. */ void sum_up_cell_stats_by_command(circuit_t *circ, cell_stats_t *cell_stats) { memset(cell_stats, 0, sizeof(cell_stats_t)); SMARTLIST_FOREACH_BEGIN(circ->testing_cell_stats, const testing_cell_stats_entry_t *, ent) { tor_assert(ent->command <= CELL_COMMAND_MAX_); if (!ent->removed && !ent->exitward) { cell_stats->added_cells_appward[ent->command] += 1; } else if (!ent->removed && ent->exitward) { cell_stats->added_cells_exitward[ent->command] += 1; } else if (!ent->exitward) { cell_stats->removed_cells_appward[ent->command] += 1; cell_stats->total_time_appward[ent->command] += ent->waiting_time * 10; } else { cell_stats->removed_cells_exitward[ent->command] += 1; cell_stats->total_time_exitward[ent->command] += ent->waiting_time * 10; } } SMARTLIST_FOREACH_END(ent); circuit_clear_testing_cell_stats(circ); } /** Helper: append a cell statistics string toevent_parts
,
* prefixed with key
=. Statistics consist of comma-separated
* key:value pairs with lower-case command strings as keys and cell
* numbers or total waiting times as values. A key:value pair is included
* if the entry in include_if_non_zero
is not zero, but with
* the (possibly zero) entry from number_to_include
. Both
* arrays are expected to have a length of CELL_COMMAND_MAX_ + 1. If no
* entry in include_if_non_zero
is positive, no string will
* be added to event_parts
. */
void
append_cell_stats_by_command(smartlist_t *event_parts, const char *key,
const uint64_t *include_if_non_zero,
const uint64_t *number_to_include)
{
smartlist_t *key_value_strings = smartlist_new();
int i;
for (i = 0; i <= CELL_COMMAND_MAX_; i++) {
if (include_if_non_zero[i] > 0) {
smartlist_add_asprintf(key_value_strings, "%s:"U64_FORMAT,
cell_command_to_string(i),
U64_PRINTF_ARG(number_to_include[i]));
}
}
if (smartlist_len(key_value_strings) > 0) {
char *joined = smartlist_join_strings(key_value_strings, ",", 0, NULL);
smartlist_add_asprintf(event_parts, "%s=%s", key, joined);
SMARTLIST_FOREACH(key_value_strings, char *, cp, tor_free(cp));
tor_free(joined);
}
smartlist_free(key_value_strings);
}
/** Helper: format cell_stats for circ for inclusion in a
* CELL_STATS event and write result string to event_string. */
void
format_cell_stats(char **event_string, circuit_t *circ,
cell_stats_t *cell_stats)
{
smartlist_t *event_parts = smartlist_new();
if (CIRCUIT_IS_ORIGIN(circ)) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
smartlist_add_asprintf(event_parts, "ID=%lu",
(unsigned long)ocirc->global_identifier);
} else if (TO_OR_CIRCUIT(circ)->p_chan) {
or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
smartlist_add_asprintf(event_parts, "InboundQueue=%lu",
(unsigned long)or_circ->p_circ_id);
smartlist_add_asprintf(event_parts, "InboundConn="U64_FORMAT,
U64_PRINTF_ARG(or_circ->p_chan->global_identifier));
append_cell_stats_by_command(event_parts, "InboundAdded",
cell_stats->added_cells_appward,
cell_stats->added_cells_appward);
append_cell_stats_by_command(event_parts, "InboundRemoved",
cell_stats->removed_cells_appward,
cell_stats->removed_cells_appward);
append_cell_stats_by_command(event_parts, "InboundTime",
cell_stats->removed_cells_appward,
cell_stats->total_time_appward);
}
if (circ->n_chan) {
smartlist_add_asprintf(event_parts, "OutboundQueue=%lu",
(unsigned long)circ->n_circ_id);
smartlist_add_asprintf(event_parts, "OutboundConn="U64_FORMAT,
U64_PRINTF_ARG(circ->n_chan->global_identifier));
append_cell_stats_by_command(event_parts, "OutboundAdded",
cell_stats->added_cells_exitward,
cell_stats->added_cells_exitward);
append_cell_stats_by_command(event_parts, "OutboundRemoved",
cell_stats->removed_cells_exitward,
cell_stats->removed_cells_exitward);
append_cell_stats_by_command(event_parts, "OutboundTime",
cell_stats->removed_cells_exitward,
cell_stats->total_time_exitward);
}
*event_string = smartlist_join_strings(event_parts, " ", 0, NULL);
SMARTLIST_FOREACH(event_parts, char *, cp, tor_free(cp));
smartlist_free(event_parts);
}
/** A second or more has elapsed: tell any interested control connection
* how many cells have been processed for a given circuit. */
int
control_event_circuit_cell_stats(void)
{
cell_stats_t *cell_stats;
char *event_string;
if (!get_options()->TestingEnableCellStatsEvent ||
!EVENT_IS_INTERESTING(EVENT_CELL_STATS))
return 0;
cell_stats = tor_malloc(sizeof(cell_stats_t));;
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
if (!circ->testing_cell_stats)
continue;
sum_up_cell_stats_by_command(circ, cell_stats);
format_cell_stats(&event_string, circ, cell_stats);
send_control_event(EVENT_CELL_STATS,
"650 CELL_STATS %s\r\n", event_string);
tor_free(event_string);
}
SMARTLIST_FOREACH_END(circ);
tor_free(cell_stats);
return 0;
}
/** Tokens in bucket have been refilled: the read bucket was empty
* for read_empty_time millis, the write bucket was empty for
* write_empty_time millis, and buckets were last refilled
* milliseconds_elapsed millis ago. Only emit TB_EMPTY event if
* either read or write bucket have been empty before. */
int
control_event_tb_empty(const char *bucket, uint32_t read_empty_time,
uint32_t write_empty_time,
int milliseconds_elapsed)
{
if (get_options()->TestingEnableTbEmptyEvent &&
EVENT_IS_INTERESTING(EVENT_TB_EMPTY) &&
(read_empty_time > 0 || write_empty_time > 0)) {
send_control_event(EVENT_TB_EMPTY,
"650 TB_EMPTY %s READ=%d WRITTEN=%d "
"LAST=%d\r\n",
bucket, read_empty_time, write_empty_time,
milliseconds_elapsed);
}
return 0;
}
/* about 5 minutes worth. */
#define N_BW_EVENTS_TO_CACHE 300
/* Index into cached_bw_events to next write. */
static int next_measurement_idx = 0;
/* number of entries set in n_measurements */
static int n_measurements = 0;
static struct cached_bw_event_s {
uint32_t n_read;
uint32_t n_written;
} cached_bw_events[N_BW_EVENTS_TO_CACHE];
/** A second or more has elapsed: tell any interested control
* connections how much bandwidth we used. */
int
control_event_bandwidth_used(uint32_t n_read, uint32_t n_written)
{
cached_bw_events[next_measurement_idx].n_read = n_read;
cached_bw_events[next_measurement_idx].n_written = n_written;
if (++next_measurement_idx == N_BW_EVENTS_TO_CACHE)
next_measurement_idx = 0;
if (n_measurements < N_BW_EVENTS_TO_CACHE)
++n_measurements;
if (EVENT_IS_INTERESTING(EVENT_BANDWIDTH_USED)) {
send_control_event(EVENT_BANDWIDTH_USED,
"650 BW %lu %lu\r\n",
(unsigned long)n_read,
(unsigned long)n_written);
}
return 0;
}
STATIC char *
get_bw_samples(void)
{
int i;
int idx = (next_measurement_idx + N_BW_EVENTS_TO_CACHE - n_measurements)
% N_BW_EVENTS_TO_CACHE;
tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE);
smartlist_t *elements = smartlist_new();
for (i = 0; i < n_measurements; ++i) {
tor_assert(0 <= idx && idx < N_BW_EVENTS_TO_CACHE);
const struct cached_bw_event_s *bwe = &cached_bw_events[idx];
smartlist_add_asprintf(elements, "%u,%u",
(unsigned)bwe->n_read,
(unsigned)bwe->n_written);
idx = (idx + 1) % N_BW_EVENTS_TO_CACHE;
}
char *result = smartlist_join_strings(elements, " ", 0, NULL);
SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
smartlist_free(elements);
return result;
}
/** Called when we are sending a log message to the controllers: suspend
* sending further log messages to the controllers until we're done. Used by
* CONN_LOG_PROTECT. */
void
disable_control_logging(void)
{
++disable_log_messages;
}
/** We're done sending a log message to the controllers: re-enable controller
* logging. Used by CONN_LOG_PROTECT. */
void
enable_control_logging(void)
{
if (--disable_log_messages < 0)
tor_assert(0);
}
/** We got a log message: tell any interested control connections. */
void
control_event_logmsg(int severity, uint32_t domain, const char *msg)
{
int event;
/* Don't even think of trying to add stuff to a buffer from a cpuworker
* thread. */
if (! in_main_thread())
return;
if (disable_log_messages)
return;
if (domain == LD_BUG && EVENT_IS_INTERESTING(EVENT_STATUS_GENERAL) &&
severity <= LOG_NOTICE) {
char *esc = esc_for_log(msg);
++disable_log_messages;
control_event_general_status(severity, "BUG REASON=%s", esc);
--disable_log_messages;
tor_free(esc);
}
event = log_severity_to_event(severity);
if (event >= 0 && EVENT_IS_INTERESTING(event)) {
char *b = NULL;
const char *s;
if (strchr(msg, '\n')) {
char *cp;
b = tor_strdup(msg);
for (cp = b; *cp; ++cp)
if (*cp == '\r' || *cp == '\n')
*cp = ' ';
}
switch (severity) {
case LOG_DEBUG: s = "DEBUG"; break;
case LOG_INFO: s = "INFO"; break;
case LOG_NOTICE: s = "NOTICE"; break;
case LOG_WARN: s = "WARN"; break;
case LOG_ERR: s = "ERR"; break;
default: s = "UnknownLogSeverity"; break;
}
++disable_log_messages;
send_control_event(event, "650 %s %s\r\n", s, b?b:msg);
if (severity == LOG_ERR) {
/* Force a flush, since we may be about to die horribly */
queued_events_flush_all(1);
}
--disable_log_messages;
tor_free(b);
}
}
/** Called whenever we receive new router descriptors: tell any
* interested control connections. routers is a list of
* routerinfo_t's.
*/
int
control_event_descriptors_changed(smartlist_t *routers)
{
char *msg;
if (!EVENT_IS_INTERESTING(EVENT_NEW_DESC))
return 0;
{
smartlist_t *names = smartlist_new();
char *ids;
SMARTLIST_FOREACH(routers, routerinfo_t *, ri, {
char *b = tor_malloc(MAX_VERBOSE_NICKNAME_LEN+1);
router_get_verbose_nickname(b, ri);
smartlist_add(names, b);
});
ids = smartlist_join_strings(names, " ", 0, NULL);
tor_asprintf(&msg, "650 NEWDESC %s\r\n", ids);
send_control_event_string(EVENT_NEW_DESC, msg);
tor_free(ids);
tor_free(msg);
SMARTLIST_FOREACH(names, char *, cp, tor_free(cp));
smartlist_free(names);
}
return 0;
}
/** Called when an address mapping on from from changes to to.
* expires values less than 3 are special; see connection_edge.c. If
* error is non-NULL, it is an error code describing the failure
* mode of the mapping.
*/
int
control_event_address_mapped(const char *from, const char *to, time_t expires,
const char *error, const int cached)
{
if (!EVENT_IS_INTERESTING(EVENT_ADDRMAP))
return 0;
if (expires < 3 || expires == TIME_MAX)
send_control_event(EVENT_ADDRMAP,
"650 ADDRMAP %s %s NEVER %s%s"
"CACHED=\"%s\"\r\n",
from, to, error?error:"", error?" ":"",
cached?"YES":"NO");
else {
char buf[ISO_TIME_LEN+1];
char buf2[ISO_TIME_LEN+1];
format_local_iso_time(buf,expires);
format_iso_time(buf2,expires);
send_control_event(EVENT_ADDRMAP,
"650 ADDRMAP %s %s \"%s\""
" %s%sEXPIRES=\"%s\" CACHED=\"%s\"\r\n",
from, to, buf,
error?error:"", error?" ":"",
buf2, cached?"YES":"NO");
}
return 0;
}
/** The authoritative dirserver has received a new descriptor that
* has passed basic syntax checks and is properly self-signed.
*
* Notify any interested party of the new descriptor and what has
* been done with it, and also optionally give an explanation/reason. */
int
control_event_or_authdir_new_descriptor(const char *action,
const char *desc, size_t desclen,
const char *msg)
{
char firstline[1024];
char *buf;
size_t totallen;
char *esc = NULL;
size_t esclen;
if (!EVENT_IS_INTERESTING(EVENT_AUTHDIR_NEWDESCS))
return 0;
tor_snprintf(firstline, sizeof(firstline),
"650+AUTHDIR_NEWDESC=\r\n%s\r\n%s\r\n",
action,
msg ? msg : "");
/* Escape the server descriptor properly */
esclen = write_escaped_data(desc, desclen, &esc);
totallen = strlen(firstline) + esclen + 1;
buf = tor_malloc(totallen);
strlcpy(buf, firstline, totallen);
strlcpy(buf+strlen(firstline), esc, totallen);
send_control_event_string(EVENT_AUTHDIR_NEWDESCS,
buf);
send_control_event_string(EVENT_AUTHDIR_NEWDESCS,
"650 OK\r\n");
tor_free(esc);
tor_free(buf);
return 0;
}
/** Cached liveness for network liveness events and GETINFO
*/
static int network_is_live = 0;
static int
get_cached_network_liveness(void)
{
return network_is_live;
}
static void
set_cached_network_liveness(int liveness)
{
network_is_live = liveness;
}
/** The network liveness has changed; this is called from circuitstats.c
* whenever we receive a cell, or when timeout expires and we assume the
* network is down. */
int
control_event_network_liveness_update(int liveness)
{
if (liveness > 0) {
if (get_cached_network_liveness() <= 0) {
/* Update cached liveness */
set_cached_network_liveness(1);
log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS UP");
send_control_event_string(EVENT_NETWORK_LIVENESS,
"650 NETWORK_LIVENESS UP\r\n");
}
/* else was already live, no-op */
} else {
if (get_cached_network_liveness() > 0) {
/* Update cached liveness */
set_cached_network_liveness(0);
log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS DOWN");
send_control_event_string(EVENT_NETWORK_LIVENESS,
"650 NETWORK_LIVENESS DOWN\r\n");
}
/* else was already dead, no-op */
}
return 0;
}
/** Helper function for NS-style events. Constructs and sends an event
* of type event with string event_string out of the set of
* networkstatuses statuses. Currently it is used for NS events
* and NEWCONSENSUS events. */
static int
control_event_networkstatus_changed_helper(smartlist_t *statuses,
uint16_t event,
const char *event_string)
{
smartlist_t *strs;
char *s, *esc = NULL;
if (!EVENT_IS_INTERESTING(event) || !smartlist_len(statuses))
return 0;
strs = smartlist_new();
smartlist_add_strdup(strs, "650+");
smartlist_add_strdup(strs, event_string);
smartlist_add_strdup(strs, "\r\n");
SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs,
{
s = networkstatus_getinfo_helper_single(rs);
if (!s) continue;
smartlist_add(strs, s);
});
s = smartlist_join_strings(strs, "", 0, NULL);
write_escaped_data(s, strlen(s), &esc);
SMARTLIST_FOREACH(strs, char *, cp, tor_free(cp));
smartlist_free(strs);
tor_free(s);
send_control_event_string(event, esc);
send_control_event_string(event,
"650 OK\r\n");
tor_free(esc);
return 0;
}
/** Called when the routerstatus_ts statuses have changed: sends
* an NS event to any controller that cares. */
int
control_event_networkstatus_changed(smartlist_t *statuses)
{
return control_event_networkstatus_changed_helper(statuses, EVENT_NS, "NS");
}
/** Called when we get a new consensus networkstatus. Sends a NEWCONSENSUS
* event consisting of an NS-style line for each relay in the consensus. */
int
control_event_newconsensus(const networkstatus_t *consensus)
{
if (!control_event_is_interesting(EVENT_NEWCONSENSUS))
return 0;
return control_event_networkstatus_changed_helper(
consensus->routerstatus_list, EVENT_NEWCONSENSUS, "NEWCONSENSUS");
}
/** Called when we compute a new circuitbuildtimeout */
int
control_event_buildtimeout_set(buildtimeout_set_event_t type,
const char *args)
{
const char *type_string = NULL;
if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET))
return 0;
switch (type) {
case BUILDTIMEOUT_SET_EVENT_COMPUTED:
type_string = "COMPUTED";
break;
case BUILDTIMEOUT_SET_EVENT_RESET:
type_string = "RESET";
break;
case BUILDTIMEOUT_SET_EVENT_SUSPENDED:
type_string = "SUSPENDED";
break;
case BUILDTIMEOUT_SET_EVENT_DISCARD:
type_string = "DISCARD";
break;
case BUILDTIMEOUT_SET_EVENT_RESUME:
type_string = "RESUME";
break;
default:
type_string = "UNKNOWN";
break;
}
send_control_event(EVENT_BUILDTIMEOUT_SET,
"650 BUILDTIMEOUT_SET %s %s\r\n",
type_string, args);
return 0;
}
/** Called when a signal has been processed from signal_callback */
int
control_event_signal(uintptr_t signal_num)
{
const char *signal_string = NULL;
if (!control_event_is_interesting(EVENT_GOT_SIGNAL))
return 0;
switch (signal_num) {
case SIGHUP:
signal_string = "RELOAD";
break;
case SIGUSR1:
signal_string = "DUMP";
break;
case SIGUSR2:
signal_string = "DEBUG";
break;
case SIGNEWNYM:
signal_string = "NEWNYM";
break;
case SIGCLEARDNSCACHE:
signal_string = "CLEARDNSCACHE";
break;
case SIGHEARTBEAT:
signal_string = "HEARTBEAT";
break;
default:
log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal",
(unsigned long)signal_num);
return -1;
}
send_control_event(EVENT_GOT_SIGNAL, "650 SIGNAL %s\r\n",
signal_string);
return 0;
}
/** Called when a single local_routerstatus_t has changed: Sends an NS event
* to any controller that cares. */
int
control_event_networkstatus_changed_single(const routerstatus_t *rs)
{
smartlist_t *statuses;
int r;
if (!EVENT_IS_INTERESTING(EVENT_NS))
return 0;
statuses = smartlist_new();
smartlist_add(statuses, (void*)rs);
r = control_event_networkstatus_changed(statuses);
smartlist_free(statuses);
return r;
}
/** Our own router descriptor has changed; tell any controllers that care.
*/
int
control_event_my_descriptor_changed(void)
{
send_control_event(EVENT_DESCCHANGED, "650 DESCCHANGED\r\n");
return 0;
}
/** Helper: sends a status event where type is one of
* EVENT_STATUS_{GENERAL,CLIENT,SERVER}, where severity is one of
* LOG_{NOTICE,WARN,ERR}, and where format is a printf-style format
* string corresponding to args. */
static int
control_event_status(int type, int severity, const char *format, va_list args)
{
char *user_buf = NULL;
char format_buf[160];
const char *status, *sev;
switch (type) {
case EVENT_STATUS_GENERAL:
status = "STATUS_GENERAL";
break;
case EVENT_STATUS_CLIENT:
status = "STATUS_CLIENT";
break;
case EVENT_STATUS_SERVER:
status = "STATUS_SERVER";
break;
default:
log_warn(LD_BUG, "Unrecognized status type %d", type);
return -1;
}
switch (severity) {
case LOG_NOTICE:
sev = "NOTICE";
break;
case LOG_WARN:
sev = "WARN";
break;
case LOG_ERR:
sev = "ERR";
break;
default:
log_warn(LD_BUG, "Unrecognized status severity %d", severity);
return -1;
}
if (tor_snprintf(format_buf, sizeof(format_buf), "650 %s %s",
status, sev)<0) {
log_warn(LD_BUG, "Format string too long.");
return -1;
}
tor_vasprintf(&user_buf, format, args);
send_control_event(type, "%s %s\r\n", format_buf, user_buf);
tor_free(user_buf);
return 0;
}
#define CONTROL_EVENT_STATUS_BODY(event, sev) \
int r; \
do { \
va_list ap; \
if (!EVENT_IS_INTERESTING(event)) \
return 0; \
\
va_start(ap, format); \
r = control_event_status((event), (sev), format, ap); \
va_end(ap); \
} while (0)
/** Format and send an EVENT_STATUS_GENERAL event whose main text is obtained
* by formatting the arguments using the printf-style format. */
int
control_event_general_status(int severity, const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, severity);
return r;
}
/** Format and send an EVENT_STATUS_GENERAL LOG_ERR event, and flush it to the
* controller(s) immediately. */
int
control_event_general_error(const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_GENERAL, LOG_ERR);
/* Force a flush, since we may be about to die horribly */
queued_events_flush_all(1);
return r;
}
/** Format and send an EVENT_STATUS_CLIENT event whose main text is obtained
* by formatting the arguments using the printf-style format. */
int
control_event_client_status(int severity, const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, severity);
return r;
}
/** Format and send an EVENT_STATUS_CLIENT LOG_ERR event, and flush it to the
* controller(s) immediately. */
int
control_event_client_error(const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_CLIENT, LOG_ERR);
/* Force a flush, since we may be about to die horribly */
queued_events_flush_all(1);
return r;
}
/** Format and send an EVENT_STATUS_SERVER event whose main text is obtained
* by formatting the arguments using the printf-style format. */
int
control_event_server_status(int severity, const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, severity);
return r;
}
/** Format and send an EVENT_STATUS_SERVER LOG_ERR event, and flush it to the
* controller(s) immediately. */
int
control_event_server_error(const char *format, ...)
{
CONTROL_EVENT_STATUS_BODY(EVENT_STATUS_SERVER, LOG_ERR);
/* Force a flush, since we may be about to die horribly */
queued_events_flush_all(1);
return r;
}
/** Called when the status of an entry guard with the given nickname
* and identity digest has changed to status: tells any
* controllers that care. */
int
control_event_guard(const char *nickname, const char *digest,
const char *status)
{
char hbuf[HEX_DIGEST_LEN+1];
base16_encode(hbuf, sizeof(hbuf), digest, DIGEST_LEN);
if (!EVENT_IS_INTERESTING(EVENT_GUARD))
return 0;
{
char buf[MAX_VERBOSE_NICKNAME_LEN+1];
const node_t *node = node_get_by_id(digest);
if (node) {
node_get_verbose_nickname(node, buf);
} else {
tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname);
}
send_control_event(EVENT_GUARD,
"650 GUARD ENTRY %s %s\r\n", buf, status);
}
return 0;
}
/** Called when a configuration option changes. This is generally triggered
* by SETCONF requests and RELOAD/SIGHUP signals. The elements is
* a smartlist_t containing (key, value, ...) pairs in sequence.
* value can be NULL. */
int
control_event_conf_changed(const smartlist_t *elements)
{
int i;
char *result;
smartlist_t *lines;
if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) ||
smartlist_len(elements) == 0) {
return 0;
}
lines = smartlist_new();
for (i = 0; i < smartlist_len(elements); i += 2) {
char *k = smartlist_get(elements, i);
char *v = smartlist_get(elements, i+1);
if (v == NULL) {
smartlist_add_asprintf(lines, "650-%s", k);
} else {
smartlist_add_asprintf(lines, "650-%s=%s", k, v);
}
}
result = smartlist_join_strings(lines, "\r\n", 0, NULL);
send_control_event(EVENT_CONF_CHANGED,
"650-CONF_CHANGED\r\n%s\r\n650 OK\r\n", result);
tor_free(result);
SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
smartlist_free(lines);
return 0;
}
/** 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 enabled 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;
/** A process-termination monitor for Tor's owning controller, or NULL
* if this Tor instance is not currently owned by a process. */
static tor_process_monitor_t *owning_controller_process_monitor = NULL;
/** Process-termination monitor callback for Tor's owning controller
* process. */
static void
owning_controller_procmon_cb(void *unused)
{
(void)unused;
lost_owning_controller("process", "vanished");
}
/** Set process_spec as Tor's owning controller process.
* Exit on failure. */
void
monitor_owning_controller_process(const char *process_spec)
{
const char *msg;
tor_assert((owning_controller_process_spec == NULL) ==
(owning_controller_process_monitor == NULL));
if (owning_controller_process_spec != NULL) {
if ((process_spec != NULL) && !strcmp(process_spec,
owning_controller_process_spec)) {
/* Same process -- return now, instead of disposing of and
* recreating the process-termination monitor. */
return;
}
/* We are currently owned by a process, and we should no longer be
* owned by it. Free the process-termination monitor. */
tor_process_monitor_free(owning_controller_process_monitor);
owning_controller_process_monitor = NULL;
tor_free(owning_controller_process_spec);
owning_controller_process_spec = NULL;
}
tor_assert((owning_controller_process_spec == NULL) &&
(owning_controller_process_monitor == NULL));
if (process_spec == NULL)
return;
owning_controller_process_spec = tor_strdup(process_spec);
owning_controller_process_monitor =
tor_process_monitor_new(tor_libevent_get_base(),
owning_controller_process_spec,
LD_CONTROL,
owning_controller_procmon_cb, NULL,
&msg);
if (owning_controller_process_monitor == NULL) {
log_err(LD_BUG, "Couldn't create process-termination monitor for "
"owning controller: %s. Exiting.",
msg);
owning_controller_process_spec = NULL;
tor_cleanup();
exit(1);
}
}
/** Convert the name of a bootstrapping phase s into strings
* tag and summary suitable for display by the controller. */
static int
bootstrap_status_to_string(bootstrap_status_t s, const char **tag,
const char **summary)
{
switch (s) {
case BOOTSTRAP_STATUS_UNDEF:
*tag = "undef";
*summary = "Undefined";
break;
case BOOTSTRAP_STATUS_STARTING:
*tag = "starting";
*summary = "Starting";
break;
case BOOTSTRAP_STATUS_CONN_DIR:
*tag = "conn_dir";
*summary = "Connecting to directory server";
break;
case BOOTSTRAP_STATUS_HANDSHAKE:
*tag = "status_handshake";
*summary = "Finishing handshake";
break;
case BOOTSTRAP_STATUS_HANDSHAKE_DIR:
*tag = "handshake_dir";
*summary = "Finishing handshake with directory server";
break;
case BOOTSTRAP_STATUS_ONEHOP_CREATE:
*tag = "onehop_create";
*summary = "Establishing an encrypted directory connection";
break;
case BOOTSTRAP_STATUS_REQUESTING_STATUS:
*tag = "requesting_status";
*summary = "Asking for networkstatus consensus";
break;
case BOOTSTRAP_STATUS_LOADING_STATUS:
*tag = "loading_status";
*summary = "Loading networkstatus consensus";
break;
case BOOTSTRAP_STATUS_LOADING_KEYS:
*tag = "loading_keys";
*summary = "Loading authority key certs";
break;
case BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS:
*tag = "requesting_descriptors";
/* XXXX this appears to incorrectly report internal on most loads */
*summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ?
"Asking for relay descriptors for internal paths" :
"Asking for relay descriptors";
break;
/* If we're sure there are no exits in the consensus,
* inform the controller by adding "internal"
* to the status summaries.
* (We only check this while loading descriptors,
* so we may not know in the earlier stages.)
* But if there are exits, we can't be sure whether
* we're creating internal or exit paths/circuits.
* XXXX Or should be use different tags or statuses
* for internal and exit/all? */
case BOOTSTRAP_STATUS_LOADING_DESCRIPTORS:
*tag = "loading_descriptors";
*summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ?
"Loading relay descriptors for internal paths" :
"Loading relay descriptors";
break;
case BOOTSTRAP_STATUS_CONN_OR:
*tag = "conn_or";
*summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ?
"Connecting to the Tor network internally" :
"Connecting to the Tor network";
break;
case BOOTSTRAP_STATUS_HANDSHAKE_OR:
*tag = "handshake_or";
*summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ?
"Finishing handshake with first hop of internal circuit" :
"Finishing handshake with first hop";
break;
case BOOTSTRAP_STATUS_CIRCUIT_CREATE:
*tag = "circuit_create";
*summary = router_have_consensus_path() == CONSENSUS_PATH_INTERNAL ?
"Establishing an internal Tor circuit" :
"Establishing a Tor circuit";
break;
case BOOTSTRAP_STATUS_DONE:
*tag = "done";
*summary = "Done";
break;
default:
// log_warn(LD_BUG, "Unrecognized bootstrap status code %d", s);
*tag = *summary = "unknown";
return -1;
}
return 0;
}
/** What percentage through the bootstrap process are we? We remember
* this so we can avoid sending redundant bootstrap status events, and
* so we can guess context for the bootstrap messages which are
* ambiguous. It starts at 'undef', but gets set to 'starting' while
* Tor initializes. */
static int bootstrap_percent = BOOTSTRAP_STATUS_UNDEF;
/** As bootstrap_percent, but holds the bootstrapping level at which we last
* logged a NOTICE-level message. We use this, plus BOOTSTRAP_PCT_INCREMENT,
* to avoid flooding the log with a new message every time we get a few more
* microdescriptors */
static int notice_bootstrap_percent = 0;
/** How many problems have we had getting to the next bootstrapping phase?
* These include failure to establish a connection to a Tor relay,
* failures to finish the TLS handshake, failures to validate the
* consensus document, etc. */
static int bootstrap_problems = 0;
/** We only tell the controller once we've hit a threshold of problems
* for the current phase. */
#define BOOTSTRAP_PROBLEM_THRESHOLD 10
/** When our bootstrapping progress level changes, but our bootstrapping
* status has not advanced, we only log at NOTICE when we have made at least
* this much progress.
*/
#define BOOTSTRAP_PCT_INCREMENT 5
/** Called when Tor has made progress at bootstrapping its directory
* information and initial circuits.
*
* status is the new status, that is, what task we will be doing
* next. progress is zero if we just started this task, else it
* represents progress on the task.
*
* Return true if we logged a message at level NOTICE, and false otherwise.
*/
int
control_event_bootstrap(bootstrap_status_t status, int progress)
{
const char *tag, *summary;
char buf[BOOTSTRAP_MSG_LEN];
if (bootstrap_percent == BOOTSTRAP_STATUS_DONE)
return 0; /* already bootstrapped; nothing to be done here. */
/* special case for handshaking status, since our TLS handshaking code
* can't distinguish what the connection is going to be for. */
if (status == BOOTSTRAP_STATUS_HANDSHAKE) {
if (bootstrap_percent < BOOTSTRAP_STATUS_CONN_OR) {
status = BOOTSTRAP_STATUS_HANDSHAKE_DIR;
} else {
status = BOOTSTRAP_STATUS_HANDSHAKE_OR;
}
}
if (status > bootstrap_percent ||
(progress && progress > bootstrap_percent)) {
int loglevel = LOG_NOTICE;
bootstrap_status_to_string(status, &tag, &summary);
if (status <= bootstrap_percent &&
(progress < notice_bootstrap_percent + BOOTSTRAP_PCT_INCREMENT)) {
/* We log the message at info if the status hasn't advanced, and if less
* than BOOTSTRAP_PCT_INCREMENT progress has been made.
*/
loglevel = LOG_INFO;
}
tor_log(loglevel, LD_CONTROL,
"Bootstrapped %d%%: %s", progress ? progress : status, summary);
tor_snprintf(buf, sizeof(buf),
"BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\"",
progress ? progress : status, tag, summary);
tor_snprintf(last_sent_bootstrap_message,
sizeof(last_sent_bootstrap_message),
"NOTICE %s", buf);
control_event_client_status(LOG_NOTICE, "%s", buf);
if (status > bootstrap_percent) {
bootstrap_percent = status; /* new milestone reached */
}
if (progress > bootstrap_percent) {
/* incremental progress within a milestone */
bootstrap_percent = progress;
bootstrap_problems = 0; /* Progress! Reset our problem counter. */
}
if (loglevel == LOG_NOTICE &&
bootstrap_percent > notice_bootstrap_percent) {
/* Remember that we gave a notice at this level. */
notice_bootstrap_percent = bootstrap_percent;
}
return loglevel == LOG_NOTICE;
}
return 0;
}
/** Called when Tor has failed to make bootstrapping progress in a way
* that indicates a problem. warn gives a hint as to why, and
* reason provides an "or_conn_end_reason" tag. or_conn
* is the connection that caused this problem.
*/
MOCK_IMPL(void,
control_event_bootstrap_problem, (const char *warn, int reason,
or_connection_t *or_conn))
{
int status = bootstrap_percent;
const char *tag = "", *summary = "";
char buf[BOOTSTRAP_MSG_LEN];
const char *recommendation = "ignore";
int severity;
/* bootstrap_percent must not be in "undefined" state here. */
tor_assert(status >= 0);
if (or_conn->have_noted_bootstrap_problem)
return;
or_conn->have_noted_bootstrap_problem = 1;
if (bootstrap_percent == 100)
return; /* already bootstrapped; nothing to be done here. */
bootstrap_problems++;
if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD)
recommendation = "warn";
if (reason == END_OR_CONN_REASON_NO_ROUTE)
recommendation = "warn";
/* If we are using bridges and all our OR connections are now
closed, it means that we totally failed to connect to our
bridges. Throw a warning. */
if (get_options()->UseBridges && !any_other_active_or_conns(or_conn))
recommendation = "warn";
if (we_are_hibernating())
recommendation = "ignore";
while (status>=0 && bootstrap_status_to_string(status, &tag, &summary) < 0)
status--; /* find a recognized status string based on current progress */
status = bootstrap_percent; /* set status back to the actual number */
severity = !strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO;
log_fn(severity,
LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; "
"count %d; recommendation %s; host %s at %s:%d)",
status, summary, warn,
orconn_end_reason_to_control_string(reason),
bootstrap_problems, recommendation,
hex_str(or_conn->identity_digest, DIGEST_LEN),
or_conn->base_.address,
or_conn->base_.port);
connection_or_report_broken_states(severity, LD_HANDSHAKE);
tor_snprintf(buf, sizeof(buf),
"BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s "
"COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s:%d\"",
bootstrap_percent, tag, summary, warn,
orconn_end_reason_to_control_string(reason), bootstrap_problems,
recommendation,
hex_str(or_conn->identity_digest, DIGEST_LEN),
or_conn->base_.address,
(int)or_conn->base_.port);
tor_snprintf(last_sent_bootstrap_message,
sizeof(last_sent_bootstrap_message),
"WARN %s", buf);
control_event_client_status(LOG_WARN, "%s", buf);
}
/** We just generated a new summary of which countries we've seen clients
* from recently. Send a copy to the controller in case it wants to
* display it for the user. */
void
control_event_clients_seen(const char *controller_str)
{
send_control_event(EVENT_CLIENTS_SEEN,
"650 CLIENTS_SEEN %s\r\n", controller_str);
}
/** A new pluggable transport called transport_name was
* launched on addr:port. mode is either
* "server" or "client" depending on the mode of the pluggable
* transport.
* "650" SP "TRANSPORT_LAUNCHED" SP Mode SP Name SP Address SP Port
*/
void
control_event_transport_launched(const char *mode, const char *transport_name,
tor_addr_t *addr, uint16_t port)
{
send_control_event(EVENT_TRANSPORT_LAUNCHED,
"650 TRANSPORT_LAUNCHED %s %s %s %u\r\n",
mode, transport_name, fmt_addr(addr), port);
}
/** Convert rendezvous auth type to string for HS_DESC control events
*/
const char *
rend_auth_type_to_string(rend_auth_type_t auth_type)
{
const char *str;
switch (auth_type) {
case REND_NO_AUTH:
str = "NO_AUTH";
break;
case REND_BASIC_AUTH:
str = "BASIC_AUTH";
break;
case REND_STEALTH_AUTH:
str = "STEALTH_AUTH";
break;
default:
str = "UNKNOWN";
}
return str;
}
/** Return a longname the node whose identity is id_digest. If
* node_get_by_id() returns NULL, base 16 encoding of id_digest is
* returned instead.
*
* This function is not thread-safe. Each call to this function invalidates
* previous values returned by this function.
*/
MOCK_IMPL(const char *,
node_describe_longname_by_id,(const char *id_digest))
{
static char longname[MAX_VERBOSE_NICKNAME_LEN+1];
node_get_verbose_nickname_by_id(id_digest, longname);
return longname;
}
/** Return either the onion address if the given pointer is a non empty
* string else the unknown string. */
static const char *
rend_hsaddress_str_or_unknown(const char *onion_address)
{
static const char *str_unknown = "UNKNOWN";
const char *str_ret = str_unknown;
/* No valid pointer, unknown it is. */
if (!onion_address) {
goto end;
}
/* Empty onion address thus we don't know, unknown it is. */
if (onion_address[0] == '\0') {
goto end;
}
/* All checks are good so return the given onion address. */
str_ret = onion_address;
end:
return str_ret;
}
/** send HS_DESC requested event.
*
* rend_query is used to fetch requested onion address and auth type.
* hs_dir is the description of contacting hs directory.
* desc_id_base32 is the ID of requested hs descriptor.
*/
void
control_event_hs_descriptor_requested(const rend_data_t *rend_query,
const char *id_digest,
const char *desc_id_base32)
{
if (!id_digest || !rend_query || !desc_id_base32) {
log_warn(LD_BUG, "Called with rend_query==%p, "
"id_digest==%p, desc_id_base32==%p",
rend_query, id_digest, desc_id_base32);
return;
}
send_control_event(EVENT_HS_DESC,
"650 HS_DESC REQUESTED %s %s %s %s\r\n",
rend_hsaddress_str_or_unknown(
rend_data_get_address(rend_query)),
rend_auth_type_to_string(
TO_REND_DATA_V2(rend_query)->auth_type),
node_describe_longname_by_id(id_digest),
desc_id_base32);
}
/** For an HS descriptor query rend_data, using the
* onion_address and HSDir fingerprint hsdir_fp, find out
* which descriptor ID in the query is the right one.
*
* Return a pointer of the binary descriptor ID found in the query's object
* or NULL if not found. */
static const char *
get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp)
{
int replica;
const char *desc_id = NULL;
const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data);
/* Possible if the fetch was done using a descriptor ID. This means that
* the HSFETCH command was used. */
if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) {
desc_id = rend_data_v2->desc_id_fetch;
goto end;
}
/* Without a directory fingerprint at this stage, we can't do much. */
if (hsdir_fp == NULL) {
goto end;
}
/* OK, we have an onion address so now let's find which descriptor ID
* is the one associated with the HSDir fingerprint. */
for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
replica++) {
const char *digest = rend_data_get_desc_id(rend_data, replica, NULL);
SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) {
if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) {
/* Found it! This descriptor ID is the right one. */
desc_id = digest;
goto end;
}
} SMARTLIST_FOREACH_END(fingerprint);
}
end:
return desc_id;
}
/** send HS_DESC CREATED event when a local service generates a descriptor.
*
* service_id is the descriptor onion address.
* desc_id_base32 is the descriptor ID.
* replica is the the descriptor replica number.
*/
void
control_event_hs_descriptor_created(const char *service_id,
const char *desc_id_base32,
int replica)
{
if (!service_id || !desc_id_base32) {
log_warn(LD_BUG, "Called with service_digest==%p, "
"desc_id_base32==%p", service_id, desc_id_base32);
return;
}
send_control_event(EVENT_HS_DESC,
"650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s "
"REPLICA=%d\r\n",
service_id,
desc_id_base32,
replica);
}
/** send HS_DESC upload event.
*
* service_id is the descriptor onion address.
* hs_dir is the description of contacting hs directory.
* desc_id_base32 is the ID of requested hs descriptor.
*/
void
control_event_hs_descriptor_upload(const char *service_id,
const char *id_digest,
const char *desc_id_base32)
{
if (!service_id || !id_digest || !desc_id_base32) {
log_warn(LD_BUG, "Called with service_digest==%p, "
"desc_id_base32==%p, id_digest==%p", service_id,
desc_id_base32, id_digest);
return;
}
send_control_event(EVENT_HS_DESC,
"650 HS_DESC UPLOAD %s UNKNOWN %s %s\r\n",
service_id,
node_describe_longname_by_id(id_digest),
desc_id_base32);
}
/** send HS_DESC event after got response from hs directory.
*
* NOTE: this is an internal function used by following functions:
* control_event_hs_descriptor_received
* control_event_hs_descriptor_failed
*
* So do not call this function directly.
*/
void
control_event_hs_descriptor_receive_end(const char *action,
const char *onion_address,
const rend_data_t *rend_data,
const char *id_digest,
const char *reason)
{
char *desc_id_field = NULL;
char *reason_field = NULL;
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
const char *desc_id = NULL;
if (!action || !rend_data || !onion_address) {
log_warn(LD_BUG, "Called with action==%p, rend_data==%p, "
"onion_address==%p", action, rend_data, onion_address);
return;
}
desc_id = get_desc_id_from_query(rend_data, id_digest);
if (desc_id != NULL) {
/* Set the descriptor ID digest to base32 so we can send it. */
base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id,
DIGEST_LEN);
/* Extra whitespace is needed before the value. */
tor_asprintf(&desc_id_field, " %s", desc_id_base32);
}
if (reason) {
tor_asprintf(&reason_field, " REASON=%s", reason);
}
send_control_event(EVENT_HS_DESC,
"650 HS_DESC %s %s %s %s%s%s\r\n",
action,
rend_hsaddress_str_or_unknown(onion_address),
rend_auth_type_to_string(
TO_REND_DATA_V2(rend_data)->auth_type),
id_digest ?
node_describe_longname_by_id(id_digest) : "UNKNOWN",
desc_id_field ? desc_id_field : "",
reason_field ? reason_field : "");
tor_free(desc_id_field);
tor_free(reason_field);
}
/** send HS_DESC event after got response from hs directory.
*
* NOTE: this is an internal function used by following functions:
* control_event_hs_descriptor_uploaded
* control_event_hs_descriptor_upload_failed
*
* So do not call this function directly.
*/
void
control_event_hs_descriptor_upload_end(const char *action,
const char *onion_address,
const char *id_digest,
const char *reason)
{
char *reason_field = NULL;
if (!action || !id_digest) {
log_warn(LD_BUG, "Called with action==%p, id_digest==%p", action,
id_digest);
return;
}
if (reason) {
tor_asprintf(&reason_field, " REASON=%s", reason);
}
send_control_event(EVENT_HS_DESC,
"650 HS_DESC %s %s UNKNOWN %s%s\r\n",
action,
rend_hsaddress_str_or_unknown(onion_address),
node_describe_longname_by_id(id_digest),
reason_field ? reason_field : "");
tor_free(reason_field);
}
/** send HS_DESC RECEIVED event
*
* called when we successfully received a hidden service descriptor.
*/
void
control_event_hs_descriptor_received(const char *onion_address,
const rend_data_t *rend_data,
const char *id_digest)
{
if (!rend_data || !id_digest || !onion_address) {
log_warn(LD_BUG, "Called with rend_data==%p, id_digest==%p, "
"onion_address==%p", rend_data, id_digest, onion_address);
return;
}
control_event_hs_descriptor_receive_end("RECEIVED", onion_address,
rend_data, id_digest, NULL);
}
/** send HS_DESC UPLOADED event
*
* called when we successfully uploaded a hidden service descriptor.
*/
void
control_event_hs_descriptor_uploaded(const char *id_digest,
const char *onion_address)
{
if (!id_digest) {
log_warn(LD_BUG, "Called with id_digest==%p",
id_digest);
return;
}
control_event_hs_descriptor_upload_end("UPLOADED", onion_address,
id_digest, NULL);
}
/** Send HS_DESC event to inform controller that query rend_data
* failed to retrieve hidden service descriptor from directory identified by
* id_digest. If NULL, "UNKNOWN" is used. If reason is not NULL,
* add it to REASON= field.
*/
void
control_event_hs_descriptor_failed(const rend_data_t *rend_data,
const char *id_digest,
const char *reason)
{
if (!rend_data) {
log_warn(LD_BUG, "Called with rend_data==%p", rend_data);
return;
}
control_event_hs_descriptor_receive_end("FAILED",
rend_data_get_address(rend_data),
rend_data, id_digest, reason);
}
/** Send HS_DESC_CONTENT event after completion of a successful fetch from hs
* directory. If hsdir_id_digest is NULL, it is replaced by "UNKNOWN".
* If content is NULL, it is replaced by an empty string. The
* onion_address or desc_id set to NULL will no trigger the
* control event. */
void
control_event_hs_descriptor_content(const char *onion_address,
const char *desc_id,
const char *hsdir_id_digest,
const char *content)
{
static const char *event_name = "HS_DESC_CONTENT";
char *esc_content = NULL;
if (!onion_address || !desc_id) {
log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, ",
onion_address, desc_id);
return;
}
if (content == NULL) {
/* Point it to empty content so it can still be escaped. */
content = "";
}
write_escaped_data(content, strlen(content), &esc_content);
send_control_event(EVENT_HS_DESC_CONTENT,
"650+%s %s %s %s\r\n%s650 OK\r\n",
event_name,
rend_hsaddress_str_or_unknown(onion_address),
desc_id,
hsdir_id_digest ?
node_describe_longname_by_id(hsdir_id_digest) :
"UNKNOWN",
esc_content);
tor_free(esc_content);
}
/** Send HS_DESC event to inform controller upload of hidden service
* descriptor identified by id_digest failed. If reason
* is not NULL, add it to REASON= field.
*/
void
control_event_hs_descriptor_upload_failed(const char *id_digest,
const char *onion_address,
const char *reason)
{
if (!id_digest) {
log_warn(LD_BUG, "Called with id_digest==%p",
id_digest);
return;
}
control_event_hs_descriptor_upload_end("UPLOAD_FAILED", onion_address,
id_digest, reason);
}
/** Free any leftover allocated memory of the control.c subsystem. */
void
control_free_all(void)
{
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);
}
if (queued_control_events) {
SMARTLIST_FOREACH(queued_control_events, queued_event_t *, ev,
queued_event_free(ev));
smartlist_free(queued_control_events);
queued_control_events = NULL;
}
if (flush_queued_events_event) {
tor_event_free(flush_queued_events_event);
flush_queued_events_event = NULL;
}
}
#ifdef TOR_UNIT_TESTS
/* For testing: change the value of global_event_mask */
void
control_testing_set_global_event_mask(uint64_t mask)
{
global_event_mask = mask;
}
#endif