From d8f74cc439ad025cc52aea8495705096d6538029 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 5 Dec 2012 19:18:18 +0200 Subject: [PATCH] Move Extended ORPort code to its own module. Move the code from the connection_or module to ext_orport. This commit only moves code: it shouldn't modify anything. --- src/or/buffers.c | 1 + src/or/config.c | 1 + src/or/connection.c | 1 + src/or/connection_or.c | 524 +--------------------------------------- src/or/connection_or.h | 14 -- src/or/ext_orport.c | 537 +++++++++++++++++++++++++++++++++++++++++ src/or/ext_orport.h | 14 ++ src/or/include.am | 2 + src/or/transports.c | 1 + 9 files changed, 558 insertions(+), 537 deletions(-) create mode 100644 src/or/ext_orport.c create mode 100644 src/or/ext_orport.h diff --git a/src/or/buffers.c b/src/or/buffers.c index ead6baa882..50016d3a86 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -19,6 +19,7 @@ #include "connection_or.h" #include "control.h" #include "reasons.h" +#include "ext_orport.h" #include "../common/util.h" #include "../common/torlog.h" #ifdef HAVE_UNISTD_H diff --git a/src/or/config.c b/src/or/config.c index 19da45a63b..542191dbeb 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -45,6 +45,7 @@ #include "routerset.h" #include "statefile.h" #include "transports.h" +#include "ext_orport.h" #ifdef _WIN32 #include #endif diff --git a/src/or/connection.c b/src/or/connection.c index a2719e9f42..fcdc9ab996 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -33,6 +33,7 @@ #include "dns.h" #include "dnsserv.h" #include "entrynodes.h" +#include "ext_orport.h" #include "geoip.h" #include "main.h" #include "policies.h" diff --git a/src/or/connection_or.c b/src/or/connection_or.c index d6d74a703c..42127ad3e8 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -37,7 +37,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" - +#include "ext_orport.h" #ifdef USE_BUFFEREVENTS #include #endif @@ -482,24 +482,6 @@ var_cell_free(var_cell_t *cell) tor_free(cell); } -/** Allocate and return a structure capable of holding an Extended - * ORPort message of body length len. */ -ext_or_cmd_t * -ext_or_cmd_new(uint16_t len) -{ - size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; - ext_or_cmd_t *cmd = tor_malloc(size); - cmd->len = len; - return cmd; -} - -/** Deallocate the Extended ORPort message in cmd. */ -void -ext_or_cmd_free(ext_or_cmd_t *cmd) -{ - tor_free(cmd); -} - /** We've received an EOF from conn. Mark it for close and return. */ int connection_or_reached_eof(or_connection_t *conn) @@ -2442,507 +2424,3 @@ connection_or_send_authenticate_cell(or_connection_t *conn, int authtype) return 0; } - -/** Get an Extended ORPort message from conn, and place it in - * out. Return -1 on fail, 0 if we need more data, and 1 if we - * successfully extracted an Extended ORPort command from the - * buffer. */ -static int -connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) -{ - IF_HAS_BUFFEREVENT(conn, { - struct evbuffer *input = bufferevent_get_input(conn->bufev); - return fetch_ext_or_command_from_evbuffer(input, out); - }) ELSE_IF_NO_BUFFEREVENT { - return fetch_ext_or_command_from_buf(conn->inbuf, out); - } -} - -/** Write an Extended ORPort message to conn. Use - * command as the command type, bodylen as the body - * length, and body, if it's present, as the body of the - * message. */ -static int -connection_write_ext_or_command(connection_t *conn, - uint16_t command, - const char *body, - size_t bodylen) -{ - char header[4]; - if (bodylen > UINT16_MAX) - return -1; - set_uint16(header, htons(command)); - set_uint16(header+2, htons(bodylen)); - connection_write_to_buf(header, 4, conn); - if (bodylen) { - tor_assert(body); - connection_write_to_buf(body, bodylen, conn); - } - return 0; -} - -/** Transition from an Extended ORPort which accepts Extended ORPort - * messages, to an Extended ORport which accepts OR traffic. */ -static void -connection_ext_or_transition(or_connection_t *conn) -{ - tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); - - conn->base_.type = CONN_TYPE_OR; - control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); - connection_tls_start_handshake(conn, 1); -} - -/** Length of authentication cookie. */ -#define EXT_OR_PORT_AUTH_COOKIE_LEN 32 -/** Length of the header of the cookie file. */ -#define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 -/** Total length of the cookie file. */ -#define EXT_OR_PORT_AUTH_COOKIE_FILE_LEN \ - EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN -/** Static cookie file header. */ -#define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" -/** Length of safe-cookie protocol hashes. */ -#define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN -/** Length of safe-cookie protocol nonces. */ -#define EXT_OR_PORT_AUTH_NONCE_LEN 32 -/** Safe-cookie protocol constants. */ -#define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ - "ExtORPort authentication server-to-client hash" -#define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ - "ExtORPort authentication client-to-server hash" - -/** If true, we've set ext_or_auth_cookie to a secret code and stored - * it to disk. */ -static int ext_or_auth_cookie_is_set = 0; -/** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk - * and which we're using to authenticate controllers. (If the controller can - * read it off disk, it has permission to connect.) */ -static char ext_or_auth_cookie[EXT_OR_PORT_AUTH_COOKIE_LEN] = {0}; - -/** Helper: Return a newly allocated string containing a path to the - * file where we store our authentication cookie. */ -char * -get_ext_or_auth_cookie_file(void) -{ - const or_options_t *options = get_options(); - if (options->ExtORPortCookieAuthFile && - strlen(options->ExtORPortCookieAuthFile)) { - return tor_strdup(options->ExtORPortCookieAuthFile); - } else { - return get_datadir_fname("extended_orport_auth_cookie"); - } -} - -/** Choose a random authentication cookie and write it to disk. - * Anybody who can read the cookie from disk will be considered - * authorized to use the control connection. Return -1 if we can't - * write the file, or 0 on success. */ -int -init_ext_or_auth_cookie_authentication(int is_enabled) -{ - char *fname; - char cookie_file_string[EXT_OR_PORT_AUTH_COOKIE_FILE_LEN]; - - if (!is_enabled) { - ext_or_auth_cookie_is_set = 0; - return 0; - } - - /* We don't want to generate a new cookie every time we call - * options_act(). One should be enough. */ - if (ext_or_auth_cookie_is_set) - return 0; /* all set */ - - if (crypto_rand(ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN) < 0) - return -1; - ext_or_auth_cookie_is_set = 1; - - memcpy(cookie_file_string, EXT_OR_PORT_AUTH_COOKIE_HEADER, - EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN); - memcpy(cookie_file_string+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, - ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN); - - fname = get_ext_or_auth_cookie_file(); - if (write_bytes_to_file(fname, cookie_file_string, - EXT_OR_PORT_AUTH_COOKIE_FILE_LEN, 1)) { - log_warn(LD_FS,"Error writing authentication cookie to %s.", - escaped(fname)); - tor_free(fname); - return -1; - } - - log_warn(LD_GENERAL, "Generated Extended ORPort cookie file in '%s'.", - fname); - - tor_free(fname); - return 0; -} - -/** Read data from conn and see if the client sent us the - * authentication type that she prefers to use in this session. - * - * Return -1 if we received corrupted data or if we don't support the - * authentication type. Return 0 if we need more data in - * conn. Return 1 if the authentication type negotiation was - * successful. */ -static int -connection_ext_or_auth_neg_auth_type(connection_t *conn) -{ - char authtype[1] = {0}; - - if (connection_get_inbuf_len(conn) < 1) - return 0; - - if (connection_fetch_from_buf(authtype, 1, conn) < 0) - return -1; - - log_warn(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); - if (authtype[0] != 1) /* '1' is the only auth type supported atm */ - return -1; - - conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; - return 1; -} - -/** Read the client's nonce out of conn, setup the safe-cookie - * crypto, and then send our own hash and nonce to the client - * - * Return -1 if there was an error; return 0 if we need more data in - * conn, and return 1 if we successfully retrieved the - * client's nonce and sent our own. */ -static int -connection_ext_or_auth_handle_client_nonce(connection_t *conn) -{ - char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; - char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; - char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; - char reply[EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; - - if (!ext_or_auth_cookie_is_set) { /* this should not happen */ - log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " - "That's weird since we should have done that on startup. " - "This might be a Tor bug, please file a bug report. "); - return -1; - } - - if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) - return 0; - - if (connection_fetch_from_buf(client_nonce, - EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) /* XXX check-spaces */ - return -1; - - /* Get our nonce */ - if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) - return -1; - - { /* set up macs */ - size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + - 2*EXT_OR_PORT_AUTH_NONCE_LEN; - size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + - 2*EXT_OR_PORT_AUTH_NONCE_LEN; - - char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); - char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); - char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); - - memcpy(hmac_s_msg, - EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, - strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); - memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), - client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); - memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + - EXT_OR_PORT_AUTH_NONCE_LEN, - server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); - - memcpy(hmac_c_msg, - EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, - strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); - memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), - client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); - memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + - EXT_OR_PORT_AUTH_NONCE_LEN, - server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); - - crypto_hmac_sha256(server_hash, - ext_or_auth_cookie, - EXT_OR_PORT_AUTH_COOKIE_LEN, - hmac_s_msg, - hmac_s_msg_len); - - crypto_hmac_sha256(correct_client_hash, - ext_or_auth_cookie, - EXT_OR_PORT_AUTH_COOKIE_LEN, - hmac_c_msg, - hmac_c_msg_len); - - /* Store the client hash we generated. We will need to compare it - with the hash sent by the client. */ - TO_OR_CONN(conn)->ext_or_auth_correct_client_hash = correct_client_hash; - - tor_free(hmac_s_msg); - tor_free(hmac_c_msg); - } - - { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ - char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; - char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; - char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; - - base16_encode(server_hash_encoded, sizeof(server_hash_encoded), - server_hash, sizeof(server_hash)); - base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), - server_nonce, sizeof(server_nonce)); - base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), - client_nonce, sizeof(client_nonce)); - - log_warn(LD_GENERAL, - "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", - server_hash_encoded, server_nonce_encoded, client_nonce_encoded); - } - - { /* write reply: (server_hash, server_nonce) */ - memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); - memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, - EXT_OR_PORT_AUTH_NONCE_LEN); - connection_write_to_buf(reply, sizeof(reply), conn); - } - - log_warn(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); - - conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; - return 1; -} - -#define connection_ext_or_auth_send_result_success(c) \ - connection_ext_or_auth_send_result(c, 1) -#define connection_ext_or_auth_send_result_fail(c) \ - connection_ext_or_auth_send_result(c, 0) - -/** Send authentication results to conn. Successful results if - * success is set; failure results otherwise. */ -static void -connection_ext_or_auth_send_result(connection_t *conn, int success) -{ - if (success) - connection_write_to_buf("\x01", 1, conn); - else - connection_write_to_buf("\x00", 1, conn); -} - -/** Receive the client's hash from conn, validate that it's - * correct, and then send the authentication results to the client. - * - * Return -1 if there was an error during validation; return 0 if we - * need more data in conn, and return 1 if we successfully - * validated the client's hash and sent a happy authentication - * result. */ -static int -connection_ext_or_auth_handle_client_hash(connection_t *conn) -{ - char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; - - if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) - return 0; - - if (connection_fetch_from_buf(provided_client_hash, - EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) - return -1; - - if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, - provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { - log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); - connection_ext_or_auth_send_result_fail(conn); - return -1; - } - - log_warn(LD_GENERAL, "Got client's hash and it was legit."); - - /* send positive auth result */ - connection_ext_or_auth_send_result_success(conn); - conn->state = EXT_OR_CONN_STATE_OPEN; - return 1; -} - -/** Handle data from or_conn received on Extended ORPort. - * Return -1 on error. 0 on unsufficient data. 1 on correct. */ -static int -connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) -{ - connection_t *conn = TO_CONN(or_conn); - - switch (conn->state) { /* Functionify */ - case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: - return connection_ext_or_auth_neg_auth_type(conn); - - case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: - return connection_ext_or_auth_handle_client_nonce(conn); - - case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: - return connection_ext_or_auth_handle_client_hash(conn); - - default: - log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " - "to process Extended ORPort authentication data.", conn->state); - return -1; - } -} - -/** Extended ORPort commands (Transport-to-Bridge) */ -#define EXT_OR_CMD_TB_DONE 0x0000 -#define EXT_OR_CMD_TB_USERADDR 0x0001 - -/** Extended ORPort commands (Bridge-to-Transport) */ -#define EXT_OR_CMD_BT_OKAY 0x1000 -#define EXT_OR_CMD_BT_DENY 0x1001 -#define EXT_OR_CMD_BT_CONTROL 0x1002 - -/** Process a USERADDR command from the Extended - * ORPort. payload is a payload of size len. - * - * If the USERADDR command was well formed, change the address of - * conn to the address on the USERADDR command. - * - * Return 0 on success and -1 on error. */ -static int -connection_ext_or_handle_useraddr(connection_t *conn, - char *payload, uint16_t len) -{ - /* Copy address string. */ - tor_addr_t addr; - uint16_t port; - char *addr_str; - char *address_part=NULL; - int res; - addr_str = tor_malloc(len + 1); - memcpy(addr_str, payload, len); - addr_str[len] = 0; - - res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); - tor_free(addr_str); - if (res<0) - return -1; - - res = tor_addr_parse(&addr, address_part); - tor_free(address_part); - if (res<0) - return -1; - - { /* do some logging */ - char *old_address = tor_dup_addr(&conn->addr); - char *new_address = tor_dup_addr(&addr); - - log_warn(LD_NET, "Received USERADDR." /* XXX Fix log severities/messages */ - "We rewrite our address from '%s:%u' to '%s:%u'.", - safe_str(old_address), conn->port, safe_str(new_address), port); - - tor_free(old_address); - tor_free(new_address); - } - - /* record the address */ - tor_addr_copy(&conn->addr, &addr); - conn->port = port; - - return 0; -} - -/** Process Extended ORPort messages from or_conn. */ -int -connection_ext_or_process_inbuf(or_connection_t *or_conn) -{ - connection_t *conn = TO_CONN(or_conn); - ext_or_cmd_t *command; - int r; - - /* If we are still in the authentication stage, process traffic as - authentication data: */ - while (conn->state <= EXT_OR_CONN_STATE_AUTH_MAX) { - log_warn(LD_GENERAL, "Got Extended ORPort authentication data (%u).", - (unsigned int) connection_get_inbuf_len(conn)); - r = connection_ext_or_auth_process_inbuf(or_conn); - if (r < 0) { - connection_mark_for_close(conn); - return -1; - } else if (r == 0) { - return 0; - } - /* if r > 0, loop and process more data (if any). */ - } - - while (1) { - log_warn(LD_GENERAL, "Got Extended ORPort data."); - command = NULL; - r = connection_fetch_ext_or_cmd_from_buf(conn, &command); - if (r < 0) - goto err; - else if (r == 0) - return 0; /* need to wait for more data */ - - /* Got a command! */ - tor_assert(command); - - if (command->cmd == EXT_OR_CMD_TB_DONE) { - if (connection_get_inbuf_len(conn)) { - /* The inbuf isn't empty; the client is misbehaving. */ - goto err; - } - - log_debug(LD_NET, "Received DONE."); - - connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); - - /* can't transition immediately; need to flush first. */ - conn->state = EXT_OR_CONN_STATE_FLUSHING; - connection_stop_reading(conn); - } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { - if (connection_ext_or_handle_useraddr(conn, - command->body, command->len) < 0) - goto err; - } else { - log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", - command->cmd); - } - - ext_or_cmd_free(command); - } - - return 0; - - err: - ext_or_cmd_free(command); - connection_mark_for_close(conn); - return -1; -} - -/** conn finished flushing Extended ORPort messages to the - * network, and is now ready to accept OR traffic. This function - * does the transition. */ -int -connection_ext_or_finished_flushing(or_connection_t *conn) -{ - if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { - connection_start_reading(TO_CONN(conn)); - connection_ext_or_transition(conn); - } - return 0; -} - -/** Initiate Extended ORPort authentication, by sending the list of - * supported authentication types to the client. */ -int -connection_ext_or_start_auth(or_connection_t *or_conn) -{ - connection_t *conn = TO_CONN(or_conn); - char authtypes[2] = "\x01\x00"; /* We only support authtype '1' for now. */ - - log_warn(LD_GENERAL, - "ExtORPort authentication: Sending supported authentication types"); - - connection_write_to_buf(authtypes, sizeof(authtypes), conn); - conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; - - return 0; -} - diff --git a/src/or/connection_or.h b/src/or/connection_or.h index d420dc7848..85e68f1a33 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -45,8 +45,6 @@ void connection_or_close_for_error(or_connection_t *orconn, int flush); void connection_or_report_broken_states(int severity, int domain); -int connection_ext_or_start_auth(or_connection_t *or_conn); - int connection_tls_start_handshake(or_connection_t *conn, int receiving); int connection_tls_continue_handshake(or_connection_t *conn); @@ -97,17 +95,5 @@ void var_cell_free(var_cell_t *cell); /** DOCDOC */ #define MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS 4 -ext_or_cmd_t *ext_or_cmd_new(uint16_t len); -void ext_or_cmd_free(ext_or_cmd_t *cmd); -void connection_or_set_ext_or_identifier(or_connection_t *conn); -void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); -void connection_or_clear_ext_or_id_map(void); - -int connection_ext_or_finished_flushing(or_connection_t *conn); -int connection_ext_or_process_inbuf(or_connection_t *or_conn); - -int init_ext_or_auth_cookie_authentication(int is_enabled); -char *get_ext_or_auth_cookie_file(void); - #endif diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c new file mode 100644 index 0000000000..18ee5ab651 --- /dev/null +++ b/src/or/ext_orport.c @@ -0,0 +1,537 @@ +/* Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ext_orport.c + * \brief Code implementing the Extended ORPort. +*/ + +#include "or.h" +#include "connection.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "control.h" +#include "config.h" +#include "main.h" + +/** Allocate and return a structure capable of holding an Extended + * ORPort message of body length len. */ +ext_or_cmd_t * +ext_or_cmd_new(uint16_t len) +{ + size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; + ext_or_cmd_t *cmd = tor_malloc(size); + cmd->len = len; + return cmd; +} + +/** Deallocate the Extended ORPort message in cmd. */ +void +ext_or_cmd_free(ext_or_cmd_t *cmd) +{ + tor_free(cmd); +} + +/** Get an Extended ORPort message from conn, and place it in + * out. Return -1 on fail, 0 if we need more data, and 1 if we + * successfully extracted an Extended ORPort command from the + * buffer. */ +static int +connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_ext_or_command_from_evbuffer(input, out); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_ext_or_command_from_buf(conn->inbuf, out); + } +} + +/** Write an Extended ORPort message to conn. Use + * command as the command type, bodylen as the body + * length, and body, if it's present, as the body of the + * message. */ +static int +connection_write_ext_or_command(connection_t *conn, + uint16_t command, + const char *body, + size_t bodylen) +{ + char header[4]; + if (bodylen > UINT16_MAX) + return -1; + set_uint16(header, htons(command)); + set_uint16(header+2, htons(bodylen)); + connection_write_to_buf(header, 4, conn); + if (bodylen) { + tor_assert(body); + connection_write_to_buf(body, bodylen, conn); + } + return 0; +} + +/** Transition from an Extended ORPort which accepts Extended ORPort + * messages, to an Extended ORport which accepts OR traffic. */ +static void +connection_ext_or_transition(or_connection_t *conn) +{ + tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); + + conn->base_.type = CONN_TYPE_OR; + control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); + connection_tls_start_handshake(conn, 1); +} + +/** Length of authentication cookie. */ +#define EXT_OR_PORT_AUTH_COOKIE_LEN 32 +/** Length of the header of the cookie file. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 +/** Total length of the cookie file. */ +#define EXT_OR_PORT_AUTH_COOKIE_FILE_LEN \ + EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN +/** Static cookie file header. */ +#define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" +/** Length of safe-cookie protocol hashes. */ +#define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN +/** Length of safe-cookie protocol nonces. */ +#define EXT_OR_PORT_AUTH_NONCE_LEN 32 +/** Safe-cookie protocol constants. */ +#define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ + "ExtORPort authentication server-to-client hash" +#define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ + "ExtORPort authentication client-to-server hash" + +/** If true, we've set ext_or_auth_cookie to a secret code and stored + * it to disk. */ +static int ext_or_auth_cookie_is_set = 0; +/** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk + * and which we're using to authenticate controllers. (If the controller can + * read it off disk, it has permission to connect.) */ +static char ext_or_auth_cookie[EXT_OR_PORT_AUTH_COOKIE_LEN] = {0}; + +/** Helper: Return a newly allocated string containing a path to the + * file where we store our authentication cookie. */ +char * +get_ext_or_auth_cookie_file(void) +{ + const or_options_t *options = get_options(); + if (options->ExtORPortCookieAuthFile && + strlen(options->ExtORPortCookieAuthFile)) { + return tor_strdup(options->ExtORPortCookieAuthFile); + } else { + return get_datadir_fname("extended_orport_auth_cookie"); + } +} + +/** Choose a random authentication cookie and write it to disk. + * Anybody who can read the cookie from disk will be considered + * authorized to use the control connection. Return -1 if we can't + * write the file, or 0 on success. */ +int +init_ext_or_auth_cookie_authentication(int is_enabled) +{ + char *fname; + char cookie_file_string[EXT_OR_PORT_AUTH_COOKIE_FILE_LEN]; + + if (!is_enabled) { + ext_or_auth_cookie_is_set = 0; + return 0; + } + + /* We don't want to generate a new cookie every time we call + * options_act(). One should be enough. */ + if (ext_or_auth_cookie_is_set) + return 0; /* all set */ + + if (crypto_rand(ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN) < 0) + return -1; + ext_or_auth_cookie_is_set = 1; + + memcpy(cookie_file_string, EXT_OR_PORT_AUTH_COOKIE_HEADER, + EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN); + memcpy(cookie_file_string+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, + ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN); + + fname = get_ext_or_auth_cookie_file(); + if (write_bytes_to_file(fname, cookie_file_string, + EXT_OR_PORT_AUTH_COOKIE_FILE_LEN, 1)) { + log_warn(LD_FS,"Error writing authentication cookie to %s.", + escaped(fname)); + tor_free(fname); + return -1; + } + + log_warn(LD_GENERAL, "Generated Extended ORPort cookie file in '%s'.", + fname); + + tor_free(fname); + return 0; +} + +/** Read data from conn and see if the client sent us the + * authentication type that she prefers to use in this session. + * + * Return -1 if we received corrupted data or if we don't support the + * authentication type. Return 0 if we need more data in + * conn. Return 1 if the authentication type negotiation was + * successful. */ +static int +connection_ext_or_auth_neg_auth_type(connection_t *conn) +{ + char authtype[1] = {0}; + + if (connection_get_inbuf_len(conn) < 1) + return 0; + + if (connection_fetch_from_buf(authtype, 1, conn) < 0) + return -1; + + log_warn(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); + if (authtype[0] != 1) /* '1' is the only auth type supported atm */ + return -1; + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; + return 1; +} + +/** Read the client's nonce out of conn, setup the safe-cookie + * crypto, and then send our own hash and nonce to the client + * + * Return -1 if there was an error; return 0 if we need more data in + * conn, and return 1 if we successfully retrieved the + * client's nonce and sent our own. */ +static int +connection_ext_or_auth_handle_client_nonce(connection_t *conn) +{ + char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + char reply[EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; + + if (!ext_or_auth_cookie_is_set) { /* this should not happen */ + log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " + "That's weird since we should have done that on startup. " + "This might be a Tor bug, please file a bug report. "); + return -1; + } + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) + return 0; + + if (connection_fetch_from_buf(client_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) /* XXX check-spaces */ + return -1; + + /* Get our nonce */ + if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) + return -1; + + { /* set up macs */ + size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + 2*EXT_OR_PORT_AUTH_NONCE_LEN; + + char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); + char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); + char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); + + memcpy(hmac_s_msg, + EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + memcpy(hmac_c_msg, + EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), + client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + + EXT_OR_PORT_AUTH_NONCE_LEN, + server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); + + crypto_hmac_sha256(server_hash, + ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_s_msg, + hmac_s_msg_len); + + crypto_hmac_sha256(correct_client_hash, + ext_or_auth_cookie, + EXT_OR_PORT_AUTH_COOKIE_LEN, + hmac_c_msg, + hmac_c_msg_len); + + /* Store the client hash we generated. We will need to compare it + with the hash sent by the client. */ + TO_OR_CONN(conn)->ext_or_auth_correct_client_hash = correct_client_hash; + + tor_free(hmac_s_msg); + tor_free(hmac_c_msg); + } + + { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ + char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; + char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; + + base16_encode(server_hash_encoded, sizeof(server_hash_encoded), + server_hash, sizeof(server_hash)); + base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), + server_nonce, sizeof(server_nonce)); + base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), + client_nonce, sizeof(client_nonce)); + + log_warn(LD_GENERAL, + "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", + server_hash_encoded, server_nonce_encoded, client_nonce_encoded); + } + + { /* write reply: (server_hash, server_nonce) */ + memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); + memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, + EXT_OR_PORT_AUTH_NONCE_LEN); + connection_write_to_buf(reply, sizeof(reply), conn); + } + + log_warn(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); + + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; + return 1; +} + +#define connection_ext_or_auth_send_result_success(c) \ + connection_ext_or_auth_send_result(c, 1) +#define connection_ext_or_auth_send_result_fail(c) \ + connection_ext_or_auth_send_result(c, 0) + +/** Send authentication results to conn. Successful results if + * success is set; failure results otherwise. */ +static void +connection_ext_or_auth_send_result(connection_t *conn, int success) +{ + if (success) + connection_write_to_buf("\x01", 1, conn); + else + connection_write_to_buf("\x00", 1, conn); +} + +/** Receive the client's hash from conn, validate that it's + * correct, and then send the authentication results to the client. + * + * Return -1 if there was an error during validation; return 0 if we + * need more data in conn, and return 1 if we successfully + * validated the client's hash and sent a happy authentication + * result. */ +static int +connection_ext_or_auth_handle_client_hash(connection_t *conn) +{ + char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; + + if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) + return 0; + + if (connection_fetch_from_buf(provided_client_hash, + EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) + return -1; + + if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, + provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { + log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); + connection_ext_or_auth_send_result_fail(conn); + return -1; + } + + log_warn(LD_GENERAL, "Got client's hash and it was legit."); + + /* send positive auth result */ + connection_ext_or_auth_send_result_success(conn); + conn->state = EXT_OR_CONN_STATE_OPEN; + return 1; +} + +/** Handle data from or_conn received on Extended ORPort. + * Return -1 on error. 0 on unsufficient data. 1 on correct. */ +static int +connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + + switch (conn->state) { /* Functionify */ + case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: + return connection_ext_or_auth_neg_auth_type(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: + return connection_ext_or_auth_handle_client_nonce(conn); + + case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: + return connection_ext_or_auth_handle_client_hash(conn); + + default: + log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " + "to process Extended ORPort authentication data.", conn->state); + return -1; + } +} + +/** Extended ORPort commands (Transport-to-Bridge) */ +#define EXT_OR_CMD_TB_DONE 0x0000 +#define EXT_OR_CMD_TB_USERADDR 0x0001 + +/** Extended ORPort commands (Bridge-to-Transport) */ +#define EXT_OR_CMD_BT_OKAY 0x1000 +#define EXT_OR_CMD_BT_DENY 0x1001 +#define EXT_OR_CMD_BT_CONTROL 0x1002 + +/** Process a USERADDR command from the Extended + * ORPort. payload is a payload of size len. + * + * If the USERADDR command was well formed, change the address of + * conn to the address on the USERADDR command. + * + * Return 0 on success and -1 on error. */ +static int +connection_ext_or_handle_useraddr(connection_t *conn, + const char *payload, uint16_t len) +{ + /* Copy address string. */ + tor_addr_t addr; + uint16_t port; + char *addr_str; + char *address_part=NULL; + int res; + addr_str = tor_malloc(len + 1); + memcpy(addr_str, payload, len); + addr_str[len] = 0; + + res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); + tor_free(addr_str); + if (res<0) + return -1; + + res = tor_addr_parse(&addr, address_part); + tor_free(address_part); + if (res<0) + return -1; + + { /* do some logging */ + char *old_address = tor_dup_addr(&conn->addr); + char *new_address = tor_dup_addr(&addr); + + log_warn(LD_NET, "Received USERADDR." /* XXX Fix log severities/messages */ + "We rewrite our address from '%s:%u' to '%s:%u'.", + safe_str(old_address), conn->port, safe_str(new_address), port); + + tor_free(old_address); + tor_free(new_address); + } + + /* record the address */ + tor_addr_copy(&conn->addr, &addr); + conn->port = port; + + return 0; +} + +/** Process Extended ORPort messages from or_conn. */ +int +connection_ext_or_process_inbuf(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + ext_or_cmd_t *command; + int r; + + /* If we are still in the authentication stage, process traffic as + authentication data: */ + while (conn->state <= EXT_OR_CONN_STATE_AUTH_MAX) { + log_warn(LD_GENERAL, "Got Extended ORPort authentication data (%u).", + (unsigned int) connection_get_inbuf_len(conn)); + r = connection_ext_or_auth_process_inbuf(or_conn); + if (r < 0) { + connection_mark_for_close(conn); + return -1; + } else if (r == 0) { + return 0; + } + /* if r > 0, loop and process more data (if any). */ + } + + while (1) { + log_warn(LD_GENERAL, "Got Extended ORPort data."); + command = NULL; + r = connection_fetch_ext_or_cmd_from_buf(conn, &command); + if (r < 0) + goto err; + else if (r == 0) + return 0; /* need to wait for more data */ + + /* Got a command! */ + tor_assert(command); + + if (command->cmd == EXT_OR_CMD_TB_DONE) { + if (connection_get_inbuf_len(conn)) { + /* The inbuf isn't empty; the client is misbehaving. */ + goto err; + } + + log_debug(LD_NET, "Received DONE."); + + connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); + + /* can't transition immediately; need to flush first. */ + conn->state = EXT_OR_CONN_STATE_FLUSHING; + connection_stop_reading(conn); + } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { + if (connection_ext_or_handle_useraddr(conn, + command->body, command->len) < 0) + goto err; + } else { + log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", + command->cmd); + } + + ext_or_cmd_free(command); + } + + return 0; + + err: + ext_or_cmd_free(command); + connection_mark_for_close(conn); + return -1; +} + +/** conn finished flushing Extended ORPort messages to the + * network, and is now ready to accept OR traffic. This function + * does the transition. */ +int +connection_ext_or_finished_flushing(or_connection_t *conn) +{ + if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { + connection_start_reading(TO_CONN(conn)); + connection_ext_or_transition(conn); + } + return 0; +} + +/** Initiate Extended ORPort authentication, by sending the list of + * supported authentication types to the client. */ +int +connection_ext_or_start_auth(or_connection_t *or_conn) +{ + connection_t *conn = TO_CONN(or_conn); + char authtypes[2] = "\x01\x00"; /* We only support authtype '1' for now. */ + + log_warn(LD_GENERAL, + "ExtORPort authentication: Sending supported authentication types"); + + connection_write_to_buf(authtypes, sizeof(authtypes), conn); + conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; + + return 0; +} + diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h new file mode 100644 index 0000000000..fbd7ed6537 --- /dev/null +++ b/src/or/ext_orport.h @@ -0,0 +1,14 @@ +int connection_ext_or_start_auth(or_connection_t *or_conn); + +ext_or_cmd_t *ext_or_cmd_new(uint16_t len); +void ext_or_cmd_free(ext_or_cmd_t *cmd); +void connection_or_set_ext_or_identifier(or_connection_t *conn); +void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); +void connection_or_clear_ext_or_id_map(void); + +int connection_ext_or_finished_flushing(or_connection_t *conn); +int connection_ext_or_process_inbuf(or_connection_t *or_conn); + +int init_ext_or_auth_cookie_authentication(int is_enabled); +char *get_ext_or_auth_cookie_file(void); + diff --git a/src/or/include.am b/src/or/include.am index f5002e6986..8922e03978 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -56,6 +56,7 @@ LIBTOR_A_SOURCES = \ src/or/fp_pair.c \ src/or/geoip.c \ src/or/entrynodes.c \ + src/or/ext_orport.c \ src/or/hibernate.c \ src/or/main.c \ src/or/microdesc.c \ @@ -153,6 +154,7 @@ ORHEADERS = \ src/or/dns.h \ src/or/dnsserv.h \ src/or/eventdns_tor.h \ + src/or/ext_orport.h \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ diff --git a/src/or/transports.c b/src/or/transports.c index 8beb9a5c73..196e18bba5 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -97,6 +97,7 @@ #include "statefile.h" #include "entrynodes.h" #include "connection_or.h" +#include "ext_orport.h" static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp);