diff options
Diffstat (limited to 'src')
99 files changed, 3280 insertions, 625 deletions
diff --git a/src/common/aes.c b/src/common/aes.c index 20b51a6758..df4368fdba 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -254,7 +254,7 @@ evaluate_ctr_for_aes(void) /* LCOV_EXCL_START */ log_err(LD_CRYPTO, "This OpenSSL has a buggy version of counter mode; " "quitting tor."); - exit(1); + exit(1); // exit ok: openssl is broken. /* LCOV_EXCL_STOP */ } return 0; diff --git a/src/common/buffers.c b/src/common/buffers.c index c45e13d551..a21cf2ab5b 100644 --- a/src/common/buffers.c +++ b/src/common/buffers.c @@ -714,6 +714,53 @@ buf_add(buf_t *buf, const char *string, size_t string_len) return (int)buf->datalen; } +/** Add a nul-terminated <b>string</b> to <b>buf</b>, not including the + * terminating NUL. */ +void +buf_add_string(buf_t *buf, const char *string) +{ + buf_add(buf, string, strlen(string)); +} + +/** As tor_snprintf, but write the results into a buf_t */ +void +buf_add_printf(buf_t *buf, const char *format, ...) +{ + va_list ap; + va_start(ap,format); + buf_add_vprintf(buf, format, ap); + va_end(ap); +} + +/** As tor_vsnprintf, but write the results into a buf_t. */ +void +buf_add_vprintf(buf_t *buf, const char *format, va_list args) +{ + /* XXXX Faster implementations are easy enough, but let's optimize later */ + char *tmp; + tor_vasprintf(&tmp, format, args); + buf_add(buf, tmp, strlen(tmp)); + tor_free(tmp); +} + +/** Return a heap-allocated string containing the contents of <b>buf</b>, plus + * a NUL byte. If <b>sz_out</b> is provided, set *<b>sz_out</b> to the length + * of the returned string, not including the terminating NUL. */ +char * +buf_extract(buf_t *buf, size_t *sz_out) +{ + tor_assert(buf); + + size_t sz = buf_datalen(buf); + char *result; + result = tor_malloc(sz+1); + buf_peek(buf, result, sz); + result[sz] = 0; + if (sz_out) + *sz_out = sz; + return result; +} + /** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> * onto <b>string</b>. */ @@ -795,6 +842,28 @@ buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) return (int)cp; } +/** Moves all data from <b>buf_in</b> to <b>buf_out</b>, without copying. + */ +void +buf_move_all(buf_t *buf_out, buf_t *buf_in) +{ + tor_assert(buf_out); + if (!buf_in) + return; + + if (buf_out->head == NULL) { + buf_out->head = buf_in->head; + buf_out->tail = buf_in->tail; + } else { + buf_out->tail->next = buf_in->head; + buf_out->tail = buf_in->tail; + } + + buf_out->datalen += buf_in->datalen; + buf_in->head = buf_in->tail = NULL; + buf_in->datalen = 0; +} + /** Internal structure: represents a position in a buffer. */ typedef struct buf_pos_t { const chunk_t *chunk; /**< Which chunk are we pointing to? */ diff --git a/src/common/buffers.h b/src/common/buffers.h index 1eaa5f2d04..dd96ddcb6c 100644 --- a/src/common/buffers.h +++ b/src/common/buffers.h @@ -43,9 +43,15 @@ int buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz, size_t *buf_flushlen); int buf_add(buf_t *buf, const char *string, size_t string_len); +void buf_add_string(buf_t *buf, const char *string); +void buf_add_printf(buf_t *buf, const char *format, ...) + CHECK_PRINTF(2, 3); +void buf_add_vprintf(buf_t *buf, const char *format, va_list args) + CHECK_PRINTF(2, 0); int buf_add_compress(buf_t *buf, struct tor_compress_state_t *state, const char *data, size_t data_len, int done); int buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); +void buf_move_all(buf_t *buf_out, buf_t *buf_in); void buf_peek(const buf_t *buf, char *string, size_t string_len); void buf_drain(buf_t *buf, size_t n); int buf_get_bytes(buf_t *buf, char *string, size_t string_len); @@ -62,6 +68,7 @@ void buf_assert_ok(buf_t *buf); int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); void buf_pullup(buf_t *buf, size_t bytes, const char **head_out, size_t *len_out); +char *buf_extract(buf_t *buf, size_t *sz_out); #ifdef BUFFERS_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/common/compat.c b/src/common/compat.c index 7fe97488e3..38693b21fe 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -1186,7 +1186,7 @@ mark_socket_open(tor_socket_t s) bitarray_set(open_sockets, s); } #else /* !(defined(DEBUG_SOCKET_COUNTING)) */ -#define mark_socket_open(s) STMT_NIL +#define mark_socket_open(s) ((void) (s)) #endif /* defined(DEBUG_SOCKET_COUNTING) */ /** @} */ @@ -1273,11 +1273,22 @@ tor_open_socket_with_extensions(int domain, int type, int protocol, goto socket_ok; /* So that socket_ok will not be unused. */ socket_ok: + tor_take_socket_ownership(s); + return s; +} + +/** + * For socket accounting: remember that we are the owner of the socket + * <b>s</b>. This will prevent us from overallocating sockets, and prevent us + * from asserting later when we close the socket <b>s</b>. + */ +void +tor_take_socket_ownership(tor_socket_t s) +{ socket_accounting_lock(); ++n_sockets_open; mark_socket_open(s); socket_accounting_unlock(); - return s; } /** As accept(), but counts the number of open sockets. */ @@ -1358,10 +1369,7 @@ tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, goto socket_ok; /* So that socket_ok will not be unused. */ socket_ok: - socket_accounting_lock(); - ++n_sockets_open; - mark_socket_open(s); - socket_accounting_unlock(); + tor_take_socket_ownership(s); return s; } diff --git a/src/common/compat.h b/src/common/compat.h index fee9e6587d..0aabee68c8 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -483,6 +483,7 @@ typedef int socklen_t; int tor_close_socket_simple(tor_socket_t s); MOCK_DECL(int, tor_close_socket, (tor_socket_t s)); +void tor_take_socket_ownership(tor_socket_t s); tor_socket_t tor_open_socket_with_extensions( int domain, int type, int protocol, int cloexec, int nonblock); diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 740cc2a11d..1c3a1b9f37 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -126,7 +126,7 @@ tor_libevent_initialize(tor_libevent_cfg *torcfg) if (!the_event_base) { /* LCOV_EXCL_START */ log_err(LD_GENERAL, "Unable to initialize Libevent: cannot continue."); - exit(1); + exit(1); // exit ok: libevent is broken. /* LCOV_EXCL_STOP */ } diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 834354c405..3d8672fc63 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -30,6 +30,7 @@ periodic_timer_t *periodic_timer_new(struct event_base *base, void periodic_timer_free(periodic_timer_t *); #define tor_event_base_loopexit event_base_loopexit +#define tor_event_base_loopbreak event_base_loopbreak /** Defines a configuration for using libevent with Tor: passed as an argument * to tor_libevent_initialize() to describe how we want to set up. */ diff --git a/src/common/compat_rust.c b/src/common/compat_rust.c deleted file mode 100644 index 366fd4037b..0000000000 --- a/src/common/compat_rust.c +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright (c) 2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file rust_compat.c - * \brief Rust FFI compatibility functions and helpers. This file is only built - * if Rust is not used. - **/ - -#include "compat_rust.h" -#include "util.h" - -/** - * Free storage pointed to by <b>str</b>, and itself. - */ -void -rust_str_free(rust_str_t str) -{ - char *s = (char *)str; - tor_free(s); -} - -/** - * Return zero-terminated contained string. - */ -const char * -rust_str_get(const rust_str_t str) -{ - return (const char *)str; -} - -/* If we were using Rust, we'd say so on startup. */ -rust_str_t -rust_welcome_string(void) -{ - char *s = tor_malloc_zero(1); - return (rust_str_t)s; -} - diff --git a/src/common/compat_rust.h b/src/common/compat_rust.h deleted file mode 100644 index 72fde39296..0000000000 --- a/src/common/compat_rust.h +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright (c) 2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file rust_compat.h - * \brief Headers for rust_compat.c - **/ - -#ifndef TOR_RUST_COMPAT_H -#define TOR_RUST_COMPAT_H - -#include "torint.h" - -/** - * Strings allocated in Rust must be freed from Rust code again. Let's make - * it less likely to accidentally mess up and call tor_free() on it, because - * currently it'll just work but might break at any time. - */ -typedef uintptr_t rust_str_t; - -void rust_str_free(rust_str_t); - -const char *rust_str_get(const rust_str_t); - -rust_str_t rust_welcome_string(void); - -#endif /* !defined(TOR_RUST_COMPAT_H) */ - diff --git a/src/common/compat_time.c b/src/common/compat_time.c index 1ce6f5ce4e..7fd4281ade 100644 --- a/src/common/compat_time.c +++ b/src/common/compat_time.c @@ -90,7 +90,7 @@ tor_gettimeofday(struct timeval *timeval) if (ft.ft_64 < EPOCH_BIAS) { /* LCOV_EXCL_START */ log_err(LD_GENERAL,"System time is before 1970; failing."); - exit(1); + exit(1); // exit ok: system clock is broken. /* LCOV_EXCL_STOP */ } ft.ft_64 -= EPOCH_BIAS; @@ -102,7 +102,7 @@ tor_gettimeofday(struct timeval *timeval) log_err(LD_GENERAL,"gettimeofday failed."); /* If gettimeofday dies, we have either given a bad timezone (we didn't), or segfaulted.*/ - exit(1); + exit(1); // exit ok: gettimeofday failed. /* LCOV_EXCL_STOP */ } #elif defined(HAVE_FTIME) diff --git a/src/common/compat_winthreads.c b/src/common/compat_winthreads.c index 50a3c498ca..5f7ec94c23 100644 --- a/src/common/compat_winthreads.c +++ b/src/common/compat_winthreads.c @@ -48,10 +48,12 @@ void spawn_exit(void) { _endthread(); + // LCOV_EXCL_START //we should never get here. my compiler thinks that _endthread returns, this //is an attempt to fool it. tor_assert(0); - _exit(0); + _exit(0); // exit ok: unreachable. + // LCOV_EXCL_STOP } void diff --git a/src/common/confline.c b/src/common/confline.c index 04545bc2c3..781ad2a12b 100644 --- a/src/common/confline.c +++ b/src/common/confline.c @@ -12,15 +12,18 @@ static int config_get_lines_aux(const char *string, config_line_t **result, int extended, int allow_include, - int *has_include, int recursion_level, - config_line_t **last); -static smartlist_t *config_get_file_list(const char *path); -static int config_get_included_list(const char *path, int recursion_level, - int extended, config_line_t **list, - config_line_t **list_last); + int *has_include, smartlist_t *opened_lst, + int recursion_level, config_line_t **last); +static smartlist_t *config_get_file_list(const char *path, + smartlist_t *opened_files); +static int config_get_included_config(const char *path, int recursion_level, + int extended, config_line_t **config, + config_line_t **config_last, + smartlist_t *opened_lst); static int config_process_include(const char *path, int recursion_level, int extended, config_line_t **list, - config_line_t **list_last); + config_line_t **list_last, + smartlist_t *opened_lst); /** Helper: allocate a new configuration option mapping 'key' to 'val', * append it to *<b>lst</b>. */ @@ -80,11 +83,13 @@ config_line_find(const config_line_t *lines, /** Auxiliary function that does all the work of config_get_lines. * <b>recursion_level</b> is the count of how many nested %includes we have. + * <b>opened_lst</b> will have a list of opened files if provided. * Returns the a pointer to the last element of the <b>result</b> in * <b>last</b>. */ static int config_get_lines_aux(const char *string, config_line_t **result, int extended, - int allow_include, int *has_include, int recursion_level, + int allow_include, int *has_include, + smartlist_t *opened_lst, int recursion_level, config_line_t **last) { config_line_t *list = NULL, **next, *list_last = NULL; @@ -134,7 +139,7 @@ config_get_lines_aux(const char *string, config_line_t **result, int extended, config_line_t *include_list; if (config_process_include(v, recursion_level, extended, &include_list, - &list_last) < 0) { + &list_last, opened_lst) < 0) { log_warn(LD_CONFIG, "Error reading included configuration " "file or directory: \"%s\".", v); config_free_lines(list); @@ -176,24 +181,27 @@ config_get_lines_aux(const char *string, config_line_t **result, int extended, /** Helper: parse the config string and strdup into key/value * strings. Set *result to the list, or NULL if parsing the string * failed. Set *has_include to 1 if <b>result</b> has values from - * %included files. Return 0 on success, -1 on failure. Warn and ignore any + * %included files. <b>opened_lst</b> will have a list of opened files if + * provided. Return 0 on success, -1 on failure. Warn and ignore any * misformatted lines. * * If <b>extended</b> is set, then treat keys beginning with / and with + as * indicating "clear" and "append" respectively. */ int config_get_lines_include(const char *string, config_line_t **result, - int extended, int *has_include) + int extended, int *has_include, + smartlist_t *opened_lst) { - return config_get_lines_aux(string, result, extended, 1, has_include, 1, - NULL); + return config_get_lines_aux(string, result, extended, 1, has_include, + opened_lst, 1, NULL); } /** Same as config_get_lines_include but does not allow %include */ int config_get_lines(const char *string, config_line_t **result, int extended) { - return config_get_lines_aux(string, result, extended, 0, NULL, 1, NULL); + return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1, + NULL); } /** Adds a list of configuration files present on <b>path</b> to @@ -201,12 +209,18 @@ config_get_lines(const char *string, config_line_t **result, int extended) * only that file will be added to <b>file_list</b>. If it is a directory, * all paths for files on that directory root (no recursion) except for files * whose name starts with a dot will be added to <b>file_list</b>. - * Return 0 on success, -1 on failure. Ignores empty files. + * <b>opened_files</b> will have a list of files opened by this function + * if provided. Return 0 on success, -1 on failure. Ignores empty files. */ static smartlist_t * -config_get_file_list(const char *path) +config_get_file_list(const char *path, smartlist_t *opened_files) { smartlist_t *file_list = smartlist_new(); + + if (opened_files) { + smartlist_add_strdup(opened_files, path); + } + file_status_t file_type = file_status(path); if (file_type == FN_FILE) { smartlist_add_strdup(file_list, path); @@ -228,6 +242,10 @@ config_get_file_list(const char *path) tor_asprintf(&fullname, "%s"PATH_SEPARATOR"%s", path, f); tor_free(f); + if (opened_files) { + smartlist_add_strdup(opened_files, fullname); + } + if (file_status(fullname) != FN_FILE) { tor_free(fullname); continue; @@ -245,19 +263,21 @@ config_get_file_list(const char *path) } /** Creates a list of config lines present on included <b>path</b>. - * Set <b>list</b> to the list and <b>list_last</b> to the last element of - * <b>list</b>. Return 0 on success, -1 on failure. */ + * Set <b>config</b> to the list and <b>config_last</b> to the last element of + * <b>config</b>. <b>opened_lst</b> will have a list of opened files if + * provided. Return 0 on success, -1 on failure. */ static int -config_get_included_list(const char *path, int recursion_level, int extended, - config_line_t **list, config_line_t **list_last) +config_get_included_config(const char *path, int recursion_level, int extended, + config_line_t **config, config_line_t **config_last, + smartlist_t *opened_lst) { char *included_conf = read_file_to_str(path, 0, NULL); if (!included_conf) { return -1; } - if (config_get_lines_aux(included_conf, list, extended, 1, NULL, - recursion_level+1, list_last) < 0) { + if (config_get_lines_aux(included_conf, config, extended, 1, NULL, + opened_lst, recursion_level+1, config_last) < 0) { tor_free(included_conf); return -1; } @@ -268,41 +288,31 @@ config_get_included_list(const char *path, int recursion_level, int extended, /** Process an %include <b>path</b> in a config file. Set <b>list</b> to the * list of configuration settings obtained and <b>list_last</b> to the last - * element of the same list. Return 0 on success, -1 on failure. */ + * element of the same list. <b>opened_lst</b> will have a list of opened + * files if provided. Return 0 on success, -1 on failure. */ static int config_process_include(const char *path, int recursion_level, int extended, - config_line_t **list, config_line_t **list_last) + config_line_t **list, config_line_t **list_last, + smartlist_t *opened_lst) { config_line_t *ret_list = NULL; config_line_t **next = &ret_list; -#if 0 - // Disabled -- we already unescape_string() on the result. */ - char *unquoted_path = get_unquoted_path(path); - if (!unquoted_path) { - return -1; - } - smartlist_t *config_files = config_get_file_list(unquoted_path); - if (!config_files) { - tor_free(unquoted_path); - return -1; - } - tor_free(unquoted_path); -#endif /* 0 */ - smartlist_t *config_files = config_get_file_list(path); + smartlist_t *config_files = config_get_file_list(path, opened_lst); if (!config_files) { return -1; } int rv = -1; SMARTLIST_FOREACH_BEGIN(config_files, const char *, config_file) { - config_line_t *included_list = NULL; - if (config_get_included_list(config_file, recursion_level, extended, - &included_list, list_last) < 0) { + config_line_t *included_config = NULL; + if (config_get_included_config(config_file, recursion_level, extended, + &included_config, list_last, + opened_lst) < 0) { goto done; } - *next = included_list; + *next = included_config; if (*list_last) next = &(*list_last)->next; diff --git a/src/common/confline.h b/src/common/confline.h index 8256326f2d..feeb9f249d 100644 --- a/src/common/confline.h +++ b/src/common/confline.h @@ -7,6 +7,8 @@ #ifndef TOR_CONFLINE_H #define TOR_CONFLINE_H +#include "container.h" + /** Ordinary configuration line. */ #define CONFIG_LINE_NORMAL 0 /** Appends to previous configuration for the same option, even if we @@ -44,7 +46,8 @@ int config_lines_eq(config_line_t *a, config_line_t *b); int config_count_key(const config_line_t *a, const char *key); int config_get_lines(const char *string, config_line_t **result, int extended); int config_get_lines_include(const char *string, config_line_t **result, - int extended, int *has_include); + int extended, int *has_include, + smartlist_t *opened_lst); void config_free_lines(config_line_t *front); const char *parse_config_line_from_str_verbose(const char *line, char **key_out, char **value_out, diff --git a/src/common/crypto_pwbox.h b/src/common/crypto_pwbox.h index cee8653587..a26b6d2c17 100644 --- a/src/common/crypto_pwbox.h +++ b/src/common/crypto_pwbox.h @@ -1,3 +1,6 @@ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #ifndef CRYPTO_PWBOX_H_INCLUDED_ #define CRYPTO_PWBOX_H_INCLUDED_ diff --git a/src/common/include.am b/src/common/include.am index cd5eea3404..2856c40fdc 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -101,11 +101,6 @@ LIBOR_A_SRC = \ $(threads_impl_source) \ $(readpassphrase_source) -if USE_RUST -else -LIBOR_A_SRC += src/common/compat_rust.c -endif - src/common/src_common_libor_testing_a-log.$(OBJEXT) \ src/common/log.$(OBJEXT): micro-revision.i @@ -156,7 +151,6 @@ COMMONHEADERS = \ src/common/compat.h \ src/common/compat_libevent.h \ src/common/compat_openssl.h \ - src/common/compat_rust.h \ src/common/compat_threads.h \ src/common/compat_time.h \ src/common/compress.h \ diff --git a/src/common/sandbox.c b/src/common/sandbox.c index 7a4e3ece38..8cb78bd28e 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -151,6 +151,7 @@ static int filter_nopar_gen[] = { SCMP_SYS(fstat64), #endif SCMP_SYS(futex), + SCMP_SYS(getdents), SCMP_SYS(getdents64), SCMP_SYS(getegid), #ifdef __NR_getegid32 @@ -1756,7 +1757,7 @@ sigsys_debugging(int nr, siginfo_t *info, void *void_context) #endif #if defined(DEBUGGING_CLOSE) - _exit(1); + _exit(1); // exit ok: programming error has led to sandbox failure. #endif // DEBUGGING_CLOSE } diff --git a/src/common/util.c b/src/common/util.c index 5ff7e104d6..7dc5e8144d 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -156,7 +156,7 @@ tor_malloc_(size_t size DMALLOC_PARAMS) /* If these functions die within a worker process, they won't call * spawn_exit, but that's ok, since the parent will run out of memory soon * anyway. */ - exit(1); + exit(1); // exit ok: alloc failed. /* LCOV_EXCL_STOP */ } return result; @@ -244,7 +244,7 @@ tor_realloc_(void *ptr, size_t size DMALLOC_PARAMS) if (PREDICT_UNLIKELY(result == NULL)) { /* LCOV_EXCL_START */ log_err(LD_MM,"Out of memory on realloc(). Dying."); - exit(1); + exit(1); // exit ok: alloc failed. /* LCOV_EXCL_STOP */ } return result; @@ -282,7 +282,7 @@ tor_strdup_(const char *s DMALLOC_PARAMS) if (PREDICT_UNLIKELY(duplicate == NULL)) { /* LCOV_EXCL_START */ log_err(LD_MM,"Out of memory on strdup(). Dying."); - exit(1); + exit(1); // exit ok: alloc failed. /* LCOV_EXCL_STOP */ } return duplicate; @@ -3590,14 +3590,14 @@ start_daemon(void) if (pipe(daemon_filedes)) { /* LCOV_EXCL_START */ log_err(LD_GENERAL,"pipe failed; exiting. Error was %s", strerror(errno)); - exit(1); + exit(1); // exit ok: during daemonize, pipe failed. /* LCOV_EXCL_STOP */ } pid = fork(); if (pid < 0) { /* LCOV_EXCL_START */ log_err(LD_GENERAL,"fork failed. Exiting."); - exit(1); + exit(1); // exit ok: during daemonize, fork failed /* LCOV_EXCL_STOP */ } if (pid) { /* Parent */ @@ -3612,9 +3612,9 @@ start_daemon(void) } fflush(stdout); if (ok == 1) - exit(0); + exit(0); // exit ok: during daemonize, daemonizing. else - exit(1); /* child reported error */ + exit(1); /* child reported error. exit ok: daemonize failed. */ } else { /* Child */ close(daemon_filedes[0]); /* we only write */ @@ -3626,7 +3626,7 @@ start_daemon(void) * _Advanced Programming in the Unix Environment_. */ if (fork() != 0) { - exit(0); + exit(0); // exit ok: during daemonize, fork failed (2) } set_main_thread(); /* We are now the main thread. */ @@ -3655,14 +3655,14 @@ finish_daemon(const char *desired_cwd) /* Don't hold the wrong FS mounted */ if (chdir(desired_cwd) < 0) { log_err(LD_GENERAL,"chdir to \"%s\" failed. Exiting.",desired_cwd); - exit(1); + exit(1); // exit ok: during daemonize, chdir failed. } nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0); if (nullfd < 0) { /* LCOV_EXCL_START */ log_err(LD_GENERAL,"/dev/null can't be opened. Exiting."); - exit(1); + exit(1); // exit ok: during daemonize, couldn't open /dev/null /* LCOV_EXCL_STOP */ } /* close fds linking to invoking terminal, but @@ -3674,7 +3674,7 @@ finish_daemon(const char *desired_cwd) dup2(nullfd,2) < 0) { /* LCOV_EXCL_START */ log_err(LD_GENERAL,"dup2 failed. Exiting."); - exit(1); + exit(1); // exit ok: during daemonize, dup2 failed. /* LCOV_EXCL_STOP */ } if (nullfd > 2) @@ -4474,7 +4474,7 @@ tor_spawn_background(const char *const filename, const char **argv, err += (nbytes < 0); } - _exit(err?254:255); + _exit(err?254:255); // exit ok: in child. } /* Never reached, but avoids compiler warning */ diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 7e92633601..9a2cc26b3b 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -541,7 +541,7 @@ addressmap_have_mapping(const char *address, int update_expiry) * (virtual address mapping) from the controller.) * * <b>new_address</b> should be a newly dup'ed string, which we'll use or - * free as appropriate. We will leave address alone. + * free as appropriate. We will leave <b>address</b> alone. * * If <b>wildcard_addr</b> is true, then the mapping will match any address * equal to <b>address</b>, or any address ending with a period followed by @@ -554,7 +554,6 @@ addressmap_have_mapping(const char *address, int update_expiry) * <b>wildcard_new_addr</b>, remove any mappings that exist from * <b>address</b>. * - * * It is an error to set <b>wildcard_new_addr</b> if <b>wildcard_addr</b> is * not set. */ void diff --git a/src/or/config.c b/src/or/config.c index d6e1abaa10..252d50944b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -82,6 +82,7 @@ #include "dirvote.h" #include "dns.h" #include "entrynodes.h" +#include "git_revision.h" #include "geoip.h" #include "hibernate.h" #include "main.h" @@ -483,6 +484,7 @@ static config_var_t option_vars_[] = { V(RendPostPeriod, INTERVAL, "1 hour"), V(RephistTrackTime, INTERVAL, "24 hours"), V(RunAsDaemon, BOOL, "0"), + V(ReducedExitPolicy, BOOL, "0"), OBSOLETE("RunTesting"), // currently unused V(Sandbox, BOOL, "0"), V(SafeLogging, STRING, "1"), @@ -561,6 +563,7 @@ static config_var_t option_vars_[] = { VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword, NULL), VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL), + VAR("__OwningControllerFD",INT,OwningControllerFD, "-1"), V(MinUptimeHidServDirectoryV2, INTERVAL, "96 hours"), V(TestingServerDownloadSchedule, CSV_INTERVAL, "0, 0, 0, 60, 60, 120, " "300, 900, 2147483647"), @@ -838,9 +841,12 @@ set_options(or_options_t *new_val, char **msg) return -1; } if (options_act(old_options) < 0) { /* acting on the options failed. die. */ - log_err(LD_BUG, - "Acting on config options left us in a broken state. Dying."); - exit(1); + if (! tor_event_loop_shutdown_is_pending()) { + log_err(LD_BUG, + "Acting on config options left us in a broken state. Dying."); + tor_shutdown_event_loop_and_exit(1); + } + return -1; } /* Issues a CONF_CHANGED event to notify controller of the change. If Tor is * just starting up then the old_options will be undefined. */ @@ -882,8 +888,6 @@ set_options(or_options_t *new_val, char **msg) return 0; } -extern const char tor_git_revision[]; /* from tor_main.c */ - /** The version of this Tor process, as parsed. */ static char *the_tor_version = NULL; /** A shorter version of this Tor process's version, for export in our router @@ -938,6 +942,10 @@ or_options_free(or_options_t *options) SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i)); smartlist_free(options->SchedulerTypes_); } + if (options->FilesOpenedByIncludes) { + SMARTLIST_FOREACH(options->FilesOpenedByIncludes, char *, f, tor_free(f)); + smartlist_free(options->FilesOpenedByIncludes); + } tor_free(options->BridgePassword_AuthDigest_); tor_free(options->command_arg); tor_free(options->master_key_fname); @@ -1692,8 +1700,11 @@ options_act(const or_options_t *old_options) else protocol_warning_severity_level = LOG_INFO; - if (consider_adding_dir_servers(options, old_options) < 0) + if (consider_adding_dir_servers(options, old_options) < 0) { + // XXXX This should get validated earlier, and committed here, to + // XXXX lower opportunities for reaching an error case. return -1; + } if (rend_non_anonymous_mode_enabled(options)) { log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run " @@ -1702,6 +1713,7 @@ options_act(const or_options_t *old_options) #ifdef ENABLE_TOR2WEB_MODE /* LCOV_EXCL_START */ + // XXXX This should move into options_validate() if (!options->Tor2webMode) { log_err(LD_CONFIG, "This copy of Tor was compiled to run in " "'tor2web mode'. It can only be run with the Tor2webMode torrc " @@ -1710,6 +1722,7 @@ options_act(const or_options_t *old_options) } /* LCOV_EXCL_STOP */ #else /* !(defined(ENABLE_TOR2WEB_MODE)) */ + // XXXX This should move into options_validate() if (options->Tor2webMode) { log_err(LD_CONFIG, "This copy of Tor was not compiled to run in " "'tor2web mode'. It cannot be run with the Tor2webMode torrc " @@ -1737,9 +1750,11 @@ options_act(const or_options_t *old_options) for (cl = options->Bridges; cl; cl = cl->next) { bridge_line_t *bridge_line = parse_bridge_line(cl->value); if (!bridge_line) { + // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated Bridge line could not be added!"); return -1; + // LCOV_EXCL_STOP } bridge_add_from_config(bridge_line); } @@ -1747,15 +1762,37 @@ options_act(const or_options_t *old_options) } if (running_tor && hs_config_service_all(options, 0)<0) { + // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated hidden services line could not be added!"); return -1; + // LCOV_EXCL_STOP } if (running_tor && rend_parse_service_authorization(options, 0) < 0) { + // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated client authorization for " "hidden services could not be added!"); return -1; + // LCOV_EXCL_STOP + } + + if (running_tor && !old_options && options->OwningControllerFD != -1) { +#ifdef _WIN32 + log_warn(LD_CONFIG, "OwningControllerFD is not supported on Windows. " + "If you need it, tell the Tor developers."); + return -1; +#else + const unsigned ctrl_flags = + CC_LOCAL_FD_IS_OWNER | + CC_LOCAL_FD_IS_AUTHENTICATED; + tor_socket_t ctrl_sock = (tor_socket_t)options->OwningControllerFD; + if (control_connection_add_local_fd(ctrl_sock, ctrl_flags) < 0) { + log_warn(LD_CONFIG, "Could not add local controller connection with " + "given FD."); + return -1; + } +#endif } /* Load state */ @@ -1778,10 +1815,12 @@ options_act(const or_options_t *old_options) if (options->ClientTransportPlugin) { for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { if (parse_transport_line(options, cl->value, 0, 0) < 0) { + // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated ClientTransportPlugin line " "could not be added!"); return -1; + // LCOV_EXCL_STOP } } } @@ -1789,10 +1828,12 @@ options_act(const or_options_t *old_options) if (options->ServerTransportPlugin && server_mode(options)) { for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { if (parse_transport_line(options, cl->value, 0, 1) < 0) { + // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated ServerTransportPlugin line " "could not be added!"); return -1; + // LCOV_EXCL_STOP } } } @@ -1878,8 +1919,10 @@ options_act(const or_options_t *old_options) /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { - log_warn(LD_CONFIG,"Error in accounting options"); + // LCOV_EXCL_START + log_warn(LD_BUG,"Error in previously validated accounting options"); return -1; + // LCOV_EXCL_STOP } if (accounting_is_enabled(options)) configure_accounting(time(NULL)); @@ -1902,6 +1945,7 @@ options_act(const or_options_t *old_options) char *http_authenticator; http_authenticator = alloc_http_authenticator(options->BridgePassword); if (!http_authenticator) { + // XXXX This should get validated in options_validate(). log_warn(LD_BUG, "Unable to allocate HTTP authenticator. Not setting " "BridgePassword."); return -1; @@ -1914,9 +1958,12 @@ options_act(const or_options_t *old_options) } if (parse_outbound_addresses(options, 0, &msg) < 0) { - log_warn(LD_BUG, "Failed parsing outbound bind addresses: %s", msg); + // LCOV_EXCL_START + log_warn(LD_BUG, "Failed parsing previously validated outbound " + "bind addresses: %s", msg); tor_free(msg); return -1; + // LCOV_EXCL_STOP } config_maybe_load_geoip_files_(options, old_options); @@ -4612,6 +4659,12 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->OwningControllerFD != new_val->OwningControllerFD) { + *msg = tor_strdup("While Tor is running, changing OwningControllerFD " + "is not allowed."); + return -1; + } + if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ @@ -5024,7 +5077,8 @@ load_torrc_from_disk(config_line_t *cmd_arg, int defaults_file) /** Read a configuration file into <b>options</b>, finding the configuration * file location based on the command line. After loading the file * call options_init_from_string() to load the config. - * Return 0 if success, -1 if failure. */ + * Return 0 if success, -1 if failure, and 1 if we succeeded but should exit + * anyway. */ int options_init_from_torrc(int argc, char **argv) { @@ -5051,22 +5105,22 @@ options_init_from_torrc(int argc, char **argv) if (config_line_find(cmdline_only_options, "-h") || config_line_find(cmdline_only_options, "--help")) { print_usage(); - exit(0); + return 1; } if (config_line_find(cmdline_only_options, "--list-torrc-options")) { /* For validating whether we've documented everything. */ list_torrc_options(); - exit(0); + return 1; } if (config_line_find(cmdline_only_options, "--list-deprecated-options")) { /* For validating whether what we have deprecated really exists. */ list_deprecated_options(); - exit(0); + return 1; } if (config_line_find(cmdline_only_options, "--version")) { printf("Tor version %s.\n",get_version()); - exit(0); + return 1; } if (config_line_find(cmdline_only_options, "--library-versions")) { @@ -5094,7 +5148,7 @@ options_init_from_torrc(int argc, char **argv) tor_compress_header_version_str(ZSTD_METHOD)); } //TODO: Hex versions? - exit(0); + return 1; } command = CMD_RUN_TOR; @@ -5155,7 +5209,8 @@ options_init_from_torrc(int argc, char **argv) get_options_mutable()->keygen_force_passphrase = FORCE_PASSPHRASE_OFF; } else { log_err(LD_CONFIG, "--no-passphrase specified without --keygen!"); - exit(1); + retval = -1; + goto err; } } @@ -5164,7 +5219,8 @@ options_init_from_torrc(int argc, char **argv) get_options_mutable()->change_key_passphrase = 1; } else { log_err(LD_CONFIG, "--newpass specified without --keygen!"); - exit(1); + retval = -1; + goto err; } } @@ -5174,17 +5230,20 @@ options_init_from_torrc(int argc, char **argv) if (fd_line) { if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_OFF) { log_err(LD_CONFIG, "--no-passphrase specified with --passphrase-fd!"); - exit(1); + retval = -1; + goto err; } else if (command != CMD_KEYGEN) { log_err(LD_CONFIG, "--passphrase-fd specified without --keygen!"); - exit(1); + retval = -1; + goto err; } else { const char *v = fd_line->value; int ok = 1; long fd = tor_parse_long(v, 10, 0, INT_MAX, &ok, NULL); if (fd < 0 || ok == 0) { log_err(LD_CONFIG, "Invalid --passphrase-fd value %s", escaped(v)); - exit(1); + retval = -1; + goto err; } get_options_mutable()->keygen_passphrase_fd = (int)fd; get_options_mutable()->use_keygen_passphrase_fd = 1; @@ -5199,7 +5258,8 @@ options_init_from_torrc(int argc, char **argv) if (key_line) { if (command != CMD_KEYGEN) { log_err(LD_CONFIG, "--master-key without --keygen!"); - exit(1); + retval = -1; + goto err; } else { get_options_mutable()->master_key_fname = tor_strdup(key_line->value); } @@ -5247,13 +5307,16 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->command = command; newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; + smartlist_t *opened_files = smartlist_new(); for (int i = 0; i < 2; ++i) { const char *body = i==0 ? cf_defaults : cf; if (!body) continue; + /* get config lines, assign them */ retval = config_get_lines_include(body, &cl, 1, - body == cf ? &cf_has_include : NULL); + body == cf ? &cf_has_include : NULL, + opened_files); if (retval < 0) { err = SETOPT_ERR_PARSE; goto err; @@ -5282,6 +5345,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, } newoptions->IncludeUsed = cf_has_include; + newoptions->FilesOpenedByIncludes = opened_files; /* If this is a testing network configuration, change defaults * for a list of dependent config options, re-initialize newoptions @@ -5321,13 +5385,16 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL; /* Assign all options a second time. */ + opened_files = smartlist_new(); for (int i = 0; i < 2; ++i) { const char *body = i==0 ? cf_defaults : cf; if (!body) continue; + /* get config lines, assign them */ retval = config_get_lines_include(body, &cl, 1, - body == cf ? &cf_has_include : NULL); + body == cf ? &cf_has_include : NULL, + opened_files); if (retval < 0) { err = SETOPT_ERR_PARSE; goto err; @@ -5352,6 +5419,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, newoptions->IncludeUsed = cf_has_include; in_option_validation = 1; + newoptions->FilesOpenedByIncludes = opened_files; /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, @@ -5378,6 +5446,12 @@ options_init_from_string(const char *cf_defaults, const char *cf, err: in_option_validation = 0; + if (opened_files) { + SMARTLIST_FOREACH(opened_files, char *, f, tor_free(f)); + smartlist_free(opened_files); + } + // may have been set to opened_files, avoid double free + newoptions->FilesOpenedByIncludes = NULL; or_options_free(newoptions); or_options_free(newdefaultoptions); if (*msg) { diff --git a/src/or/connection.c b/src/or/connection.c index 632a833652..e6aae71627 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -118,8 +118,6 @@ static connection_t *connection_listener_new( const port_cfg_t *portcfg); static void connection_init(time_t now, connection_t *conn, int type, int socket_family); -static int connection_init_accepted_conn(connection_t *conn, - const listener_connection_t *listener); static int connection_handle_listener_read(connection_t *conn, int new_type); static int connection_bucket_should_increase(int bucket, or_connection_t *conn); @@ -1662,11 +1660,15 @@ connection_handle_listener_read(connection_t *conn, int new_type) } /** Initialize states for newly accepted connection <b>conn</b>. + * * If conn is an OR, start the TLS handshake. + * * If conn is a transparent AP, get its original destination * and place it in circuit_wait. + * + * The <b>listener</b> parameter is only used for AP connections. */ -static int +int connection_init_accepted_conn(connection_t *conn, const listener_connection_t *listener) { @@ -4045,6 +4047,68 @@ connection_flush(connection_t *conn) return connection_handle_write(conn, 1); } +/** Helper for connection_write_to_buf_impl and connection_write_buf_to_buf: + * + * Return true iff it is okay to queue bytes on <b>conn</b>'s outbuf for + * writing. + */ +static int +connection_may_write_to_buf(connection_t *conn) +{ + /* if it's marked for close, only allow write if we mean to flush it */ + if (conn->marked_for_close && !conn->hold_open_until_flushed) + return 0; + + return 1; +} + +/** Helper for connection_write_to_buf_impl and connection_write_buf_to_buf: + * + * Called when an attempt to add bytes on <b>conn</b>'s outbuf has failed; + * mark the connection and warn as appropriate. + */ +static void +connection_write_to_buf_failed(connection_t *conn) +{ + if (CONN_IS_EDGE(conn)) { + /* if it failed, it means we have our package/delivery windows set + wrong compared to our max outbuf size. close the whole circuit. */ + log_warn(LD_NET, + "write_to_buf failed. Closing circuit (fd %d).", (int)conn->s); + circuit_mark_for_close(circuit_get_by_edge_conn(TO_EDGE_CONN(conn)), + END_CIRC_REASON_INTERNAL); + } else if (conn->type == CONN_TYPE_OR) { + or_connection_t *orconn = TO_OR_CONN(conn); + log_warn(LD_NET, + "write_to_buf failed on an orconn; notifying of error " + "(fd %d)", (int)(conn->s)); + connection_or_close_for_error(orconn, 0); + } else { + log_warn(LD_NET, + "write_to_buf failed. Closing connection (fd %d).", + (int)conn->s); + connection_mark_for_close(conn); + } +} + +/** Helper for connection_write_to_buf_impl and connection_write_buf_to_buf: + * + * Called when an attempt to add bytes on <b>conn</b>'s outbuf has succeeded: + * record the number of bytes added. + */ +static void +connection_write_to_buf_commit(connection_t *conn, size_t len) +{ + /* If we receive optimistic data in the EXIT_CONN_STATE_RESOLVING + * state, we don't want to try to write it right away, since + * conn->write_event won't be set yet. Otherwise, write data from + * this conn as the socket is available. */ + if (conn->write_event) { + connection_start_writing(conn); + } + conn->outbuf_flushlen += len; +} + /** Append <b>len</b> bytes of <b>string</b> onto <b>conn</b>'s * outbuf, and ask it to start writing. * @@ -4059,58 +4123,52 @@ connection_write_to_buf_impl_,(const char *string, size_t len, { /* XXXX This function really needs to return -1 on failure. */ int r; - size_t old_datalen; if (!len && !(zlib<0)) return; - /* if it's marked for close, only allow write if we mean to flush it */ - if (conn->marked_for_close && !conn->hold_open_until_flushed) + + if (!connection_may_write_to_buf(conn)) return; - old_datalen = buf_datalen(conn->outbuf); + size_t written; + if (zlib) { + size_t old_datalen = buf_datalen(conn->outbuf); dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; CONN_LOG_PROTECT(conn, r = buf_add_compress(conn->outbuf, - dir_conn->compress_state, - string, len, done)); + dir_conn->compress_state, + string, len, done)); + written = buf_datalen(conn->outbuf) - old_datalen; } else { CONN_LOG_PROTECT(conn, r = buf_add(conn->outbuf, string, len)); + written = len; } if (r < 0) { - if (CONN_IS_EDGE(conn)) { - /* if it failed, it means we have our package/delivery windows set - wrong compared to our max outbuf size. close the whole circuit. */ - log_warn(LD_NET, - "write_to_buf failed. Closing circuit (fd %d).", (int)conn->s); - circuit_mark_for_close(circuit_get_by_edge_conn(TO_EDGE_CONN(conn)), - END_CIRC_REASON_INTERNAL); - } else if (conn->type == CONN_TYPE_OR) { - or_connection_t *orconn = TO_OR_CONN(conn); - log_warn(LD_NET, - "write_to_buf failed on an orconn; notifying of error " - "(fd %d)", (int)(conn->s)); - connection_or_close_for_error(orconn, 0); - } else { - log_warn(LD_NET, - "write_to_buf failed. Closing connection (fd %d).", - (int)conn->s); - connection_mark_for_close(conn); - } + connection_write_to_buf_failed(conn); return; } + connection_write_to_buf_commit(conn, written); +} - /* If we receive optimistic data in the EXIT_CONN_STATE_RESOLVING - * state, we don't want to try to write it right away, since - * conn->write_event won't be set yet. Otherwise, write data from - * this conn as the socket is available. */ - if (conn->write_event) { - connection_start_writing(conn); - } - if (zlib) { - conn->outbuf_flushlen += buf_datalen(conn->outbuf) - old_datalen; - } else { - conn->outbuf_flushlen += len; - } +/** + * Add all bytes from <b>buf</b> to <b>conn</b>'s outbuf, draining them + * from <b>buf</b>. (If the connection is marked and will soon be closed, + * nothing is drained.) + */ +void +connection_buf_add_buf(connection_t *conn, buf_t *buf) +{ + tor_assert(conn); + tor_assert(buf); + size_t len = buf_datalen(buf); + if (len == 0) + return; + + if (!connection_may_write_to_buf(conn)) + return; + + buf_move_all(conn->outbuf, buf); + connection_write_to_buf_commit(conn, len); } #define CONN_GET_ALL_TEMPLATE(var, test) \ diff --git a/src/or/connection.h b/src/or/connection.h index 4a5bd6971b..450229ce8f 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -26,7 +26,8 @@ entry_connection_t *entry_connection_new(int type, int socket_family); control_connection_t *control_connection_new(int socket_family); listener_connection_t *listener_connection_new(int type, int socket_family); connection_t *connection_new(int type, int socket_family); - +int connection_init_accepted_conn(connection_t *conn, + const listener_connection_t *listener); void connection_link_connections(connection_t *conn_a, connection_t *conn_b); MOCK_DECL(void,connection_free,(connection_t *conn)); void connection_free_all(void); @@ -155,6 +156,7 @@ connection_buf_add_compress(const char *string, size_t len, { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); } +void connection_buf_add_buf(connection_t *conn, buf_t *buf); /* DOCDOC connection_get_inbuf_len */ static size_t connection_get_inbuf_len(connection_t *conn); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index f178917f0b..41f0fe6a1d 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -999,7 +999,7 @@ connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, * So the fix is to tell it right now that it ought to finish its loop at * its next available opportunity. */ - tell_event_loop_to_finish(); + tell_event_loop_to_run_external_code(); } /** Mark <b>entry_conn</b> as no longer waiting for a circuit. */ diff --git a/src/or/control.c b/src/or/control.c index 8173cb1e56..d417d42f73 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -549,6 +549,49 @@ decode_escaped_string(const char *start, size_t in_len_max, return end+1; } +/** Create and add a new controller connection on <b>sock</b>. If + * <b>CC_LOCAL_FD_IS_OWNER</b> is set in <b>flags</b>, this Tor process should + * exit when the connection closes. If <b>CC_LOCAL_FD_IS_AUTHENTICATED</b> + * is set, then the connection does not need to authenticate. + */ +int +control_connection_add_local_fd(tor_socket_t sock, unsigned flags) +{ + if (BUG(! SOCKET_OK(sock))) + return -1; + const int is_owner = !!(flags & CC_LOCAL_FD_IS_OWNER); + const int is_authenticated = !!(flags & CC_LOCAL_FD_IS_AUTHENTICATED); + control_connection_t *control_conn = control_connection_new(AF_UNSPEC); + connection_t *conn = TO_CONN(control_conn); + conn->s = sock; + tor_addr_make_unspec(&conn->addr); + conn->port = 1; + conn->address = tor_strdup("<local socket>"); + + /* We take ownership of this socket so that later, when we close it, + * we don't freak out. */ + tor_take_socket_ownership(sock); + + if (set_socket_nonblocking(sock) < 0 || + connection_add(conn) < 0) { + connection_free(conn); + return -1; + } + + control_conn->is_owning_control_connection = is_owner; + + if (connection_init_accepted_conn(conn, NULL) < 0) { + connection_mark_for_close(conn); + return -1; + } + + if (is_authenticated) { + conn->state = CONTROL_CONN_STATE_OPEN; + } + + return 0; +} + /** Acts like sprintf, but writes its formatted string to the end of * <b>conn</b>-\>outbuf. */ static void @@ -6571,8 +6614,7 @@ monitor_owning_controller_process(const char *process_spec) "owning controller: %s. Exiting.", msg); owning_controller_process_spec = NULL; - tor_cleanup(); - exit(1); + tor_shutdown_event_loop_and_exit(1); } } diff --git a/src/or/control.h b/src/or/control.h index e957b593a6..7ec182cb78 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -27,6 +27,10 @@ void control_ports_write_to_file(void); #define LOG_FN_CONN(conn, args) \ CONN_LOG_PROTECT(conn, log_fn args) +#define CC_LOCAL_FD_IS_OWNER (1u<<0) +#define CC_LOCAL_FD_IS_AUTHENTICATED (1u<<1) +int control_connection_add_local_fd(tor_socket_t sock, unsigned flags); + int connection_control_finished_flushing(control_connection_t *conn); int connection_control_reached_eof(control_connection_t *conn); void connection_control_closed(control_connection_t *conn); diff --git a/src/or/directory.c b/src/or/directory.c index 7a1364bd7d..4dd6da592a 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -2237,8 +2237,6 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } -static int handle_response_fetch_consensus(dir_connection_t *, - const response_handler_args_t *); static int handle_response_fetch_certificate(dir_connection_t *, const response_handler_args_t *); static int handle_response_fetch_status_vote(dir_connection_t *, @@ -2585,7 +2583,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * consensus document by checking the consensus, storing it, and marking * router requests as reachable. **/ -static int +STATIC int handle_response_fetch_consensus(dir_connection_t *conn, const response_handler_args_t *args) { @@ -3517,63 +3515,47 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, long cache_lifetime) { char date[RFC1123_TIME_LEN+1]; - char tmp[1024]; - char *cp; time_t now = time(NULL); + buf_t *buf = buf_new_with_capacity(1024); tor_assert(conn); format_rfc1123_time(date, now); - cp = tmp; - tor_snprintf(cp, sizeof(tmp), - "HTTP/1.0 200 OK\r\nDate: %s\r\n", - date); - cp += strlen(tmp); + + buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date); if (type) { - tor_snprintf(cp, sizeof(tmp)-(cp-tmp), "Content-Type: %s\r\n", type); - cp += strlen(cp); + buf_add_printf(buf, "Content-Type: %s\r\n", type); } if (!is_local_addr(&conn->base_.addr)) { /* Don't report the source address for a nearby/private connection. * Otherwise we tend to mis-report in cases where incoming ports are * being forwarded to a Tor server running behind the firewall. */ - tor_snprintf(cp, sizeof(tmp)-(cp-tmp), - X_ADDRESS_HEADER "%s\r\n", conn->base_.address); - cp += strlen(cp); + buf_add_printf(buf, X_ADDRESS_HEADER "%s\r\n", conn->base_.address); } if (encoding) { - tor_snprintf(cp, sizeof(tmp)-(cp-tmp), - "Content-Encoding: %s\r\n", encoding); - cp += strlen(cp); + buf_add_printf(buf, "Content-Encoding: %s\r\n", encoding); } if (length >= 0) { - tor_snprintf(cp, sizeof(tmp)-(cp-tmp), - "Content-Length: %ld\r\n", (long)length); - cp += strlen(cp); + buf_add_printf(buf, "Content-Length: %ld\r\n", (long)length); } if (cache_lifetime > 0) { char expbuf[RFC1123_TIME_LEN+1]; format_rfc1123_time(expbuf, (time_t)(now + cache_lifetime)); /* We could say 'Cache-control: max-age=%d' here if we start doing * http/1.1 */ - tor_snprintf(cp, sizeof(tmp)-(cp-tmp), - "Expires: %s\r\n", expbuf); - cp += strlen(cp); + buf_add_printf(buf, "Expires: %s\r\n", expbuf); } else if (cache_lifetime == 0) { /* We could say 'Cache-control: no-cache' here if we start doing * http/1.1 */ - strlcpy(cp, "Pragma: no-cache\r\n", sizeof(tmp)-(cp-tmp)); - cp += strlen(cp); + buf_add_string(buf, "Pragma: no-cache\r\n"); } if (extra_headers) { - strlcpy(cp, extra_headers, sizeof(tmp)-(cp-tmp)); - cp += strlen(cp); + buf_add_string(buf, extra_headers); } - if (sizeof(tmp)-(cp-tmp) > 3) - memcpy(cp, "\r\n", 3); - else - tor_assert(0); - connection_buf_add(tmp, strlen(tmp), TO_CONN(conn)); + buf_add_string(buf, "\r\n"); + + connection_buf_add_buf(TO_CONN(conn), buf); + buf_free(buf); } /** As write_http_response_header_impl, but sets encoding and content-typed diff --git a/src/or/directory.h b/src/or/directory.h index 79984be32d..764f0092e0 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -194,6 +194,9 @@ STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, const response_handler_args_t *args); +STATIC int handle_response_fetch_consensus(dir_connection_t *conn, + const response_handler_args_t *args); + #endif /* defined(DIRECTORY_PRIVATE) */ #ifdef TOR_UNIT_TESTS diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 9fbf426433..76a8f591b9 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2126,9 +2126,9 @@ circuit_guard_state_free(circuit_guard_state_t *state) /** Allocate and return a new circuit_guard_state_t to track the result * of using <b>guard</b> for a given operation. */ -static circuit_guard_state_t * -circuit_guard_state_new(entry_guard_t *guard, unsigned state, - entry_guard_restriction_t *rst) +MOCK_IMPL(STATIC circuit_guard_state_t *, +circuit_guard_state_new,(entry_guard_t *guard, unsigned state, + entry_guard_restriction_t *rst)) { circuit_guard_state_t *result; diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 9e1e729930..86f0517df2 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -488,6 +488,10 @@ STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs, MOCK_DECL(STATIC time_t, randomize_time, (time_t now, time_t max_backdate)); +MOCK_DECL(STATIC circuit_guard_state_t *, + circuit_guard_state_new,(entry_guard_t *guard, unsigned state, + entry_guard_restriction_t *rst)); + STATIC entry_guard_t *entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node); STATIC entry_guard_t *entry_guards_expand_sample(guard_selection_t *gs); diff --git a/src/or/geoip.c b/src/or/geoip.c index 3944b2cf69..c976b8d276 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -30,6 +30,7 @@ #define GEOIP_PRIVATE #include "or.h" #include "ht.h" +#include "buffers.h" #include "config.h" #include "control.h" #include "dnsserv.h" @@ -930,9 +931,9 @@ static char * geoip_get_dirreq_history(dirreq_type_t type) { char *result = NULL; + buf_t *buf = NULL; smartlist_t *dirreq_completed = NULL; uint32_t complete = 0, timeouts = 0, running = 0; - int bufsize = 1024, written; dirreq_map_entry_t **ptr, **next; struct timeval now; @@ -965,13 +966,9 @@ geoip_get_dirreq_history(dirreq_type_t type) DIR_REQ_GRANULARITY); running = round_uint32_to_next_multiple_of(running, DIR_REQ_GRANULARITY); - result = tor_malloc_zero(bufsize); - written = tor_snprintf(result, bufsize, "complete=%u,timeout=%u," - "running=%u", complete, timeouts, running); - if (written < 0) { - tor_free(result); - goto done; - } + buf = buf_new_with_capacity(1024); + buf_add_printf(buf, "complete=%u,timeout=%u," + "running=%u", complete, timeouts, running); #define MIN_DIR_REQ_RESPONSES 16 if (complete >= MIN_DIR_REQ_RESPONSES) { @@ -992,7 +989,7 @@ geoip_get_dirreq_history(dirreq_type_t type) dltimes[ent_sl_idx] = bytes_per_second; } SMARTLIST_FOREACH_END(ent); median_uint32(dltimes, complete); /* sorts as a side effect. */ - written = tor_snprintf(result + written, bufsize - written, + buf_add_printf(buf, ",min=%u,d1=%u,d2=%u,q1=%u,d3=%u,d4=%u,md=%u," "d6=%u,d7=%u,q3=%u,d8=%u,d9=%u,max=%u", dltimes[0], @@ -1008,14 +1005,15 @@ geoip_get_dirreq_history(dirreq_type_t type) dltimes[8*complete/10-1], dltimes[9*complete/10-1], dltimes[complete-1]); - if (written<0) - tor_free(result); tor_free(dltimes); } - done: + + result = buf_extract(buf, NULL); + SMARTLIST_FOREACH(dirreq_completed, dirreq_map_entry_t *, ent, tor_free(ent)); smartlist_free(dirreq_completed); + buf_free(buf); return result; } diff --git a/src/or/git_revision.c b/src/or/git_revision.c new file mode 100644 index 0000000000..8f326b8751 --- /dev/null +++ b/src/or/git_revision.c @@ -0,0 +1,17 @@ +/* Copyright 2001-2004 Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "git_revision.h" + +/** String describing which Tor Git repository version the source was + * built from. This string is generated by a bit of shell kludging in + * src/or/include.am, and is usually right. + */ +const char tor_git_revision[] = +#ifndef _MSC_VER +#include "micro-revision.i" +#endif + ""; + diff --git a/src/or/git_revision.h b/src/or/git_revision.h new file mode 100644 index 0000000000..1ceaeedf16 --- /dev/null +++ b/src/or/git_revision.h @@ -0,0 +1,12 @@ +/* Copyright 2001-2004 Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_GIT_REVISION_H +#define TOR_GIT_REVISION_H + +extern const char tor_git_revision[]; + +#endif + diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 74ab766468..4dc35f68d0 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -34,6 +34,7 @@ hibernating, phase 2: #include "config.h" #include "connection.h" #include "connection_edge.h" +#include "connection_or.h" #include "control.h" #include "hibernate.h" #include "main.h" @@ -818,8 +819,8 @@ hibernate_begin(hibernate_state_t new_state, time_t now) log_notice(LD_GENERAL,"SIGINT received %s; exiting now.", hibernate_state == HIBERNATE_STATE_EXITING ? "a second time" : "while hibernating"); - tor_cleanup(); - exit(0); + tor_shutdown_event_loop_and_exit(0); + return; } if (new_state == HIBERNATE_STATE_LOWBANDWIDTH && @@ -906,20 +907,23 @@ hibernate_go_dormant(time_t now) while ((conn = connection_get_by_type(CONN_TYPE_OR)) || (conn = connection_get_by_type(CONN_TYPE_AP)) || (conn = connection_get_by_type(CONN_TYPE_EXIT))) { - if (CONN_IS_EDGE(conn)) + if (CONN_IS_EDGE(conn)) { connection_edge_end(TO_EDGE_CONN(conn), END_STREAM_REASON_HIBERNATING); + } log_info(LD_NET,"Closing conn type %d", conn->type); - if (conn->type == CONN_TYPE_AP) /* send socks failure if needed */ + if (conn->type == CONN_TYPE_AP) { + /* send socks failure if needed */ connection_mark_unattached_ap(TO_ENTRY_CONN(conn), END_STREAM_REASON_HIBERNATING); - else if (conn->type == CONN_TYPE_OR) { + } else if (conn->type == CONN_TYPE_OR) { if (TO_OR_CONN(conn)->chan) { - channel_mark_for_close(TLS_CHAN_TO_BASE(TO_OR_CONN(conn)->chan)); + connection_or_close_normally(TO_OR_CONN(conn), 0); } else { connection_mark_for_close(conn); } - } else + } else { connection_mark_for_close(conn); + } } if (now < interval_wakeup_time) @@ -980,8 +984,7 @@ consider_hibernation(time_t now) tor_assert(shutdown_time); if (shutdown_time <= now) { log_notice(LD_GENERAL, "Clean shutdown finished. Exiting."); - tor_cleanup(); - exit(0); + tor_shutdown_event_loop_and_exit(0); } return; /* if exiting soon, don't worry about bandwidth limits */ } diff --git a/src/or/include.am b/src/or/include.am index 7216aba9af..b783f4855a 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -51,6 +51,7 @@ LIBTOR_A_SOURCES = \ src/or/geoip.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ + src/or/git_revision.c \ src/or/hibernate.c \ src/or/hs_cache.c \ src/or/hs_cell.c \ @@ -78,6 +79,7 @@ LIBTOR_A_SOURCES = \ src/or/parsecommon.c \ src/or/periodic.c \ src/or/protover.c \ + src/or/protover_rust.c \ src/or/proto_cell.c \ src/or/proto_control0.c \ src/or/proto_ext_or.c \ @@ -104,6 +106,7 @@ LIBTOR_A_SOURCES = \ src/or/statefile.c \ src/or/status.c \ src/or/torcert.c \ + src/or/tor_api.c \ src/or/onion_ntor.c \ $(tor_platform_source) @@ -189,6 +192,7 @@ ORHEADERS = \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ + src/or/git_revision.h \ src/or/hibernate.h \ src/or/hs_cache.h \ src/or/hs_cell.h \ @@ -243,9 +247,13 @@ ORHEADERS = \ src/or/scheduler.h \ src/or/statefile.h \ src/or/status.h \ - src/or/torcert.h + src/or/torcert.h \ + src/or/tor_api_internal.h -noinst_HEADERS+= $(ORHEADERS) micro-revision.i +# This may someday want to be an installed file? +noinst_HEADERS += src/or/tor_api.h + +noinst_HEADERS += $(ORHEADERS) micro-revision.i micro-revision.i: FORCE $(AM_V_at)rm -f micro-revision.tmp; \ diff --git a/src/or/main.c b/src/or/main.c index c340e4128b..97dcdcb3c2 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -60,7 +60,6 @@ #include "circuitlist.h" #include "circuituse.h" #include "command.h" -#include "compat_rust.h" #include "compress.h" #include "config.h" #include "confparse.h" @@ -106,6 +105,8 @@ #include "shared_random.h" #include "statefile.h" #include "status.h" +#include "tor_api.h" +#include "tor_api_internal.h" #include "util_process.h" #include "ext_orport.h" #ifdef USE_DMALLOC @@ -128,6 +129,12 @@ void evdns_shutdown(int); +#ifdef HAVE_RUST +// helper function defined in Rust to output a log message indicating if tor is +// running with Rust enabled. See src/rust/tor_util +char *rust_welcome_string(void); +#endif + /********* PROTOTYPES **********/ static void dumpmemusage(int severity); @@ -140,6 +147,8 @@ static void connection_start_reading_from_linked_conn(connection_t *conn); static int connection_should_read_from_linked_conn(connection_t *conn); static int run_main_loop_until_done(void); static void process_signal(int sig); +static void shutdown_did_not_work_callback(evutil_socket_t fd, short event, + void *arg) ATTR_NORETURN; /********* START VARIABLES **********/ int global_read_bucket; /**< Max number of bytes I can read this second. */ @@ -192,6 +201,14 @@ static smartlist_t *active_linked_connection_lst = NULL; * <b>loop_once</b>. If so, there's no need to trigger a loopexit in order * to handle linked connections. */ static int called_loop_once = 0; +/** Flag: if true, it's time to shut down, so the main loop should exit as + * soon as possible. + */ +static int main_loop_should_exit = 0; +/** The return value that the main loop should yield when it exits, if + * main_loop_should_exit is true. + */ +static int main_loop_exit_value = 0; /** We set this to 1 when we've opened a circuit, so we can print a log * entry to inform the user that Tor is working. We set it to 0 when @@ -637,9 +654,10 @@ connection_should_read_from_linked_conn(connection_t *conn) /** If we called event_base_loop() and told it to never stop until it * runs out of events, now we've changed our mind: tell it we want it to - * finish. */ + * exit once the current round of callbacks is done, so that we can + * run external code, and then return to the main loop. */ void -tell_event_loop_to_finish(void) +tell_event_loop_to_run_external_code(void) { if (!called_loop_once) { struct timeval tv = { 0, 0 }; @@ -648,6 +666,54 @@ tell_event_loop_to_finish(void) } } +/** Failsafe measure that should never actually be necessary: If + * tor_shutdown_event_loop_and_exit() somehow doesn't successfully exit the + * event loop, then this callback will kill Tor with an assertion failure + * seconds later + */ +static void +shutdown_did_not_work_callback(evutil_socket_t fd, short event, void *arg) +{ + // LCOV_EXCL_START + (void) fd; + (void) event; + (void) arg; + tor_assert_unreached(); + // LCOV_EXCL_STOP +} + +/** + * After finishing the current callback (if any), shut down the main loop, + * clean up the process, and exit with <b>exitcode</b>. + */ +void +tor_shutdown_event_loop_and_exit(int exitcode) +{ + if (main_loop_should_exit) + return; /* Ignore multiple calls to this function. */ + + main_loop_should_exit = 1; + main_loop_exit_value = exitcode; + + /* Die with an assertion failure in ten seconds, if for some reason we don't + * exit normally. */ + /* XXXX We should consider this code if it's never used. */ + struct timeval ten_seconds = { 10, 0 }; + event_base_once(tor_libevent_get_base(), -1, EV_TIMEOUT, + shutdown_did_not_work_callback, NULL, + &ten_seconds); + + /* Unlike loopexit, loopbreak prevents other callbacks from running. */ + tor_event_base_loopbreak(tor_libevent_get_base()); +} + +/** Return true iff tor_shutdown_event_loop_and_exit() has been called. */ +int +tor_event_loop_shutdown_is_pending(void) +{ + return main_loop_should_exit; +} + /** Helper: Tell the main loop to begin reading bytes into <b>conn</b> from * its linked connection, if it is not doing so already. Called by * connection_start_reading and connection_start_writing as appropriate. */ @@ -663,7 +729,7 @@ connection_start_reading_from_linked_conn(connection_t *conn) /* make sure that the event_base_loop() function exits at * the end of its run through the current connections, so we can * activate read events for linked connections. */ - tell_event_loop_to_finish(); + tell_event_loop_to_run_external_code(); } else { tor_assert(smartlist_contains(active_linked_connection_lst, conn)); } @@ -1554,8 +1620,7 @@ check_ed_keys_callback(time_t now, const or_options_t *options) if (new_signing_key < 0 || generate_ed_link_cert(options, now, new_signing_key > 0)) { log_err(LD_OR, "Unable to update Ed25519 keys! Exiting."); - tor_cleanup(); - exit(1); + tor_shutdown_event_loop_and_exit(1); } } return 30; @@ -2366,10 +2431,18 @@ do_hup(void) /* first, reload config variables, in case they've changed */ if (options->ReloadTorrcOnSIGHUP) { /* no need to provide argc/v, they've been cached in init_from_config */ - if (options_init_from_torrc(0, NULL) < 0) { + int init_rv = options_init_from_torrc(0, NULL); + if (init_rv < 0) { log_err(LD_CONFIG,"Reading config failed--see warnings above. " "For usage, try -h."); return -1; + } else if (BUG(init_rv > 0)) { + // LCOV_EXCL_START + /* This should be impossible: the only "return 1" cases in + * options_init_from_torrc are ones caused by command-line arguments; + * but they can't change while Tor is running. */ + return -1; + // LCOV_EXCL_STOP } options = get_options(); /* they have changed now */ /* Logs are only truncated the first time they are opened, but were @@ -2595,6 +2668,9 @@ do_main_loop(void) } #endif /* defined(HAVE_SYSTEMD) */ + main_loop_should_exit = 0; + main_loop_exit_value = 0; + return run_main_loop_until_done(); } @@ -2610,6 +2686,9 @@ run_main_loop_once(void) if (nt_service_is_stopping()) return 0; + if (main_loop_should_exit) + return 0; + #ifndef _WIN32 /* Make it easier to tell whether libevent failure is our fault or not. */ errno = 0; @@ -2656,6 +2735,9 @@ run_main_loop_once(void) } } + if (main_loop_should_exit) + return 0; + /* And here is where we put callbacks that happen "every time the event loop * runs." They must be very fast, or else the whole Tor process will get * slowed down. @@ -2684,7 +2766,11 @@ run_main_loop_until_done(void) do { loop_result = run_main_loop_once(); } while (loop_result == 1); - return loop_result; + + if (main_loop_should_exit) + return main_loop_exit_value; + else + return loop_result; } /** Libevent callback: invoked when we get a signal. @@ -2708,14 +2794,13 @@ process_signal(int sig) { case SIGTERM: log_notice(LD_GENERAL,"Catching signal TERM, exiting cleanly."); - tor_cleanup(); - exit(0); + tor_shutdown_event_loop_and_exit(0); break; case SIGINT: if (!server_mode(get_options())) { /* do it now */ log_notice(LD_GENERAL,"Interrupt: exiting cleanly."); - tor_cleanup(); - exit(0); + tor_shutdown_event_loop_and_exit(0); + return; } #ifdef HAVE_SYSTEMD sd_notify(0, "STOPPING=1"); @@ -2744,8 +2829,8 @@ process_signal(int sig) #endif if (do_hup() < 0) { log_warn(LD_CONFIG,"Restart failed (config error?). Exiting."); - tor_cleanup(); - exit(1); + tor_shutdown_event_loop_and_exit(1); + return; } #ifdef HAVE_SYSTEMD sd_notify(0, "READY=1"); @@ -3019,7 +3104,8 @@ activate_signal(int signal_num) } } -/** Main entry point for the Tor command-line client. +/** Main entry point for the Tor command-line client. Return 0 on "success", + * negative on "failure", and positive on "success and exit". */ int tor_init(int argc, char *argv[]) @@ -3111,14 +3197,13 @@ tor_init(int argc, char *argv[]) "Expect more bugs than usual."); } - { - rust_str_t rust_str = rust_welcome_string(); - const char *s = rust_str_get(rust_str); - if (strlen(s) > 0) { - log_notice(LD_GENERAL, "%s", s); - } - rust_str_free(rust_str); +#ifdef HAVE_RUST + char *rust_str = rust_welcome_string(); + if (rust_str != NULL && strlen(rust_str) > 0) { + log_notice(LD_GENERAL, "%s", rust_str); } + tor_free(rust_str); +#endif if (network_init()<0) { log_err(LD_BUG,"Error initializing network; exiting."); @@ -3126,9 +3211,14 @@ tor_init(int argc, char *argv[]) } atexit(exit_function); - if (options_init_from_torrc(argc,argv) < 0) { + int init_rv = options_init_from_torrc(argc,argv); + if (init_rv < 0) { log_err(LD_CONFIG,"Reading config failed--see warnings above."); return -1; + } else if (init_rv > 0) { + // We succeeded, and should exit anyway -- probably the user just said + // "--version" or something like that. + return 1; } /* The options are now initialised */ @@ -3198,7 +3288,7 @@ try_locking(const or_options_t *options, int err_if_locked) r = try_locking(options, 0); if (r<0) { log_err(LD_GENERAL, "No, it's still there. Exiting."); - exit(1); + return -1; } return r; } @@ -3521,6 +3611,10 @@ sandbox_init_filter(void) } } + SMARTLIST_FOREACH(options->FilesOpenedByIncludes, char *, f, { + OPEN(f); + }); + #define RENAME_SUFFIX(name, suffix) \ sandbox_cfg_allow_rename(&cfg, \ get_datadir_fname(name suffix), \ @@ -3703,14 +3797,16 @@ sandbox_init_filter(void) return cfg; } -/** Main entry point for the Tor process. Called from main(). */ -/* This function is distinct from main() only so we can link main.c into - * the unittest binary without conflicting with the unittests' main. */ +/* Main entry point for the Tor process. Called from tor_main(), and by + * anybody embedding Tor. */ int -tor_main(int argc, char *argv[]) +tor_run_main(const tor_main_configuration_t *tor_cfg) { int result = 0; + int argc = tor_cfg->argc; + char **argv = tor_cfg->argv; + #ifdef _WIN32 #ifndef HeapEnableTerminationOnCorruption #define HeapEnableTerminationOnCorruption 1 @@ -3754,8 +3850,13 @@ tor_main(int argc, char *argv[]) if (done) return result; } #endif /* defined(NT_SERVICE) */ - if (tor_init(argc, argv)<0) - return -1; + { + int init_rv = tor_init(argc, argv); + if (init_rv < 0) + return -1; + else if (init_rv > 0) + return 0; + } if (get_options()->Sandbox && get_options()->command == CMD_RUN_TOR) { sandbox_cfg_t* cfg = sandbox_init_filter(); diff --git a/src/or/main.h b/src/or/main.h index 132ab12bbb..8eb977575e 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -45,7 +45,9 @@ int connection_is_writing(connection_t *conn); MOCK_DECL(void,connection_stop_writing,(connection_t *conn)); MOCK_DECL(void,connection_start_writing,(connection_t *conn)); -void tell_event_loop_to_finish(void); +void tell_event_loop_to_run_external_code(void); +void tor_shutdown_event_loop_and_exit(int exitcode); +int tor_event_loop_shutdown_is_pending(void); void connection_stop_reading_from_linked_conn(connection_t *conn); @@ -74,8 +76,6 @@ void release_lockfile(void); void tor_cleanup(void); void tor_free_all(int postfork); -int tor_main(int argc, char *argv[]); - int do_main_loop(void); int tor_init(int argc, char **argv); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 93bb8643d8..abcd4d8658 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1671,7 +1671,7 @@ handle_missing_protocol_warning_impl(const networkstatus_t *c, } tor_free(protocol_warning); if (should_exit) - exit(1); + exit(1); // XXXX bad exit: should return from main. } /** Called when we have received a networkstatus <b>c</b>. If there are @@ -1720,7 +1720,7 @@ networkstatus_set_current_consensus(const char *consensus, { networkstatus_t *c=NULL; int r, result = -1; - time_t now = time(NULL); + time_t now = approx_time(); const or_options_t *options = get_options(); char *unverified_fname = NULL, *consensus_fname = NULL; int flav = networkstatus_parse_flavor_name(flavor); diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 508e5844eb..ebbe0018bd 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -195,7 +195,7 @@ nt_service_loadlibrary(void) return; err: printf("Unable to load library support for NT services: exiting.\n"); - exit(1); + exit(1); // exit ok: ntmain can't read libraries } /** If we're compiled to run as an NT service, and the service wants to @@ -318,7 +318,7 @@ nt_service_main(void) printf("Service error %d : %s\n", (int) result, errmsg); tor_free(errmsg); if (result == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { - if (tor_init(backup_argc, backup_argv) < 0) + if (tor_init(backup_argc, backup_argv)) return; switch (get_options()->command) { case CMD_RUN_TOR: diff --git a/src/or/or.h b/src/or/or.h index 809714b5e2..a68f1c3c47 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2234,18 +2234,18 @@ typedef struct { * uploaded it. */ #define ROUTER_PURPOSE_GENERAL 0 /** Tor should avoid using this router for circuit-building: we got it - * from a crontroller. If the controller wants to use it, it'll have to + * from a controller. If the controller wants to use it, it'll have to * ask for it by identity. */ #define ROUTER_PURPOSE_CONTROLLER 1 /** Tor should use this router only for bridge positions in circuits: we got * it via a directory request from the bridge itself, or a bridge - * authority. x*/ + * authority. */ #define ROUTER_PURPOSE_BRIDGE 2 /** Tor should not use this router; it was marked in cached-descriptors with * a purpose we didn't recognize. */ #define ROUTER_PURPOSE_UNKNOWN 255 - /* In what way did we find out about this router? One of ROUTER_PURPOSE_*. + /** In what way did we find out about this router? One of ROUTER_PURPOSE_*. * Routers of different purposes are kept segregated and used for different * things; see notes on ROUTER_PURPOSE_* macros above. */ @@ -3674,6 +3674,7 @@ typedef struct { * interface addresses? * Includes OutboundBindAddresses and * configured ports. */ + int ReducedExitPolicy; /**<Should we use the Reduced Exit Policy? */ config_line_t *SocksPolicy; /**< Lists of socks policy components */ config_line_t *DirPolicy; /**< Lists of dir policy components */ /** Local address to bind outbound sockets */ @@ -4070,6 +4071,8 @@ typedef struct { /** Process specifier for a controller that ‘owns’ this Tor * instance. Tor will terminate if its owning controller does. */ char *OwningControllerProcess; + /** FD specifier for a controller that owns this Tor instance. */ + int OwningControllerFD; int ShutdownWaitLength; /**< When we get a SIGINT and we're a server, how * long do we wait before exiting? */ @@ -4627,6 +4630,9 @@ typedef struct { smartlist_t *Schedulers; /* An ordered list of scheduler_types mapped from Schedulers. */ smartlist_t *SchedulerTypes_; + + /** List of files that were opened by %include in torrc and torrc-defaults */ + smartlist_t *FilesOpenedByIncludes; } or_options_t; #define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) diff --git a/src/or/policies.c b/src/or/policies.c index 4c24bfbc32..1f80130710 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -81,7 +81,8 @@ static int policies_parse_exit_policy_internal( const smartlist_t *configured_addresses, int reject_interface_addresses, int reject_configured_port_addresses, - int add_default_policy); + int add_default_policy, + int add_reduced_policy); /** Replace all "private" entries in *<b>policy</b> with their expanded * equivalents. */ @@ -1144,7 +1145,7 @@ validate_addr_policies(const or_options_t *options, char **msg) "to 1 to disable this warning, and for forward compatibility.", options->ExitPolicy == NULL ? " with the default exit policy" : ""); - if (options->ExitPolicy == NULL) { + if (options->ExitPolicy == NULL && options->ReducedExitPolicy == 0) { log_warn(LD_CONFIG, "In a future version of Tor, ExitRelay 0 may become the " "default when no ExitPolicy is given."); @@ -1877,6 +1878,24 @@ policies_log_first_redundant_entry(const smartlist_t *policy) "reject *:563,reject *:1214,reject *:4661-4666," \ "reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*" +#define REDUCED_EXIT_POLICY \ + "accept *:20-23,accept *:43,accept *:53,accept *:79-81,accept *:88," \ + "accept *:110,accept *:143,accept *:194,accept *:220,accept *:389," \ + "accept *:443,accept *:464,accept *:465,accept *:531,accept *:543-544," \ + "accept *:554,accept *:563,accept *:587,accept *:636,accept *:706," \ + "accept *:749,accept *:873,accept *:902-904,accept *:981,accept *:989-995," \ + "accept *:1194,accept *:1220,accept *:1293,accept *:1500,accept *:1533," \ + "accept *:1677,accept *:1723,accept *:1755,accept *:1863," \ + "accept *:2082-2083,accept *:2086-2087,accept *:2095-2096," \ + "accept *:2102-2104,accept *:3128,accept *:3389,accept *:3690," \ + "accept *:4321,accept *:4643,accept *:5050,accept *:5190," \ + "accept *:5222-5223,accept *:5228,accept *:5900,accept *:6660-6669," \ + "accept *:6679,accept *:6697,accept *:8000,accept *:8008,accept *:8074," \ + "accept *:8080,accept *:8082,accept *:8087-8088,accept *:8232-8233," \ + "accept *:8332-8333,accept *:8443,accept *:8888,accept *:9418," \ + "accept *:9999,accept *:10000,accept *:11371,accept *:19294," \ + "accept *:19638,accept *:50002,accept *:64738,reject *:*" + /** Parse the exit policy <b>cfg</b> into the linked list *<b>dest</b>. * * If <b>ipv6_exit</b> is false, prepend "reject *6:*" to the policy. @@ -1912,7 +1931,8 @@ policies_parse_exit_policy_internal(config_line_t *cfg, const smartlist_t *configured_addresses, int reject_interface_addresses, int reject_configured_port_addresses, - int add_default_policy) + int add_default_policy, + int add_reduced_policy) { if (!ipv6_exit) { append_exit_policy_string(dest, "reject *6:*"); @@ -1938,7 +1958,9 @@ policies_parse_exit_policy_internal(config_line_t *cfg, * effect, and are most likely an error. */ policies_log_first_redundant_entry(*dest); - if (add_default_policy) { + if (add_reduced_policy) { + append_exit_policy_string(dest, REDUCED_EXIT_POLICY); + } else if (add_default_policy) { append_exit_policy_string(dest, DEFAULT_EXIT_POLICY); } else { append_exit_policy_string(dest, "reject *4:*"); @@ -1979,13 +2001,15 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, int add_default = (options & EXIT_POLICY_ADD_DEFAULT) ? 1 : 0; int reject_local_interfaces = (options & EXIT_POLICY_REJECT_LOCAL_INTERFACES) ? 1 : 0; + int add_reduced = (options & EXIT_POLICY_ADD_REDUCED) ? 1 : 0; return policies_parse_exit_policy_internal(cfg,dest,ipv6_enabled, reject_private, configured_addresses, reject_local_interfaces, reject_local_interfaces, - add_default); + add_default, + add_reduced); } /** Helper function that adds a copy of addr to a smartlist as long as it is @@ -2095,7 +2119,10 @@ policies_parse_exit_policy_from_options(const or_options_t *or_options, } if (!or_options->BridgeRelay) { - parser_cfg |= EXIT_POLICY_ADD_DEFAULT; + if (or_options->ReducedExitPolicy) + parser_cfg |= EXIT_POLICY_ADD_REDUCED; + else + parser_cfg |= EXIT_POLICY_ADD_DEFAULT; } if (or_options->ExitPolicyRejectLocalInterfaces) { @@ -2186,21 +2213,16 @@ exit_policy_is_general_exit_helper(smartlist_t *policy, int port) } /** Return true iff <b>ri</b> is "useful as an exit node", meaning - * it allows exit to at least one /8 address space for at least - * two of ports 80, 443, and 6667. */ + * it allows exit to at least one /8 address space for each of ports 80 + * and 443. */ int exit_policy_is_general_exit(smartlist_t *policy) { - static const int ports[] = { 80, 443, 6667 }; - int n_allowed = 0; - int i; if (!policy) /*XXXX disallow NULL policies? */ return 0; - for (i = 0; i < 3; ++i) { - n_allowed += exit_policy_is_general_exit_helper(policy, ports[i]); - } - return n_allowed >= 2; + return (exit_policy_is_general_exit_helper(policy, 80) && + exit_policy_is_general_exit_helper(policy, 443)); } /** Return false if <b>policy</b> might permit access to some addr:port; diff --git a/src/or/policies.h b/src/or/policies.h index 52ff4e2f99..cd97ee7f59 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -22,7 +22,8 @@ #define EXIT_POLICY_REJECT_PRIVATE (1 << 1) #define EXIT_POLICY_ADD_DEFAULT (1 << 2) #define EXIT_POLICY_REJECT_LOCAL_INTERFACES (1 << 3) -#define EXIT_POLICY_OPTION_MAX EXIT_POLICY_REJECT_LOCAL_INTERFACES +#define EXIT_POLICY_ADD_REDUCED (1 << 4) +#define EXIT_POLICY_OPTION_MAX EXIT_POLICY_ADD_REDUCED /* All options set: used for unit testing */ #define EXIT_POLICY_OPTION_ALL ((EXIT_POLICY_OPTION_MAX << 1) - 1) diff --git a/src/or/protover.c b/src/or/protover.c index 1a3e69be10..0e74deb112 100644 --- a/src/or/protover.c +++ b/src/or/protover.c @@ -27,6 +27,8 @@ #include "protover.h" #include "routerparse.h" +#ifndef HAVE_RUST + static const smartlist_t *get_supported_protocol_list(void); static int protocol_list_contains(const smartlist_t *protos, protocol_type_t pr, uint32_t ver); @@ -735,3 +737,5 @@ protover_free_all(void) } } +#endif + diff --git a/src/or/protover.h b/src/or/protover.h index 657977279e..7f1938e0c9 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -70,11 +70,15 @@ typedef struct proto_entry_t { smartlist_t *ranges; } proto_entry_t; +#if !defined(HAVE_RUST) && defined(TOR_UNIT_TESTS) STATIC smartlist_t *parse_protocol_list(const char *s); -STATIC void proto_entry_free(proto_entry_t *entry); STATIC char *encode_protocol_list(const smartlist_t *sl); STATIC const char *protocol_type_to_str(protocol_type_t pr); STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out); +STATIC void proto_entry_free(proto_entry_t *entry); + +#endif + #endif /* defined(PROTOVER_PRIVATE) */ #endif /* !defined(TOR_PROTOVER_H) */ diff --git a/src/or/protover_rust.c b/src/or/protover_rust.c new file mode 100644 index 0000000000..0c409b1681 --- /dev/null +++ b/src/or/protover_rust.c @@ -0,0 +1,19 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* + * \file protover_rust.c + * \brief Provide a C wrapper for functions exposed in /src/rust/protover, + * and safe translation/handling between the Rust/C boundary. + */ + +#include "or.h" +#include "protover.h" + +#ifdef HAVE_RUST + +/* Define for compatibility, used in main.c */ +void protover_free_all(void) {} + +#endif + diff --git a/src/or/routerlist.c b/src/or/routerlist.c index f0bd343f45..f04e2ca160 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -3082,7 +3082,7 @@ signed_descriptor_get_body_impl(const signed_descriptor_t *desc, log_err(LD_DIR, "We couldn't read a descriptor that is supposedly " "mmaped in our cache. Is another process running in our data " "directory? Exiting."); - exit(1); + exit(1); // XXXX bad exit: should recover. } } if (!r) /* no mmap, or not in cache. */ @@ -3096,7 +3096,7 @@ signed_descriptor_get_body_impl(const signed_descriptor_t *desc, log_err(LD_DIR, "descriptor at %p begins with unexpected string %s. " "Is another process running in our data directory? Exiting.", desc, escaped(cp)); - exit(1); + exit(1); // XXXX bad exit: should recover. } } @@ -5358,8 +5358,10 @@ update_extrainfo_downloads(time_t now) smartlist_free(wanted); } -/** Reset the descriptor download failure count on all routers, so that we - * can retry any long-failed routers immediately. +/** Reset the consensus and extra-info download failure count on all routers. + * When we get a new consensus, + * routers_update_status_from_consensus_networkstatus() will reset the + * download statuses on the descriptors in that consensus. */ void router_reset_descriptor_download_failures(void) @@ -5371,6 +5373,8 @@ router_reset_descriptor_download_failures(void) last_descriptor_download_attempted = 0; if (!routerlist) return; + /* We want to download *all* extra-info descriptors, not just those in + * the consensus we currently have (or are about to have) */ SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, { download_status_reset(&ri->cache_info.ei_dl_status); diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 15cdb0bbde..65874ae068 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -4027,7 +4027,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, /** Return the common_digests_t that holds the digests of the * <b>flavor_name</b>-flavored networkstatus according to the detached * signatures document <b>sigs</b>, allocating a new common_digests_t as - * neeeded. */ + * needed. */ static common_digests_t * detached_get_digests(ns_detached_signatures_t *sigs, const char *flavor_name) { @@ -4041,7 +4041,7 @@ detached_get_digests(ns_detached_signatures_t *sigs, const char *flavor_name) /** Return the list of signatures of the <b>flavor_name</b>-flavored * networkstatus according to the detached signatures document <b>sigs</b>, - * allocating a new common_digests_t as neeeded. */ + * allocating a new common_digests_t as needed. */ static smartlist_t * detached_get_signatures(ns_detached_signatures_t *sigs, const char *flavor_name) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 1438dc60f6..510c16b217 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -284,7 +284,7 @@ select_scheduler(void) * wishes of using what it has been configured and don't do a sneaky * fallback. Because this can be changed at runtime, we have to stop tor * right now. */ - exit(1); + exit(1); // XXXX bad exit } /* Set the chosen scheduler. */ diff --git a/src/or/tor_api.c b/src/or/tor_api.c new file mode 100644 index 0000000000..4260cc88f4 --- /dev/null +++ b/src/or/tor_api.c @@ -0,0 +1,88 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor_api.c + **/ + +#include "tor_api.h" +#include "tor_api_internal.h" + +// Include this after the above headers, to insure that they don't +// depend on anything else. +#include "orconfig.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// We don't want to use tor_malloc and tor_free here, since this needs +// to run before anything is initialized at all, and ought to run when +// we're not linked to anything at all. + +#define raw_malloc malloc +#define raw_free free + +tor_main_configuration_t * +tor_main_configuration_new(void) +{ + static const char *fake_argv[] = { "tor" }; + tor_main_configuration_t *cfg = raw_malloc(sizeof(*cfg)); + if (cfg == NULL) + return NULL; + + memset(cfg, 0, sizeof(*cfg)); + + cfg->argc = 1; + cfg->argv = (char **) fake_argv; + + return cfg; +} + +int +tor_main_configuration_set_command_line(tor_main_configuration_t *cfg, + int argc, char *argv[]) +{ + if (cfg == NULL) + return -1; + cfg->argc = argc; + cfg->argv = argv; + return 0; +} + +void +tor_main_configuration_free(tor_main_configuration_t *cfg) +{ + if (cfg == NULL) + return; + raw_free(cfg); +} + +/* Main entry point for the Tor process. Called from main(). + * + * This function is distinct from main() only so we can link main.c into + * the unittest binary without conflicting with the unittests' main. + * + * Some embedders have historically called this function; but that usage is + * deprecated: they should use tor_run_main() instead. + */ +int +tor_main(int argc, char *argv[]) +{ + tor_main_configuration_t *cfg = tor_main_configuration_new(); + if (!cfg) { + puts("INTERNAL ERROR: Allocation failure. Cannot proceed"); + return 1; + } + if (tor_main_configuration_set_command_line(cfg, argc, argv) < 0) { + puts("INTERNAL ERROR: Can't set command line. Cannot proceed."); + return 1; + } + int rv = tor_run_main(cfg); + tor_main_configuration_free(cfg); + return rv; +} + diff --git a/src/or/tor_api.h b/src/or/tor_api.h new file mode 100644 index 0000000000..b12ed718c1 --- /dev/null +++ b/src/or/tor_api.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor_api.h + * \brief Public C API for the Tor network service. + * + * This interface is intended for use by programs that need to link Tor as + * a library, and launch it in a separate thread. If you have the ability + * to run Tor as a separate executable, you should probably do that instead + * of embedding it as a library. + * + * To use this API, first construct a tor_main_configuration_t object using + * tor_main_configuration_new(). Then, you use one or more other function + * calls (such as tor_main_configuration_set_command_line() to configure how + * Tor should be run. Finally, you pass the configuration object to + * tor_run_main(). + * + * At this point, tor_run_main() will block its thread to run a Tor daemon; + * when the Tor daemon exits, it will return. See notes on bugs and + * limitations below. + * + * There is no other public C API to Tor: calling any C Tor function not + * documented in this file is not guaranteed to be stable. + **/ + +#ifndef TOR_API_H +#define TOR_API_H + +typedef struct tor_main_configuration_t tor_main_configuration_t; + +/** + * Create and return a new tor_main_configuration(). + */ +tor_main_configuration_t *tor_main_configuration_new(void); + +/** + * Set the command-line arguments in <b>cfg</b>. + * + * The <b>argc</b> and <b>argv</b> values here are as for main(). The + * contents of the argv pointer must remain unchanged until tor_run_main() has + * finished and you call tor_main_configuration_free(). + * + * Return 0 on success, -1 on failure. + */ +int tor_main_configuration_set_command_line(tor_main_configuration_t *cfg, + int argc, char *argv[]); + +/** + * Release all storage held in <b>cfg</b>. + * + * Once you have passed a tor_main_configuration_t to tor_run_main(), you + * must not free it until tor_run_main() has finished. + */ +void tor_main_configuration_free(tor_main_configuration_t *cfg); + +/** + * Run the tor process, as if from the command line. + * + * The command line arguments from tor_main_configuration_set_command_line() + * are taken as if they had been passed to main(). + * + * This function will not return until Tor is done running. It returns zero + * on success, and nonzero on failure. + * + * BUG 23848: In many cases, tor_main will call exit() or abort() instead of + * returning. This is not the intended long-term behavior; we are trying to + * fix it. + * + * BUG 23847: You can only call tor_main() once in a single process; if it + * returns and you call it again, you may crash. This is not intended + * long-term behavior; we are trying to fix it. + * + * LIMITATION: You cannot run more than one instance of Tor in the same + * process at the same time. Concurrent calls will cause undefined behavior. + * We do not currently have plans to change this. + * + * LIMITATION: While we will try to fix any problems found here, you + * should be aware that Tor was originally written to run as its own + * process, and that the functionality of this file was added later. If + * you find any bugs or strange behavior, please report them, and we'll + * try to straighten them out. + */ +int tor_run_main(const tor_main_configuration_t *); + +/** + * Run the tor process, as if from the command line. + * + * @deprecated Using this function from outside Tor is deprecated; you should + * use tor_run_main() instead. + * + * BUGS: This function has all the same bugs as tor_run_main(). + * + * LIMITATIONS: This function has all the limitations of tor_run_main(). + */ +int tor_main(int argc, char **argv); + +#endif /* !defined(TOR_API_H) */ + diff --git a/src/or/tor_api_internal.h b/src/or/tor_api_internal.h new file mode 100644 index 0000000000..a69ba76420 --- /dev/null +++ b/src/or/tor_api_internal.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_API_INTERNAL_H +#define TOR_API_INTERNAL_H + +/* The contents of this type are private; don't mess with them from outside + * Tor. */ +struct tor_main_configuration_t { + /** As in main() */ + int argc; + /** As in main(). This pointer is owned by the caller */ + char **argv; +}; + +#endif + diff --git a/src/or/tor_main.c b/src/or/tor_main.c index a3a8838602..c203d8248f 100644 --- a/src/or/tor_main.c +++ b/src/or/tor_main.c @@ -3,18 +3,6 @@ * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -extern const char tor_git_revision[]; - -/** String describing which Tor Git repository version the source was - * built from. This string is generated by a bit of shell kludging in - * src/or/include.am, and is usually right. - */ -const char tor_git_revision[] = -#ifndef _MSC_VER -#include "micro-revision.i" -#endif - ""; - /** * \file tor_main.c * \brief Stub module containing a main() function. @@ -26,7 +14,7 @@ const char tor_git_revision[] = int tor_main(int argc, char *argv[]); /** We keep main() in a separate file so that our unit tests can use - * functions from main.c) + * functions from main.c. */ int main(int argc, char *argv[]) diff --git a/src/rust/.cargo/config.in b/src/rust/.cargo/config.in index 414b253a57..301e7fdbe7 100644 --- a/src/rust/.cargo/config.in +++ b/src/rust/.cargo/config.in @@ -5,4 +5,4 @@ @RUST_DL@ replace-with = 'vendored-sources' @RUST_DL@ [source.vendored-sources] -@RUST_DL@ directory = '@RUST_DEPENDENCIES@' +@RUST_DL@ directory = '@TOR_RUST_DEPENDENCIES@' diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 4ac9606ce8..224d2135bf 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -3,6 +3,14 @@ name = "tor_util" version = "0.0.1" dependencies = [ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tor_allocate 0.0.1", +] + +[[package]] +name = "external" +version = "0.0.1" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -10,5 +18,38 @@ name = "libc" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "protover" +version = "0.0.1" +dependencies = [ + "external 0.0.1", + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "smartlist 0.0.1", + "tor_allocate 0.0.1", + "tor_util 0.0.1", +] + +[[package]] +name = "smartlist" +version = "0.0.1" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tor_allocate" +version = "0.0.1" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tor_rust" +version = "0.1.0" +dependencies = [ + "protover 0.0.1", + "tor_util 0.0.1", +] + [metadata] "checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index fc4377e8b4..953c9b96b7 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["tor_util"] +members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", "tor_rust"] [profile.release] debug = true diff --git a/src/rust/external/Cargo.toml b/src/rust/external/Cargo.toml new file mode 100644 index 0000000000..bccd7033a7 --- /dev/null +++ b/src/rust/external/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["The Tor Project"] +version = "0.0.1" +name = "external" + +[dependencies] +libc = "0.2.22" + +[lib] +name = "external" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + diff --git a/src/rust/external/external.rs b/src/rust/external/external.rs new file mode 100644 index 0000000000..b9e17f021d --- /dev/null +++ b/src/rust/external/external.rs @@ -0,0 +1,33 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +use libc::{c_char, c_int}; +use std::ffi::CString; + +extern "C" { + fn tor_version_as_new_as( + platform: *const c_char, + cutoff: *const c_char, + ) -> c_int; +} + +/// Wrap calls to tor_version_as_new_as, defined in src/or/routerparse.c +pub fn c_tor_version_as_new_as(platform: &str, cutoff: &str) -> bool { + // CHK: These functions should log a warning if an error occurs. This + // can be added when integration with tor's logger is added to rust + let c_platform = match CString::new(platform) { + Ok(n) => n, + Err(_) => return false, + }; + + let c_cutoff = match CString::new(cutoff) { + Ok(n) => n, + Err(_) => return false, + }; + + let result: c_int = unsafe { + tor_version_as_new_as(c_platform.as_ptr(), c_cutoff.as_ptr()) + }; + + result == 1 +} diff --git a/src/rust/external/lib.rs b/src/rust/external/lib.rs new file mode 100644 index 0000000000..0af0d6452d --- /dev/null +++ b/src/rust/external/lib.rs @@ -0,0 +1,14 @@ +//! Copyright (c) 2016-2017, The Tor Project, Inc. */ +//! See LICENSE for licensing information */ + +//! Interface for external calls to tor C ABI +//! +//! The purpose of this module is to provide a clean interface for when Rust +//! modules need to interact with functionality in tor C code rather than each +//! module implementing this functionality repeatedly. + +extern crate libc; + +mod external; + +pub use external::*; diff --git a/src/rust/include.am b/src/rust/include.am index 20afc6c4db..9c4337484a 100644 --- a/src/rust/include.am +++ b/src/rust/include.am @@ -1,6 +1,26 @@ -include src/rust/tor_util/include.am +include src/rust/tor_rust/include.am EXTRA_DIST +=\ src/rust/Cargo.toml \ src/rust/Cargo.lock \ - src/rust/.cargo/config.in + src/rust/.cargo/config.in \ + src/rust/external/Cargo.toml \ + src/rust/external/external.rs \ + src/rust/external/lib.rs \ + src/rust/protover/Cargo.toml \ + src/rust/protover/ffi.rs \ + src/rust/protover/lib.rs \ + src/rust/protover/protover.rs \ + src/rust/protover/tests/protover.rs \ + src/rust/smartlist/Cargo.toml \ + src/rust/smartlist/lib.rs \ + src/rust/smartlist/smartlist.rs \ + src/rust/tor_allocate/Cargo.toml \ + src/rust/tor_allocate/lib.rs \ + src/rust/tor_allocate/tor_allocate.rs \ + src/rust/tor_rust/Cargo.toml \ + src/rust/tor_rust/include.am \ + src/rust/tor_rust/lib.rs \ + src/rust/tor_util/Cargo.toml \ + src/rust/tor_util/ffi.rs \ + src/rust/tor_util/lib.rs diff --git a/src/rust/protover/Cargo.toml b/src/rust/protover/Cargo.toml new file mode 100644 index 0000000000..04d2f2ed7d --- /dev/null +++ b/src/rust/protover/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors = ["The Tor Project"] +version = "0.0.1" +name = "protover" + +[dependencies] +libc = "0.2.22" + +[dependencies.smartlist] +path = "../smartlist" + +[dependencies.external] +path = "../external" + +[dependencies.tor_util] +path = "../tor_util" + +[dependencies.tor_allocate] +path = "../tor_allocate" + +[lib] +name = "protover" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + diff --git a/src/rust/protover/ffi.rs b/src/rust/protover/ffi.rs new file mode 100644 index 0000000000..cf2e9fd783 --- /dev/null +++ b/src/rust/protover/ffi.rs @@ -0,0 +1,185 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +//! FFI functions, only to be called from C. +//! +//! Equivalent C versions of this api are in `src/or/protover.c` + +use libc::{c_char, c_int, uint32_t}; +use std::ffi::CStr; +use std::ffi::CString; + +use protover::*; +use smartlist::*; +use tor_allocate::allocate_and_copy_string; + +/// Translate C enums to Rust Proto enums, using the integer value of the C +/// enum to map to its associated Rust enum +/// This is dependant on the associated C enum preserving ordering. +fn translate_to_rust(c_proto: uint32_t) -> Result<Proto, &'static str> { + match c_proto { + 0 => Ok(Proto::Link), + 1 => Ok(Proto::LinkAuth), + 2 => Ok(Proto::Relay), + 3 => Ok(Proto::DirCache), + 4 => Ok(Proto::HSDir), + 5 => Ok(Proto::HSIntro), + 6 => Ok(Proto::HSRend), + 7 => Ok(Proto::Desc), + 8 => Ok(Proto::Microdesc), + 9 => Ok(Proto::Cons), + _ => Err("Invalid protocol type"), + } +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::all_supported +#[no_mangle] +pub extern "C" fn protover_all_supported( + c_relay_version: *const c_char, + missing_out: *mut *mut c_char, +) -> c_int { + + if c_relay_version.is_null() { + return 1; + } + + // Require an unsafe block to read the version from a C string. The pointer + // is checked above to ensure it is not null. + let c_str: &CStr = unsafe { CStr::from_ptr(c_relay_version) }; + + let relay_version = match c_str.to_str() { + Ok(n) => n, + Err(_) => return 1, + }; + + let (is_supported, unsupported) = all_supported(relay_version); + + if unsupported.len() > 0 { + let c_unsupported = match CString::new(unsupported) { + Ok(n) => n, + Err(_) => return 1, + }; + + let ptr = c_unsupported.into_raw(); + unsafe { *missing_out = ptr }; + } + + return if is_supported { 1 } else { 0 }; +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::list_supports_protocol +#[no_mangle] +pub extern "C" fn protocol_list_supports_protocol( + c_protocol_list: *const c_char, + c_protocol: uint32_t, + version: uint32_t, +) -> c_int { + if c_protocol_list.is_null() { + return 1; + } + + // Require an unsafe block to read the version from a C string. The pointer + // is checked above to ensure it is not null. + let c_str: &CStr = unsafe { CStr::from_ptr(c_protocol_list) }; + + let protocol_list = match c_str.to_str() { + Ok(n) => n, + Err(_) => return 1, + }; + + let protocol = match translate_to_rust(c_protocol) { + Ok(n) => n, + Err(_) => return 0, + }; + + let is_supported = + protover_string_supports_protocol(protocol_list, protocol, version); + + return if is_supported { 1 } else { 0 }; +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::get_supported_protocols +#[no_mangle] +pub extern "C" fn protover_get_supported_protocols() -> *mut c_char { + // Not handling errors when unwrapping as the content is controlled + // and is an empty string + let empty = CString::new("").unwrap(); + + let supported = get_supported_protocols(); + let c_supported = match CString::new(supported) { + Ok(n) => n, + Err(_) => return empty.into_raw(), + }; + + c_supported.into_raw() +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::compute_vote +#[no_mangle] +pub extern "C" fn protover_compute_vote( + list: *const Stringlist, + threshold: c_int, +) -> *mut c_char { + + if list.is_null() { + let empty = String::new(); + return allocate_and_copy_string(&empty); + } + + // Dereference of raw pointer requires an unsafe block. The pointer is + // checked above to ensure it is not null. + let data: Vec<String> = unsafe { (*list).get_list() }; + + let vote = compute_vote(data, threshold); + + allocate_and_copy_string(&vote) +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::is_supported_here +#[no_mangle] +pub extern "C" fn protover_is_supported_here( + c_protocol: uint32_t, + version: uint32_t, +) -> c_int { + let protocol = match translate_to_rust(c_protocol) { + Ok(n) => n, + Err(_) => return 0, + }; + + let is_supported = is_supported_here(protocol, version); + + return if is_supported { 1 } else { 0 }; +} + +/// Provide an interface for C to translate arguments and return types for +/// protover::compute_for_old_tor +#[no_mangle] +pub extern "C" fn protover_compute_for_old_tor( + version: *const c_char, +) -> *mut c_char { + // Not handling errors when unwrapping as the content is controlled + // and is an empty string + let empty = String::new(); + + if version.is_null() { + return allocate_and_copy_string(&empty); + } + + // Require an unsafe block to read the version from a C string. The pointer + // is checked above to ensure it is not null. + let c_str: &CStr = unsafe { CStr::from_ptr(version) }; + + let version = match c_str.to_str() { + Ok(n) => n, + Err(_) => return allocate_and_copy_string(&empty), + }; + + let supported = compute_for_old_tor(&version); + + allocate_and_copy_string(&supported) +} diff --git a/src/rust/protover/lib.rs b/src/rust/protover/lib.rs new file mode 100644 index 0000000000..5a5dea4408 --- /dev/null +++ b/src/rust/protover/lib.rs @@ -0,0 +1,33 @@ +//! Copyright (c) 2016-2017, The Tor Project, Inc. */ +//! See LICENSE for licensing information */ + +//! Versioning information for different pieces of the Tor protocol. +//! +//! The below description is taken from src/rust/protover.c, which is currently +//! enabled by default. We are in the process of experimenting with Rust in +//! tor, and this protover module is implemented to help achieve this goal. +//! +//! Starting in version 0.2.9.3-alpha, Tor places separate version numbers on +//! each of the different components of its protocol. Relays use these numbers +//! to advertise what versions of the protocols they can support, and clients +//! use them to find what they can ask a given relay to do. Authorities vote +//! on the supported protocol versions for each relay, and also vote on the +//! which protocols you should have to support in order to be on the Tor +//! network. All Tor instances use these required/recommended protocol versions +//! to tell what level of support for recent protocols each relay has, and +//! to decide whether they should be running given their current protocols. +//! +//! The main advantage of these protocol versions numbers over using Tor +//! version numbers is that they allow different implementations of the Tor +//! protocols to develop independently, without having to claim compatibility +//! with specific versions of Tor. + +extern crate libc; +extern crate smartlist; +extern crate external; +extern crate tor_allocate; + +mod protover; +pub mod ffi; + +pub use protover::*; diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs new file mode 100644 index 0000000000..f19f7972fd --- /dev/null +++ b/src/rust/protover/protover.rs @@ -0,0 +1,852 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +use external::c_tor_version_as_new_as; + +use std::str::FromStr; +use std::fmt; +use std::collections::{HashMap, HashSet}; +use std::string::String; + +/// The first version of Tor that included "proto" entries in its descriptors. +/// Authorities should use this to decide whether to guess proto lines. +const FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS: &'static str = "0.2.9.3-alpha"; + +/// The maximum number of subprotocol version numbers we will attempt to expand +/// before concluding that someone is trying to DoS us +const MAX_PROTOCOLS_TO_EXPAND: u32 = 500; + +/// Currently supported protocols and their versions +const SUPPORTED_PROTOCOLS: &'static [&'static str] = &[ + "Cons=1-2", + "Desc=1-2", + "DirCache=1-2", + "HSDir=1-2", + "HSIntro=3-4", + "HSRend=1-2", + "Link=1-4", + "LinkAuth=1,3", + "Microdesc=1-2", + "Relay=1-2", +]; + +/// Known subprotocols in Tor. Indicates which subprotocol a relay supports. +#[derive(Hash, Eq, PartialEq, Debug)] +pub enum Proto { + Cons, + Desc, + DirCache, + HSDir, + HSIntro, + HSRend, + Link, + LinkAuth, + Microdesc, + Relay, +} + +impl fmt::Display for Proto { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Translates a string representation of a protocol into a Proto type. +/// Error if the string is an unrecognized protocol name. +impl FromStr for Proto { + type Err = &'static str; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "Cons" => Ok(Proto::Cons), + "Desc" => Ok(Proto::Desc), + "DirCache" => Ok(Proto::DirCache), + "HSDir" => Ok(Proto::HSDir), + "HSIntro" => Ok(Proto::HSIntro), + "HSRend" => Ok(Proto::HSRend), + "Link" => Ok(Proto::Link), + "LinkAuth" => Ok(Proto::LinkAuth), + "Microdesc" => Ok(Proto::Microdesc), + "Relay" => Ok(Proto::Relay), + _ => Err("Not a valid protocol type"), + } + } +} + +/// Get the string representation of current supported protocols +/// +/// # Returns +/// +/// A `String` whose value is the existing protocols supported by tor. +/// Returned data is in the format as follows: +/// +/// "HSDir=1-1 LinkAuth=1" +/// +pub fn get_supported_protocols() -> String { + SUPPORTED_PROTOCOLS.join(" ") +} + +/// Translates a vector representation of a protocol list into a HashMap +fn parse_protocols( + protocols: &[&str], +) -> Result<HashMap<Proto, HashSet<u32>>, &'static str> { + let mut parsed = HashMap::new(); + + for subproto in protocols { + let (name, version) = get_proto_and_vers(subproto)?; + parsed.insert(name, version); + } + Ok(parsed) +} + +/// Translates a string representation of a protocol list to a HashMap +fn parse_protocols_from_string<'a>( + protocol_string: &'a str, +) -> Result<HashMap<Proto, HashSet<u32>>, &'static str> { + let protocols: &[&'a str] = + &protocol_string.split(" ").collect::<Vec<&'a str>>()[..]; + + parse_protocols(protocols) +} + +/// Translates supported tor versions from a string into a HashMap, which is +/// useful when looking up a specific subprotocol. +/// +/// # Returns +/// +/// A `Result` whose `Ok` value is a `HashMap<Proto, <u32>>` holding all +/// subprotocols and versions currently supported by tor. +/// +/// The returned `Result`'s `Err` value is an `&'static str` with a description +/// of the error. +/// +fn tor_supported() -> Result<HashMap<Proto, HashSet<u32>>, &'static str> { + parse_protocols(&SUPPORTED_PROTOCOLS) +} + +/// Get the unique version numbers supported by a subprotocol. +/// +/// # Inputs +/// +/// * `version_string`, a string comprised of "[0-9,-]" +/// +/// # Returns +/// +/// A `Result` whose `Ok` value is a `HashSet<u32>` holding all of the unique +/// version numbers. If there were ranges in the `version_string`, then these +/// are expanded, i.e. `"1-3"` would expand to `HashSet<u32>::new([1, 2, 3])`. +/// The returned HashSet is *unordered*. +/// +/// The returned `Result`'s `Err` value is an `&'static str` with a description +/// of the error. +/// +/// # Errors +/// +/// This function will error if: +/// +/// * the `version_string` is empty or contains an equals (`"="`) sign, +/// * the expansion of a version range produces an error (see +/// `expand_version_range`), +/// * any single version number is not parseable as an `u32` in radix 10, or +/// * there are greater than 2^16 version numbers to expand. +/// +fn get_versions(version_string: &str) -> Result<HashSet<u32>, &'static str> { + if version_string.is_empty() { + return Err("version string is empty"); + } + + let mut versions = HashSet::<u32>::new(); + + for piece in version_string.split(",") { + if piece.contains("-") { + for p in expand_version_range(piece)? { + versions.insert(p); + } + } else { + versions.insert(u32::from_str(piece).or( + Err("invalid protocol entry"), + )?); + } + + if versions.len() > MAX_PROTOCOLS_TO_EXPAND as usize { + return Err("Too many versions to expand"); + } + } + Ok(versions) +} + + +/// Parse the subprotocol type and its version numbers. +/// +/// # Inputs +/// +/// * A `protocol_entry` string, comprised of a keyword, an "=" sign, and one +/// or more version numbers. +/// +/// # Returns +/// +/// A `Result` whose `Ok` value is a tuple of `(Proto, HashSet<u32>)`, where the +/// first element is the subprotocol type (see `protover::Proto`) and the last +/// element is a(n unordered) set of unique version numbers which are supported. +/// Otherwise, the `Err` value of this `Result` is a description of the error +/// +fn get_proto_and_vers<'a>( + protocol_entry: &'a str, +) -> Result<(Proto, HashSet<u32>), &'static str> { + let mut parts = protocol_entry.splitn(2, "="); + + let proto = match parts.next() { + Some(n) => n, + None => return Err("invalid protover entry"), + }; + + let vers = match parts.next() { + Some(n) => n, + None => return Err("invalid protover entry"), + }; + + let versions = get_versions(vers)?; + let proto_name = proto.parse()?; + + Ok((proto_name, versions)) +} + +/// Parses a single subprotocol entry string into subprotocol and version +/// parts, and then checks whether any of those versions are unsupported. +/// Helper for protover::all_supported +/// +/// # Inputs +/// +/// Accepted data is in the string format as follows: +/// +/// "HSDir=1-1" +/// +/// # Returns +/// +/// Returns `true` if the protocol entry is well-formatted and only contains +/// versions that are also supported in tor. Otherwise, returns false +/// +fn contains_only_supported_protocols(proto_entry: &str) -> bool { + let (name, mut vers) = match get_proto_and_vers(proto_entry) { + Ok(n) => n, + Err(_) => return false, + }; + + let currently_supported: HashMap<Proto, HashSet<u32>> = + match tor_supported() { + Ok(n) => n, + Err(_) => return false, + }; + + let supported_versions = match currently_supported.get(&name) { + Some(n) => n, + None => return false, + }; + + vers.retain(|x| !supported_versions.contains(x)); + vers.is_empty() +} + +/// Determine if we support every protocol a client supports, and if not, +/// determine which protocols we do not have support for. +/// +/// # Inputs +/// +/// Accepted data is in the string format as follows: +/// +/// "HSDir=1-1 LinkAuth=1-2" +/// +/// # Returns +/// +/// Return `true` if every protocol version is one that we support. +/// Otherwise, return `false`. +/// Optionally, return parameters which the client supports but which we do not +/// +/// # Examples +/// ``` +/// use protover::all_supported; +/// +/// let (is_supported, unsupported) = all_supported("Link=1"); +/// assert_eq!(true, is_supported); +/// +/// let (is_supported, unsupported) = all_supported("Link=5-6"); +/// assert_eq!(false, is_supported); +/// assert_eq!("Link=5-6", unsupported); +/// +pub fn all_supported(protocols: &str) -> (bool, String) { + let unsupported = protocols + .split_whitespace() + .filter(|v| !contains_only_supported_protocols(v)) + .collect::<Vec<&str>>(); + + (unsupported.is_empty(), unsupported.join(" ")) +} + +/// Return true iff the provided protocol list includes support for the +/// indicated protocol and version. +/// Otherwise, return false +/// +/// # Inputs +/// +/// * `list`, a string representation of a list of protocol entries. +/// * `proto`, a `Proto` to test support for +/// * `vers`, a `u32` version which we will go on to determine whether the +/// specified protocol supports. +/// +/// # Examples +/// ``` +/// use protover::*; +/// +/// let is_supported = protover_string_supports_protocol("Link=3-4 Cons=1", +/// Proto::Cons,1); +/// assert_eq!(true, is_supported); +/// +/// let is_not_supported = protover_string_supports_protocol("Link=3-4 Cons=1", +/// Proto::Cons,5); +/// assert_eq!(false, is_not_supported) +/// ``` +pub fn protover_string_supports_protocol( + list: &str, + proto: Proto, + vers: u32, +) -> bool { + let supported: HashMap<Proto, HashSet<u32>>; + + match parse_protocols_from_string(list) { + Ok(result) => supported = result, + Err(_) => return false, + } + + let supported_versions = match supported.get(&proto) { + Some(n) => n, + None => return false, + }; + + supported_versions.contains(&vers) +} + +/// Fully expand a version range. For example, 1-3 expands to 1,2,3 +/// Helper for get_versions +/// +/// # Inputs +/// +/// `range`, a string comprised of "[0-9,-]" +/// +/// # Returns +/// +/// A `Result` whose `Ok` value a vector of unsigned integers representing the +/// expanded range of supported versions by a single protocol. +/// Otherwise, the `Err` value of this `Result` is a description of the error +/// +/// # Errors +/// +/// This function will error if: +/// +/// * the specified range is empty +/// * the version range does not contain both a valid lower and upper bound. +/// +fn expand_version_range(range: &str) -> Result<Vec<u32>, &'static str> { + if range.is_empty() { + return Err("version string empty"); + } + + let mut parts = range.split("-"); + + let lower_string = parts.next().ok_or( + "cannot parse protocol range lower bound", + )?; + + let lower = u32::from_str_radix(lower_string, 10).or(Err( + "cannot parse protocol range lower bound", + ))?; + + let higher_string = parts.next().ok_or( + "cannot parse protocol range upper bound", + )?; + + let higher = u32::from_str_radix(higher_string, 10).or(Err( + "cannot parse protocol range upper bound", + ))?; + + // We can use inclusive range syntax when it becomes stable. + Ok((lower..higher + 1).collect()) +} + +/// Checks to see if there is a continuous range of integers, starting at the +/// first in the list. Returns the last integer in the range if a range exists. +/// Helper for compute_vote +/// +/// # Inputs +/// +/// `list`, an ordered vector of `u32` integers of "[0-9,-]" representing the +/// supported versions for a single protocol. +/// +/// # Returns +/// +/// A `bool` indicating whether the list contains a range, starting at the +/// first in the list, and an `u32` of the last integer in the range. +/// +/// For example, if given vec![1, 2, 3, 5], find_range will return true, +/// as there is a continuous range, and 3, which is the last number in the +/// continuous range. +/// +fn find_range(list: &Vec<u32>) -> (bool, u32) { + if list.len() == 0 { + return (false, 0); + } + + let mut iterable = list.iter().peekable(); + let mut range_end = match iterable.next() { + Some(n) => *n, + None => return (false, 0), + }; + + let mut has_range = false; + + while iterable.peek().is_some() { + let n = *iterable.next().unwrap(); + if n != range_end + 1 { + break; + } + + has_range = true; + range_end = n; + } + + (has_range, range_end) +} + +/// Contracts a HashSet representation of supported versions into a string. +/// Helper for compute_vote +/// +/// # Inputs +/// +/// `supported_set`, a set of integers of "[0-9,-]" representing the +/// supported versions for a single protocol. +/// +/// # Returns +/// +/// A `String` representation of this set in ascending order. +/// +fn contract_protocol_list<'a>(supported_set: &'a HashSet<u32>) -> String { + let mut supported: Vec<u32> = supported_set.iter() + .map(|x| *x) + .collect(); + supported.sort(); + + let mut final_output: Vec<String> = Vec::new(); + + while supported.len() != 0 { + let (has_range, end) = find_range(&supported); + let current = supported.remove(0); + + if has_range { + final_output.push(format!( + "{}-{}", + current.to_string(), + &end.to_string(), + )); + supported.retain(|&x| x > end); + } else { + final_output.push(current.to_string()); + } + } + + final_output.join(",") +} + +/// Parses a protocol list without validating the protocol names +/// +/// # Inputs +/// +/// * `protocol_string`, a string comprised of keys and values, both which are +/// strings. The keys are the protocol names while values are a string +/// representation of the supported versions. +/// +/// The input is _not_ expected to be a subset of the Proto types +/// +/// # Returns +/// +/// A `Result` whose `Ok` value is a `HashSet<u32>` holding all of the unique +/// version numbers. +/// +/// The returned `Result`'s `Err` value is an `&'static str` with a description +/// of the error. +/// +/// # Errors +/// +/// This function will error if: +/// +/// * The protocol string does not follow the "protocol_name=version_list" +/// expected format +/// * If the version string is malformed. See `get_versions`. +/// +fn parse_protocols_from_string_with_no_validation<'a>( + protocol_string: &'a str, +) -> Result<HashMap<String, HashSet<u32>>, &'static str> { + let protocols = &protocol_string.split(" ").collect::<Vec<&'a str>>()[..]; + + let mut parsed: HashMap<String, HashSet<u32>> = HashMap::new(); + + for subproto in protocols { + let mut parts = subproto.splitn(2, "="); + + let name = match parts.next() { + Some(n) => n, + None => return Err("invalid protover entry"), + }; + + let vers = match parts.next() { + Some(n) => n, + None => return Err("invalid protover entry"), + }; + + let versions = get_versions(vers)?; + + parsed.insert(String::from(name), versions); + } + Ok(parsed) +} + +/// Protocol voting implementation. +/// +/// Given a list of strings describing protocol versions, return a new +/// string encoding all of the protocols that are listed by at +/// least threshold of the inputs. +/// +/// The string is sorted according to the following conventions: +/// - Protocols names are alphabetized +/// - Protocols are in order low to high +/// - Individual and ranges are listed together. For example, +/// "3, 5-10,13" +/// - All entries are unique +/// +/// # Examples +/// ``` +/// use protover::compute_vote; +/// +/// let protos = vec![String::from("Link=3-4"), String::from("Link=3")]; +/// let vote = compute_vote(protos, 2); +/// assert_eq!("Link=3", vote) +/// ``` +pub fn compute_vote( + list_of_proto_strings: Vec<String>, + threshold: i32, +) -> String { + let empty = String::from(""); + + if list_of_proto_strings.is_empty() { + return empty; + } + + // all_count is a structure to represent the count of the number of + // supported versions for a specific protocol. For example, in JSON format: + // { + // "FirstSupportedProtocol": { + // "1": "3", + // "2": "1" + // } + // } + // means that FirstSupportedProtocol has three votes which support version + // 1, and one vote that supports version 2 + let mut all_count: HashMap<String, HashMap<u32, usize>> = HashMap::new(); + + // parse and collect all of the protos and their versions and collect them + for vote in list_of_proto_strings { + let this_vote: HashMap<String, HashSet<u32>> = + match parse_protocols_from_string_with_no_validation(&vote) { + Ok(result) => result, + Err(_) => continue, + }; + + for (protocol, versions) in this_vote { + let supported_vers: &mut HashMap<u32, usize> = + all_count.entry(protocol).or_insert(HashMap::new()); + + for version in versions { + let counter: &mut usize = + supported_vers.entry(version).or_insert(0); + *counter += 1; + } + } + } + + let mut final_output: HashMap<String, String> = + HashMap::with_capacity(SUPPORTED_PROTOCOLS.len()); + + // Go through and remove verstions that are less than the threshold + for (protocol, versions) in all_count { + let mut meets_threshold = HashSet::new(); + for (version, count) in versions { + if count >= threshold as usize { + meets_threshold.insert(version); + } + } + + // For each protocol, compress its version list into the expected + // protocol version string format + let contracted = contract_protocol_list(&meets_threshold); + if !contracted.is_empty() { + final_output.insert(protocol, contracted); + } + } + + write_vote_to_string(&final_output) +} + +/// Return a String comprised of protocol entries in alphabetical order +/// +/// # Inputs +/// +/// * `vote`, a `HashMap` comprised of keys and values, both which are strings. +/// The keys are the protocol names while values are a string representation of +/// the supported versions. +/// +/// # Returns +/// +/// A `String` whose value is series of pairs, comprising of the protocol name +/// and versions that it supports. The string takes the following format: +/// +/// "first_protocol_name=1,2-5, second_protocol_name=4,5" +/// +/// Sorts the keys in alphabetical order and creates the expected subprotocol +/// entry format. +/// +fn write_vote_to_string(vote: &HashMap<String, String>) -> String { + let mut keys: Vec<&String> = vote.keys().collect(); + keys.sort(); + + let mut output = Vec::new(); + for key in keys { + // TODO error in indexing here? + output.push(format!("{}={}", key, vote[key])); + } + output.join(" ") +} + +/// Returns a boolean indicating whether the given protocol and version is +/// supported in any of the existing Tor protocols +/// +/// # Examples +/// ``` +/// use protover::*; +/// +/// let is_supported = is_supported_here(Proto::Link, 5); +/// assert_eq!(false, is_supported); +/// +/// let is_supported = is_supported_here(Proto::Link, 1); +/// assert_eq!(true, is_supported); +/// ``` +pub fn is_supported_here(proto: Proto, vers: u32) -> bool { + let currently_supported: HashMap<Proto, HashSet<u32>>; + + match tor_supported() { + Ok(result) => currently_supported = result, + Err(_) => return false, + } + + let supported_versions = match currently_supported.get(&proto) { + Some(n) => n, + None => return false, + }; + + supported_versions.contains(&vers) +} + +/// Older versions of Tor cannot infer their own subprotocols +/// Used to determine which subprotocols are supported by older Tor versions. +/// +/// # Inputs +/// +/// * `version`, a string comprised of "[0-9,-]" +/// +/// # Returns +/// +/// A `String` whose value is series of pairs, comprising of the protocol name +/// and versions that it supports. The string takes the following format: +/// +/// "HSDir=1-1 LinkAuth=1" +/// +/// This function returns the protocols that are supported by the version input, +/// only for tor versions older than FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS. +/// +pub fn compute_for_old_tor(version: &str) -> String { + if c_tor_version_as_new_as( + version, + FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS, + ) + { + return String::new(); + } + + if c_tor_version_as_new_as(version, "0.2.9.1-alpha") { + let ret = "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2"; + return String::from(ret); + } + + if c_tor_version_as_new_as(version, "0.2.7.5") { + let ret = "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2"; + return String::from(ret); + } + + if c_tor_version_as_new_as(version, "0.2.4.19") { + let ret = "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2"; + return String::from(ret); + } + String::new() +} + +#[cfg(test)] +mod test { + #[test] + fn test_get_versions() { + use std::collections::HashSet; + + use super::get_versions; + + assert_eq!(Err("version string is empty"), get_versions("")); + assert_eq!(Err("invalid protocol entry"), get_versions("a,b")); + assert_eq!(Err("invalid protocol entry"), get_versions("1,!")); + + { + let mut versions: HashSet<u32> = HashSet::new(); + versions.insert(1); + assert_eq!(Ok(versions), get_versions("1")); + } + { + let mut versions: HashSet<u32> = HashSet::new(); + versions.insert(1); + versions.insert(2); + assert_eq!(Ok(versions), get_versions("1,2")); + } + { + let mut versions: HashSet<u32> = HashSet::new(); + versions.insert(1); + versions.insert(2); + versions.insert(3); + assert_eq!(Ok(versions), get_versions("1-3")); + } + { + let mut versions: HashSet<u32> = HashSet::new(); + versions.insert(1); + versions.insert(2); + versions.insert(5); + assert_eq!(Ok(versions), get_versions("1-2,5")); + } + { + let mut versions: HashSet<u32> = HashSet::new(); + versions.insert(1); + versions.insert(3); + versions.insert(4); + versions.insert(5); + assert_eq!(Ok(versions), get_versions("1,3-5")); + } + } + + #[test] + fn test_contains_only_supported_protocols() { + use super::contains_only_supported_protocols; + + assert_eq!(false, contains_only_supported_protocols("")); + assert_eq!(false, contains_only_supported_protocols("Cons=")); + assert_eq!(true, contains_only_supported_protocols("Cons=1")); + assert_eq!(false, contains_only_supported_protocols("Cons=0")); + assert_eq!(false, contains_only_supported_protocols("Cons=0-1")); + assert_eq!(false, contains_only_supported_protocols("Cons=5")); + assert_eq!(false, contains_only_supported_protocols("Cons=1-5")); + assert_eq!(false, contains_only_supported_protocols("Cons=1,5")); + assert_eq!(false, contains_only_supported_protocols("Cons=5,6")); + assert_eq!(false, contains_only_supported_protocols("Cons=1,5,6")); + assert_eq!(true, contains_only_supported_protocols("Cons=1,2")); + assert_eq!(true, contains_only_supported_protocols("Cons=1-2")); + } + + #[test] + fn test_find_range() { + use super::find_range; + + assert_eq!((false, 0), find_range(&vec![])); + assert_eq!((false, 1), find_range(&vec![1])); + assert_eq!((true, 2), find_range(&vec![1, 2])); + assert_eq!((true, 3), find_range(&vec![1, 2, 3])); + assert_eq!((true, 3), find_range(&vec![1, 2, 3, 5])); + } + + #[test] + fn test_expand_version_range() { + use super::expand_version_range; + + assert_eq!(Err("version string empty"), expand_version_range("")); + assert_eq!(Ok(vec![1, 2]), expand_version_range("1-2")); + assert_eq!(Ok(vec![1, 2, 3, 4]), expand_version_range("1-4")); + assert_eq!( + Err("cannot parse protocol range lower bound"), + expand_version_range("a") + ); + assert_eq!( + Err("cannot parse protocol range upper bound"), + expand_version_range("1-a") + ); + } + + #[test] + fn test_contract_protocol_list() { + use std::collections::HashSet; + use super::contract_protocol_list; + + { + let mut versions = HashSet::<u32>::new(); + assert_eq!(String::from(""), contract_protocol_list(&versions)); + + versions.insert(1); + assert_eq!(String::from("1"), contract_protocol_list(&versions)); + + versions.insert(2); + assert_eq!(String::from("1-2"), contract_protocol_list(&versions)); + } + + { + let mut versions = HashSet::<u32>::new(); + versions.insert(1); + versions.insert(3); + assert_eq!(String::from("1,3"), contract_protocol_list(&versions)); + } + + { + let mut versions = HashSet::<u32>::new(); + versions.insert(1); + versions.insert(2); + versions.insert(3); + versions.insert(4); + assert_eq!(String::from("1-4"), contract_protocol_list(&versions)); + } + + { + let mut versions = HashSet::<u32>::new(); + versions.insert(1); + versions.insert(3); + versions.insert(5); + versions.insert(6); + versions.insert(7); + assert_eq!( + String::from("1,3,5-7"), + contract_protocol_list(&versions) + ); + } + + { + let mut versions = HashSet::<u32>::new(); + versions.insert(1); + versions.insert(2); + versions.insert(3); + versions.insert(500); + assert_eq!( + String::from("1-3,500"), + contract_protocol_list(&versions) + ); + } + } +} diff --git a/src/rust/protover/tests/protover.rs b/src/rust/protover/tests/protover.rs new file mode 100644 index 0000000000..f4e394b3e2 --- /dev/null +++ b/src/rust/protover/tests/protover.rs @@ -0,0 +1,291 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +extern crate protover; + +#[test] +fn parse_protocol_list_with_single_proto_and_single_version() { + let protocol = "Cons=1"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_single_protocol_and_multiple_versions() { + let protocol = "Cons=1-2"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_different_single_protocol_and_single_version() { + let protocol = "HSDir=1"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_single_protocol_and_supported_version() { + let protocol = "Desc=2"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_two_protocols_and_single_version() { + let protocols = "Cons=1 HSDir=1"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + + +#[test] +fn parse_protocol_list_with_single_protocol_and_two_nonsequential_versions() { + let protocol = "Desc=1,2"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + + +#[test] +fn parse_protocol_list_with_single_protocol_and_two_sequential_versions() { + let protocol = "Desc=1-2"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_single_protocol_and_protocol_range_returns_set() { + let protocol = "Link=1-4"; + let (is_supported, unsupported) = protover::all_supported(protocol); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn parse_protocol_list_with_single_protocol_and_protocol_set() { + let protocols = "Link=3-4 Desc=2"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn protover_all_supported_with_two_values() { + let protocols = "Microdesc=1-2 Relay=2"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!("", &unsupported); + assert_eq!(true, is_supported); +} + +#[test] +fn protover_all_supported_with_one_value() { + let protocols = "Microdesc=1-2"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!("", &unsupported); + assert_eq!(true, is_supported); +} + +#[test] +fn protover_all_supported_with_empty() { + let protocols = ""; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(true, is_supported); + assert_eq!("", &unsupported); +} + +#[test] +fn protover_all_supported_with_three_values() { + let protocols = "LinkAuth=1 Microdesc=1-2 Relay=2"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!("", &unsupported); + assert_eq!(true, is_supported); +} + +#[test] +fn protover_all_supported_with_unsupported_protocol() { + let protocols = "Wombat=9"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Wombat=9", &unsupported); +} + +#[test] +fn protover_all_supported_with_unsupported_versions() { + let protocols = "Link=3-999"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Link=3-999", &unsupported); +} + +#[test] +fn protover_all_supported_with_unsupported_low_version() { + let protocols = "Cons=0-1"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Cons=0-1", &unsupported); +} + +#[test] +fn protover_all_supported_with_unsupported_high_version() { + let protocols = "Cons=1-3"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Cons=1-3", &unsupported); +} + +#[test] +fn protover_all_supported_with_mix_of_supported_and_unsupproted() { + let protocols = "Link=3-4 Wombat=9"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Wombat=9", &unsupported); +} + +#[test] +fn protover_string_supports_protocol_returns_true_for_single_supported() { + let protocols = "Link=3-4 Cons=1"; + let is_supported = protover::protover_string_supports_protocol( + protocols, + protover::Proto::Cons, + 1, + ); + assert_eq!(true, is_supported); +} + +#[test] +fn protover_string_supports_protocol_returns_false_for_single_unsupported() { + let protocols = "Link=3-4 Cons=1"; + let is_supported = protover::protover_string_supports_protocol( + protocols, + protover::Proto::Cons, + 2, + ); + assert_eq!(false, is_supported); +} + +#[test] +fn protover_string_supports_protocol_returns_false_for_unsupported() { + let protocols = "Link=3-4"; + let is_supported = protover::protover_string_supports_protocol( + protocols, + protover::Proto::Cons, + 2, + ); + assert_eq!(false, is_supported); +} + +#[test] +fn protover_all_supported_with_unexpected_characters() { + let protocols = "Cons=*-%"; + let (is_supported, unsupported) = protover::all_supported(protocols); + assert_eq!(false, is_supported); + assert_eq!("Cons=*-%", &unsupported); +} + +#[test] +fn protover_compute_vote_returns_empty_for_empty_string() { + let protocols = vec![String::from("")]; + let listed = protover::compute_vote(protocols, 1); + assert_eq!("", listed); +} + +#[test] +fn protover_compute_vote_returns_single_protocol_for_matching() { + let protocols = vec![String::from("Cons=1")]; + let listed = protover::compute_vote(protocols, 1); + assert_eq!("Cons=1", listed); +} + +#[test] +fn protover_compute_vote_returns_two_protocols_for_two_matching() { + let protocols = vec![String::from("Link=1 Cons=1")]; + let listed = protover::compute_vote(protocols, 1); + assert_eq!("Cons=1 Link=1", listed); +} + +#[test] +fn protover_compute_vote_returns_one_protocol_when_one_out_of_two_matches() { + let protocols = vec![String::from("Cons=1 Link=2"), String::from("Cons=1")]; + let listed = protover::compute_vote(protocols, 2); + assert_eq!("Cons=1", listed); +} + +#[test] +fn protover_compute_vote_returns_protocols_that_it_doesnt_currently_support() { + let protocols = vec![String::from("Foo=1 Cons=2"), String::from("Bar=1")]; + let listed = protover::compute_vote(protocols, 1); + assert_eq!("Bar=1 Cons=2 Foo=1", listed); +} + +#[test] +fn protover_compute_vote_returns_matching_for_mix() { + let protocols = vec![String::from("Link=1-10,500 Cons=1,3-7,8")]; + let listed = protover::compute_vote(protocols, 1); + assert_eq!("Cons=1,3-8 Link=1-10,500", listed); +} + +#[test] +fn protover_compute_vote_returns_matching_for_longer_mix() { + let protocols = vec![ + String::from("Desc=1-10,500 Cons=1,3-7,8"), + String::from("Link=123-456,78 Cons=2-6,8 Desc=9"), + ]; + + let listed = protover::compute_vote(protocols, 1); + assert_eq!("Cons=1-8 Desc=1-10,500 Link=78,123-456", listed); +} + +#[test] +fn protover_compute_vote_returns_matching_for_longer_mix_with_threshold_two() { + let protocols = vec![ + String::from("Desc=1-10,500 Cons=1,3-7,8"), + String::from("Link=123-456,78 Cons=2-6,8 Desc=9"), + ]; + + let listed = protover::compute_vote(protocols, 2); + assert_eq!("Cons=3-6,8 Desc=9", listed); +} + +#[test] +fn protover_compute_vote_handles_duplicated_versions() { + let protocols = vec![String::from("Cons=1"), String::from("Cons=1")]; + assert_eq!("Cons=1", protover::compute_vote(protocols, 2)); + + let protocols = vec![String::from("Cons=1-2"), String::from("Cons=1-2")]; + assert_eq!("Cons=1-2", protover::compute_vote(protocols, 2)); +} + +#[test] +fn protover_compute_vote_handles_invalid_proto_entries() { + let protocols = vec![ + String::from("Cons=1"), + String::from("Cons=1"), + String::from("Link=a"), + ]; + assert_eq!("Cons=1", protover::compute_vote(protocols, 2)); + + let protocols = vec![ + String::from("Cons=1"), + String::from("Cons=1"), + String::from("Link=1-%"), + ]; + assert_eq!("Cons=1", protover::compute_vote(protocols, 2)); +} + +#[test] +fn protover_is_supported_here_returns_true_for_supported_protocol() { + assert_eq!(true, protover::is_supported_here(protover::Proto::Cons, 1)); +} + +#[test] +fn protover_is_supported_here_returns_false_for_unsupported_protocol() { + assert_eq!(false, protover::is_supported_here(protover::Proto::Cons, 5)); +} diff --git a/src/rust/smartlist/Cargo.toml b/src/rust/smartlist/Cargo.toml new file mode 100644 index 0000000000..51f486c4d7 --- /dev/null +++ b/src/rust/smartlist/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["The Tor Project"] +version = "0.0.1" +name = "smartlist" + +[dependencies] +libc = "0.2.22" + +[lib] +name = "smartlist" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + diff --git a/src/rust/smartlist/lib.rs b/src/rust/smartlist/lib.rs new file mode 100644 index 0000000000..14a8148315 --- /dev/null +++ b/src/rust/smartlist/lib.rs @@ -0,0 +1,8 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +extern crate libc; + +mod smartlist; + +pub use smartlist::*; diff --git a/src/rust/smartlist/smartlist.rs b/src/rust/smartlist/smartlist.rs new file mode 100644 index 0000000000..ec5d7a57f5 --- /dev/null +++ b/src/rust/smartlist/smartlist.rs @@ -0,0 +1,115 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +use std::slice; +use libc::{c_char, c_int}; +use std::ffi::CStr; + +/// Smartlists are a type used in C code in tor to define a collection of a +/// generic type, which has a capacity and a number used. Each Smartlist +/// defines how to extract the list of values from the underlying C structure +/// +/// Implementations are required to have a C representation, as this module +/// serves purely to translate smartlists as defined in tor to vectors in Rust. +pub trait Smartlist<T> { + fn get_list(&self) -> Vec<T>; +} + +#[repr(C)] +pub struct Stringlist { + pub list: *const *const c_char, + pub num_used: c_int, + pub capacity: c_int, +} + +impl Smartlist<String> for Stringlist { + fn get_list(&self) -> Vec<String> { + let empty: Vec<String> = Vec::new(); + let mut rust_list: Vec<String> = Vec::new(); + + if self.list.is_null() || self.num_used == 0 { + return empty; + } + + // unsafe, as we need to extract the smartlist list into a vector of + // pointers, and then transform each element into a Rust string. + let elems: &[*const i8] = + unsafe { slice::from_raw_parts(self.list, self.num_used as usize) }; + + for elem in elems.iter() { + if elem.is_null() { + continue; + } + + // unsafe, as we need to create a cstring from the referenced + // element + let c_string = unsafe { CStr::from_ptr(*elem) }; + + let r_string = match c_string.to_str() { + Ok(n) => n, + Err(_) => return empty, + }; + + rust_list.push(String::from(r_string)); + } + + rust_list + } +} + +// TODO: CHK: this module maybe should be tested from a test in C with a +// smartlist as defined in tor. +#[cfg(test)] +mod test { + #[test] + fn test_get_list_of_strings() { + extern crate libc; + + use std::ffi::CString; + use libc::c_char; + + use super::Smartlist; + use super::Stringlist; + + { + // test to verify that null pointers are gracefully handled + use std::ptr; + + let sl = Stringlist { + list: ptr::null(), + num_used: 0, + capacity: 0, + }; + + let data = sl.get_list(); + assert_eq!(0, data.len()); + } + + { + let args = vec![String::from("a"), String::from("b")]; + + // for each string, transform it into a CString + let c_strings: Vec<_> = args.iter() + .map(|arg| CString::new(arg.as_str()).unwrap()) + .collect(); + + // then, collect a pointer for each CString + let p_args: Vec<_> = + c_strings.iter().map(|arg| arg.as_ptr()).collect(); + + let p: *const *const c_char = p_args.as_ptr(); + + // This is the representation that we expect when receiving a + // smartlist at the Rust/C FFI layer. + let sl = Stringlist { + list: p, + num_used: 2, + capacity: 2, + }; + + let data = sl.get_list(); + assert_eq!("a", &data[0]); + assert_eq!("b", &data[1]); + } + } +} diff --git a/src/rust/tor_allocate/Cargo.toml b/src/rust/tor_allocate/Cargo.toml new file mode 100644 index 0000000000..ceb08b78ab --- /dev/null +++ b/src/rust/tor_allocate/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["The Tor Project"] +version = "0.0.1" +name = "tor_allocate" + +[dependencies] +libc = "0.2.22" + +[lib] +name = "tor_allocate" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + diff --git a/src/rust/tor_allocate/lib.rs b/src/rust/tor_allocate/lib.rs new file mode 100644 index 0000000000..937a5dcf63 --- /dev/null +++ b/src/rust/tor_allocate/lib.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +//! Allocation helper functions that allow data to be allocated in Rust +//! using tor's specified allocator. In doing so, this can be later freed +//! from C. +//! +//! This is currently a temporary solution, we will later use tor's allocator +//! by default for any allocation that occurs in Rust. However, as this will +//! stabalize in 2018, we can use this as a temporary measure. + +extern crate libc; + +mod tor_allocate; +pub use tor_allocate::*; diff --git a/src/rust/tor_allocate/tor_allocate.rs b/src/rust/tor_allocate/tor_allocate.rs new file mode 100644 index 0000000000..359df1cd7a --- /dev/null +++ b/src/rust/tor_allocate/tor_allocate.rs @@ -0,0 +1,97 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +use libc::{c_char, c_void}; +use std::{ptr, slice, mem}; + +#[cfg(not(test))] +extern "C" { + fn tor_malloc_(size: usize) -> *mut c_void; +} + +// Defined only for tests, used for testing purposes, so that we don't need +// to link to tor C files. Uses the system allocator +#[cfg(test)] +unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void { + use libc::malloc; + malloc(size) +} + +/// Allocate memory using tor_malloc_ and copy an existing string into the +/// allocated buffer, returning a pointer that can later be called in C. +/// +/// # Inputs +/// +/// * `src`, a reference to a String. +/// +/// # Returns +/// +/// A `*mut c_char` that should be freed by tor_free in C +/// +pub fn allocate_and_copy_string(src: &String) -> *mut c_char { + let bytes: &[u8] = src.as_bytes(); + + let size = mem::size_of_val::<[u8]>(bytes); + let size_one_byte = mem::size_of::<u8>(); + + // handle integer overflow when adding one to the calculated length + let size_with_null_byte = match size.checked_add(size_one_byte) { + Some(n) => n, + None => return ptr::null_mut(), + }; + + let dest = unsafe { tor_malloc_(size_with_null_byte) as *mut u8 }; + + if dest.is_null() { + return ptr::null_mut(); + } + + unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dest, size) }; + + // set the last byte as null, using the ability to index into a slice + // rather than doing pointer arithmatic + let slice = unsafe { slice::from_raw_parts_mut(dest, size_with_null_byte) }; + slice[size] = 0; // add a null terminator + + dest as *mut c_char +} + +#[cfg(test)] +mod test { + + #[test] + fn test_allocate_and_copy_string_with_empty() { + use std::ffi::CStr; + use libc::{free, c_void}; + + use tor_allocate::allocate_and_copy_string; + + let empty = String::new(); + let allocated_empty = allocate_and_copy_string(&empty); + + let allocated_empty_rust = + unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() }; + + assert_eq!("", allocated_empty_rust); + + unsafe { free(allocated_empty as *mut c_void) }; + } + + #[test] + fn test_allocate_and_copy_string_with_not_empty_string() { + use std::ffi::CStr; + use libc::{free, c_void}; + + use tor_allocate::allocate_and_copy_string; + + let empty = String::from("foo bar biz"); + let allocated_empty = allocate_and_copy_string(&empty); + + let allocated_empty_rust = + unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() }; + + assert_eq!("foo bar biz", allocated_empty_rust); + + unsafe { free(allocated_empty as *mut c_void) }; + } +} diff --git a/src/rust/tor_rust/Cargo.toml b/src/rust/tor_rust/Cargo.toml new file mode 100644 index 0000000000..86fad3ee76 --- /dev/null +++ b/src/rust/tor_rust/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["The Tor Project"] +name = "tor_rust" +version = "0.1.0" + +[lib] +name = "tor_rust" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + +[dependencies.tor_util] +path = "../tor_util" + +[dependencies.protover] +path = "../protover" + diff --git a/src/rust/tor_rust/include.am b/src/rust/tor_rust/include.am new file mode 100644 index 0000000000..90f37a9f1b --- /dev/null +++ b/src/rust/tor_rust/include.am @@ -0,0 +1,24 @@ +EXTRA_DIST +=\ + src/rust/tor_rust/Cargo.toml \ + src/rust/tor_rust/lib.rs + +src/rust/target/release/@TOR_RUST_STATIC_NAME@: FORCE + ( cd "$(abs_top_srcdir)/src/rust/tor_rust" ; \ + CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ + CARGO_HOME="$(abs_top_builddir)/src/rust" \ + $(CARGO) build --release --quiet $(CARGO_ONLINE) ) + +distclean-rust: + ( cd "$(abs_top_srcdir)/src/rust/tor_rust" ; \ + CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ + CARGO_HOME="$(abs_top_builddir)/src/rust" \ + $(CARGO) clean --quiet $(CARGO_ONLINE) ) + rm -rf "$(abs_top_builddir)/src/rust/registry" + +if USE_RUST +build-rust: src/rust/target/release/@TOR_RUST_STATIC_NAME@ +else +build-rust: +endif + +FORCE: diff --git a/src/rust/tor_rust/lib.rs b/src/rust/tor_rust/lib.rs new file mode 100644 index 0000000000..c1585c0480 --- /dev/null +++ b/src/rust/tor_rust/lib.rs @@ -0,0 +1,5 @@ +extern crate tor_util; +extern crate protover; + +pub use tor_util::*; +pub use protover::*; diff --git a/src/rust/tor_util/Cargo.toml b/src/rust/tor_util/Cargo.toml index f175fbdfb0..d7379a5988 100644 --- a/src/rust/tor_util/Cargo.toml +++ b/src/rust/tor_util/Cargo.toml @@ -8,6 +8,9 @@ name = "tor_util" path = "lib.rs" crate_type = ["rlib", "staticlib"] +[dependencies.tor_allocate] +path = "../tor_allocate" + [dependencies] -libc = "*" +libc = "0.2.22" diff --git a/src/rust/tor_util/ffi.rs b/src/rust/tor_util/ffi.rs index af4bfc41af..5c3cdba4be 100644 --- a/src/rust/tor_util/ffi.rs +++ b/src/rust/tor_util/ffi.rs @@ -1,56 +1,26 @@ -//! FFI functions, only to be called from C. -//! -//! Equivalent C versions of these live in `src/common/compat_rust.c` - -use std::mem::forget; -use std::ffi::CString; +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ -use libc; -use rust_string::RustString; - -/// Free the passed `RustString` (`rust_str_t` in C), to be used in place of -/// `tor_free`(). -/// -/// # Examples -/// ```c -/// rust_str_t r_s = rust_welcome_string(); -/// rust_str_free(r_s); -/// ``` -#[no_mangle] -#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] -pub unsafe extern "C" fn rust_str_free(_str: RustString) { - // Empty body: Just drop _str and we're done (Drop takes care of it). -} +//! FFI functions to announce Rust support during tor startup, only to be +//! called from C. +//! -/// Lends an immutable, NUL-terminated C String. -/// -/// # Examples -/// ```c -/// rust_str_t r_s = rust_welcome_string(); -/// const char *s = rust_str_get(r_s); -/// printf("%s", s); -/// rust_str_free(r_s); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn rust_str_get(str: RustString) -> *const libc::c_char { - let res = str.as_ptr(); - forget(str); - res -} +use libc::c_char; +use tor_allocate::allocate_and_copy_string; /// Returns a short string to announce Rust support during startup. /// /// # Examples /// ```c -/// rust_str_t r_s = rust_welcome_string(); -/// const char *s = rust_str_get(r_s); -/// printf("%s", s); -/// rust_str_free(r_s); +/// char *rust_str = rust_welcome_string(); +/// printf("%s", rust_str); +/// tor_free(rust_str); /// ``` #[no_mangle] -pub extern "C" fn rust_welcome_string() -> RustString { - let s = CString::new("Tor is running with Rust integration. Please report \ - any bugs you encouter.") - .unwrap(); - RustString::from(s) +pub extern "C" fn rust_welcome_string() -> *mut c_char { + let rust_welcome = String::from( + "Tor is running with Rust integration. Please report \ + any bugs you encounter.", + ); + allocate_and_copy_string(&rust_welcome) } diff --git a/src/rust/tor_util/include.am b/src/rust/tor_util/include.am deleted file mode 100644 index ec3898577b..0000000000 --- a/src/rust/tor_util/include.am +++ /dev/null @@ -1,13 +0,0 @@ -EXTRA_DIST +=\ - src/rust/tor_util/Cargo.toml \ - src/rust/tor_util/lib.rs \ - src/rust/tor_util/ffi.rs \ - src/rust/tor_util/rust_string.rs - -src/rust/target/release/@TOR_RUST_UTIL_STATIC_NAME@: FORCE - ( cd "$(abs_top_srcdir)/src/rust/tor_util" ; \ - CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ - CARGO_HOME="$(abs_top_builddir)/src/rust" \ - $(CARGO) build --release --quiet $(CARGO_ONLINE) ) - -FORCE: diff --git a/src/rust/tor_util/lib.rs b/src/rust/tor_util/lib.rs index 79d583d1ae..42fa9d5ad0 100644 --- a/src/rust/tor_util/lib.rs +++ b/src/rust/tor_util/lib.rs @@ -1,13 +1,11 @@ -//! C <-> Rust compatibility helpers and types. +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +//! Small module to announce Rust support during startup for demonstration +//! purposes. //! -//! Generically useful, small scale helpers should go here. This goes for both -//! the C side (in the form of the ffi module) as well as the Rust side -//! (individual modules per functionality). The corresponding C stuff lives in -//! `src/common/compat_rust.{c,h}`. extern crate libc; +extern crate tor_allocate; -mod rust_string; pub mod ffi; - -pub use rust_string::*; diff --git a/src/rust/tor_util/rust_string.rs b/src/rust/tor_util/rust_string.rs deleted file mode 100644 index 46ec3fd7a8..0000000000 --- a/src/rust/tor_util/rust_string.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::ffi::CString; -use std::mem::forget; -use libc; - -/// Compatibility wrapper for strings allocated in Rust and passed to C. -/// -/// Rust doesn't ensure the safety of freeing memory across an FFI boundary, so -/// we need to take special care to ensure we're not accidentally calling -/// `tor_free`() on any string allocated in Rust. To more easily differentiate -/// between strings that possibly (if Rust support is enabled) were allocated -/// in Rust, C has the `rust_str_t` helper type. The equivalent on the Rust -/// side is `RustString`. -/// -/// Note: This type must not be used for strings allocated in C. -#[repr(C)] -#[derive(Debug)] -pub struct RustString(*mut libc::c_char); - -impl RustString { - /// Returns a pointer to the underlying NUL-terminated byte array. - /// - /// Note that this function is not typically useful for Rust callers, - /// except in a direct FFI context. - /// - /// # Examples - /// ``` - /// # use tor_util::RustString; - /// use std::ffi::CString; - /// - /// let r = RustString::from(CString::new("asdf").unwrap()); - /// let c_str = r.as_ptr(); - /// assert_eq!(b'a', unsafe { *c_str as u8}); - /// ``` - pub fn as_ptr(&self) -> *const libc::c_char { - self.0 as *const libc::c_char - } -} - -impl From<CString> for RustString { - /// Constructs a new `RustString` - /// - /// # Examples - /// ``` - /// # use tor_util::RustString; - /// use std::ffi::CString; - /// - /// let r = RustString::from(CString::new("asdf").unwrap()); - /// ``` - fn from(str: CString) -> RustString { - RustString(str.into_raw()) - } -} - -impl Into<CString> for RustString { - /// Reconstructs a `CString` from this `RustString`. - /// - /// Useful to take ownership back from a `RustString` that was given to C - /// code. - /// - /// # Examples - /// ``` - /// # use tor_util::RustString; - /// use std::ffi::CString; - /// - /// let cs = CString::new("asdf").unwrap(); - /// let r = RustString::from(cs.clone()); - /// let cs2 = r.into(); - /// assert_eq!(cs, cs2); - /// ``` - fn into(self) -> CString { - // Calling from_raw is always OK here: We only construct self using - // valid CStrings and don't expose anything that could mutate it - let ret = unsafe { CString::from_raw(self.0) }; - forget(self); - ret - } -} - -impl Drop for RustString { - fn drop(&mut self) { - // Don't use into() here, because we would need to move out of - // self. Same safety consideration. Immediately drop the created - // CString, which takes care of freeing the wrapped string. - unsafe { CString::from_raw(self.0) }; - } -} - -#[cfg(test)] -mod test { - use std::mem; - use super::*; - - use libc; - - /// Ensures we're not adding overhead by using RustString. - #[test] - fn size_of() { - assert_eq!(mem::size_of::<*mut libc::c_char>(), - mem::size_of::<RustString>()) - } -} diff --git a/src/rust/tor_util/tests/rust_string.rs b/src/rust/tor_util/tests/rust_string.rs deleted file mode 100644 index 1ff605a43c..0000000000 --- a/src/rust/tor_util/tests/rust_string.rs +++ /dev/null @@ -1,37 +0,0 @@ -extern crate tor_util; -extern crate libc; - -use std::ffi::CString; -use tor_util::RustString; - -#[test] -fn rust_string_conversions_preserve_c_string() { - let s = CString::new("asdf foo").unwrap(); - let r = RustString::from(s.clone()); - let r2 = RustString::from(s.clone()); - let c = r2.as_ptr(); - assert_eq!(unsafe { libc::strlen(c) }, 8); - let c_str = r.into(); - assert_eq!(s, c_str); -} - -#[test] -fn empty_string() { - let s = CString::new("").unwrap(); - let r = RustString::from(s.clone()); - let c = r.as_ptr(); - assert_eq!(unsafe { libc::strlen(c) }, 0); - let c_str = r.into(); - assert_eq!(s, c_str); -} - -#[test] -fn c_string_with_unicode() { - // The euro sign is three bytes - let s = CString::new("asd€asd").unwrap(); - let r = RustString::from(s.clone()); - let c = r.as_ptr(); - assert_eq!(unsafe { libc::strlen(c) }, 9); - let c_str = r.into(); - assert_eq!(s, c_str); -} diff --git a/src/test/bench.c b/src/test/bench.c index b7b123eee2..f30b609900 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -3,11 +3,6 @@ * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -extern const char tor_git_revision[]; -/* Ordinarily defined in tor_main.c; this bit is just here to provide one - * since we're not linking to tor_main.c */ -const char tor_git_revision[] = ""; - /** * \file bench.c * \brief Benchmarks for lower level Tor modules. diff --git a/src/test/fuzz/fuzzing_common.c b/src/test/fuzz/fuzzing_common.c index 7ebddde1a8..d5c71859b5 100644 --- a/src/test/fuzz/fuzzing_common.c +++ b/src/test/fuzz/fuzzing_common.c @@ -9,9 +9,6 @@ #include "crypto.h" #include "crypto_ed25519.h" -extern const char tor_git_revision[]; -const char tor_git_revision[] = ""; - static or_options_t *mock_options = NULL; static const or_options_t * mock_get_options(void) diff --git a/src/test/include.am b/src/test/include.am index 610918e9da..bbf8a370e2 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -152,7 +152,6 @@ src_test_test_SOURCES = \ src/test/test_routerkeys.c \ src/test/test_routerlist.c \ src/test/test_routerset.c \ - src/test/test_rust.c \ src/test/test_scheduler.c \ src/test/test_shared_random.c \ src/test/test_socks.c \ diff --git a/src/test/test-memwipe.c b/src/test/test-memwipe.c index 484f13dd05..89d946d506 100644 --- a/src/test/test-memwipe.c +++ b/src/test/test-memwipe.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #include "orconfig.h" #include <string.h> #include <stdio.h> diff --git a/src/test/test.c b/src/test/test.c index da4f7a0553..00857c2386 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1227,7 +1227,6 @@ struct testgroup_t testgroups[] = { { "routerkeys/", routerkeys_tests }, { "routerlist/", routerlist_tests }, { "routerset/" , routerset_tests }, - { "rust/", rust_tests }, { "scheduler/", scheduler_tests }, { "socks/", socks_tests }, { "shared-random/", sr_tests }, diff --git a/src/test/test.h b/src/test/test.h index 634f3e7b02..b1a3366a80 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -239,7 +239,6 @@ extern struct testcase_t router_tests[]; extern struct testcase_t routerkeys_tests[]; extern struct testcase_t routerlist_tests[]; extern struct testcase_t routerset_tests[]; -extern struct testcase_t rust_tests[]; extern struct testcase_t scheduler_tests[]; extern struct testcase_t storagedir_tests[]; extern struct testcase_t socks_tests[]; diff --git a/src/test/test_accounting.c b/src/test/test_accounting.c index 7edba988a6..b0d37b2989 100644 --- a/src/test/test_accounting.c +++ b/src/test/test_accounting.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #include "or.h" #include "test.h" #define HIBERNATE_PRIVATE diff --git a/src/test/test_channelpadding.c b/src/test/test_channelpadding.c index d5713688a0..4346ee343f 100644 --- a/src/test/test_channelpadding.c +++ b/src/test/test_channelpadding.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #define TOR_CHANNEL_INTERNAL_ #define MAIN_PRIVATE #define NETWORKSTATUS_PRIVATE diff --git a/src/test/test_config.c b/src/test/test_config.c index 456d8bcc3e..79f72fca1f 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4833,7 +4833,7 @@ test_config_include_limit(void *data) torrc_path); tt_int_op(write_str_to_file(torrc_path, torrc_contents, 0), OP_EQ, 0); - tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL, NULL), OP_EQ, -1); done: @@ -4863,7 +4863,7 @@ test_config_include_does_not_exist(void *data) tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", missing_path); - tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL, NULL), OP_EQ, -1); done: @@ -4895,7 +4895,7 @@ test_config_include_error_in_included_file(void *data) tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", invalid_path); - tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL), + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL, NULL), OP_EQ, -1); done: @@ -4937,8 +4937,8 @@ test_config_include_empty_file_folder(void *data) folder_path, file_path); int include_used; - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used, + NULL), OP_EQ, 0); tt_ptr_op(result, OP_EQ, NULL); tt_int_op(include_used, OP_EQ, 1); @@ -4975,7 +4975,8 @@ test_config_include_no_permission(void *data) folder_path); int include_used; - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, + &include_used, NULL), OP_EQ, -1); tt_ptr_op(result, OP_EQ, NULL); @@ -5031,8 +5032,8 @@ test_config_include_recursion_before_after(void *data) } int include_used; - tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used, + NULL), OP_EQ, 0); tt_ptr_op(result, OP_NE, NULL); tt_int_op(include_used, OP_EQ, 1); @@ -5096,8 +5097,8 @@ test_config_include_recursion_after_only(void *data) } int include_used; - tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used, + NULL), OP_EQ, 0); tt_ptr_op(result, OP_NE, NULL); tt_int_op(include_used, OP_EQ, 1); @@ -5185,8 +5186,8 @@ test_config_include_folder_order(void *data) torrcd); int include_used; - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, &include_used, + NULL), OP_EQ, 0); tt_ptr_op(result, OP_NE, NULL); tt_int_op(include_used, OP_EQ, 1); @@ -5239,8 +5240,8 @@ test_config_include_path_syntax(void *data) esc_dir_with_pathsep); int include_used; - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used, + NULL), OP_EQ, 0); tt_ptr_op(result, OP_EQ, NULL); tt_int_op(include_used, OP_EQ, 1); @@ -5294,14 +5295,14 @@ test_config_include_has_include(void *data) char torrc_contents[1000] = "Test 1\n"; int include_used; - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used, + NULL), OP_EQ, 0); tt_int_op(include_used, OP_EQ, 0); config_free_lines(result); tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s\n", dir); - tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), - OP_EQ, 0); + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used, + NULL), OP_EQ, 0); tt_int_op(include_used, OP_EQ, 1); done: @@ -5513,6 +5514,85 @@ test_config_check_bridge_distribution_setting_unrecognised(void *arg) return; } +static void +test_config_include_opened_file_list(void *data) +{ + (void)data; + + config_line_t *result = NULL; + smartlist_t *opened_files = smartlist_new(); + char *dir = tor_strdup(get_fname("test_include_opened_file_list")); + tt_ptr_op(dir, OP_NE, NULL); + +#ifdef _WIN32 + tt_int_op(mkdir(dir), OP_EQ, 0); +#else + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); +#endif + + char torrcd[PATH_MAX+1]; + tor_snprintf(torrcd, sizeof(torrcd), "%s"PATH_SEPARATOR"%s", dir, "torrc.d"); + +#ifdef _WIN32 + tt_int_op(mkdir(torrcd), OP_EQ, 0); +#else + tt_int_op(mkdir(torrcd, 0700), OP_EQ, 0); +#endif + + char subfolder[PATH_MAX+1]; + tor_snprintf(subfolder, sizeof(subfolder), "%s"PATH_SEPARATOR"%s", torrcd, + "subfolder"); + +#ifdef _WIN32 + tt_int_op(mkdir(subfolder), OP_EQ, 0); +#else + tt_int_op(mkdir(subfolder, 0700), OP_EQ, 0); +#endif + + char path[PATH_MAX+1]; + tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", subfolder, + "01_file_in_subfolder"); + tt_int_op(write_str_to_file(path, "Test 1\n", 0), OP_EQ, 0); + + char empty[PATH_MAX+1]; + tor_snprintf(empty, sizeof(empty), "%s"PATH_SEPARATOR"%s", torrcd, "empty"); + tt_int_op(write_str_to_file(empty, "", 0), OP_EQ, 0); + + char file[PATH_MAX+1]; + tor_snprintf(file, sizeof(file), "%s"PATH_SEPARATOR"%s", torrcd, "file"); + tt_int_op(write_str_to_file(file, "Test 2\n", 0), OP_EQ, 0); + + char dot[PATH_MAX+1]; + tor_snprintf(dot, sizeof(dot), "%s"PATH_SEPARATOR"%s", torrcd, ".dot"); + tt_int_op(write_str_to_file(dot, "Test 3\n", 0), OP_EQ, 0); + + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), + "%%include %s\n", + torrcd); + + int include_used; + tt_int_op(config_get_lines_include(torrc_contents, &result, 0, &include_used, + opened_files), OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + tt_int_op(include_used, OP_EQ, 1); + + tt_int_op(smartlist_len(opened_files), OP_EQ, 4); + tt_int_op(smartlist_contains_string(opened_files, torrcd), OP_EQ, 1); + tt_int_op(smartlist_contains_string(opened_files, subfolder), OP_EQ, 1); + // files inside subfolders are not opended, only the subfolder is opened + tt_int_op(smartlist_contains_string(opened_files, empty), OP_EQ, 1); + tt_int_op(smartlist_contains_string(opened_files, file), OP_EQ, 1); + // dot files are not opened as we ignore them when we get their name from + // their parent folder + + done: + SMARTLIST_FOREACH(opened_files, char *, f, tor_free(f)); + smartlist_free(opened_files); + config_free_lines(result); + tor_free(dir); +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } @@ -5560,6 +5640,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(check_bridge_distribution_setting_valid, 0), CONFIG_TEST(check_bridge_distribution_setting_invalid, 0), CONFIG_TEST(check_bridge_distribution_setting_unrecognised, 0), + CONFIG_TEST(include_opened_file_list, 0), END_OF_TESTCASES }; diff --git a/src/test/test_dns.c b/src/test/test_dns.c index 19dcb02931..66b231b6da 100644 --- a/src/test/test_dns.c +++ b/src/test/test_dns.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2015-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #include "or.h" #include "test.h" diff --git a/src/test/test_keygen.sh b/src/test/test_keygen.sh index 87012cd283..b3d4d8e39a 100755 --- a/src/test/test_keygen.sh +++ b/src/test/test_keygen.sh @@ -40,6 +40,12 @@ fi CASE8=$dflt CASE9=$dflt CASE10=$dflt + CASE11A=$dflt + CASE11B=$dflt + CASE11C=$dflt + CASE11D=$dflt + CASE11E=$dflt + CASE11F=$dflt if [ $# -ge 1 ]; then eval "CASE${1}"=1 @@ -363,6 +369,109 @@ echo "==== Case 10 ok" fi +# Case 11a: -passphrase-fd without --keygen + +if [ "$CASE11A" = 1 ]; then + +ME="${DATA_DIR}/case11a" + +mkdir -p "${ME}/keys" + +${TOR} --DataDirectory "${ME}" --passphrase-fd 1 > "${ME}/stdout" && die "Successfully started with passphrase-fd but no keygen?" || true + +grep "passphrase-fd specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + +echo "==== Case 11A ok" + +fi + +# Case 11b: --no-passphrase without --keygen + +if [ "$CASE11B" = 1 ]; then + +ME="${DATA_DIR}/case11b" + +mkdir -p "${ME}/keys" + +${TOR} --DataDirectory "${ME}" --no-passphrase > "${ME}/stdout" && die "Successfully started with no-passphrase but no keygen?" || true + +grep "no-passphrase specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + +echo "==== Case 11B ok" + +fi + +# Case 11c: --newpass without --keygen + +if [ "$CASE11C" = 1 ]; then + +ME="${DATA_DIR}/case11C" + +mkdir -p "${ME}/keys" + +${TOR} --DataDirectory "${ME}" --newpass > "${ME}/stdout" && die "Successfully started with newpass but no keygen?" || true + +grep "newpass specified without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + +echo "==== Case 11C ok" + +fi + +######## --master-key does not work yet, but this will test the error case +######## when it does. +# +# Case 11d: --master-key without --keygen +# +if [ "$CASE11D" = 1 ]; then +# +# ME="${DATA_DIR}/case11d" +# +# mkdir -p "${ME}/keys" +# +# ${TOR} --DataDirectory "${ME}" --master-key "${ME}/foobar" > "${ME}/stdout" && die "Successfully started with master-key but no keygen?" || true +# +# cat "${ME}/stdout" +# +# grep "master-key without --keygen" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + + echo "==== Case 11D skipped" + +fi + + +# Case 11E: Silly passphrase-fd + +if [ "$CASE11E" = 1 ]; then + +ME="${DATA_DIR}/case11E" + +mkdir -p "${ME}/keys" + +${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd ewigeblumenkraft > "${ME}/stdout" && die "Successfully started with bogus passphrase-fd?" || true + +grep "Invalid --passphrase-fd value" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + +echo "==== Case 11E ok" + +fi + + +# Case 11F: --no-passphrase with --passphrase-fd + +if [ "$CASE11F" = 1 ]; then + +ME="${DATA_DIR}/case11F" + +mkdir -p "${ME}/keys" + +${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd 1 --no-passphrase > "${ME}/stdout" && die "Successfully started with bogus passphrase-fd combination?" || true + +grep "no-passphrase specified with --passphrase-fd" "${ME}/stdout" >/dev/null || die "Tor didn't declare that there was a problem with the arguments." + +echo "==== Case 11F ok" + +fi + # Check cert-only. diff --git a/src/test/test_protover.c b/src/test/test_protover.c index 6ce54890d6..9ae907183b 100644 --- a/src/test/test_protover.c +++ b/src/test/test_protover.c @@ -12,6 +12,13 @@ static void test_protover_parse(void *arg) { (void) arg; +#ifdef HAVE_RUST + /** This test is disabled on rust builds, because it only exists to test + * internal C functions. */ + tt_skip(); + done: + ; +#else char *re_encoded = NULL; const char *orig = "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900"; @@ -78,12 +85,18 @@ test_protover_parse(void *arg) SMARTLIST_FOREACH(elts, proto_entry_t *, ent, proto_entry_free(ent)); smartlist_free(elts); tor_free(re_encoded); +#endif } static void test_protover_parse_fail(void *arg) { (void)arg; +#ifdef HAVE_RUST + /** This test is disabled on rust builds, because it only exists to test + * internal C functions. */ + tt_skip(); +#else smartlist_t *elts; /* random junk */ @@ -109,7 +122,7 @@ test_protover_parse_fail(void *arg) /* Broken range */ elts = parse_protocol_list("Link=1,9-8,3"); tt_ptr_op(elts, OP_EQ, NULL); - +#endif done: ; } @@ -182,6 +195,32 @@ test_protover_all_supported(void *arg) tor_free(msg); } +static void +test_protover_list_supports_protocol_returns_true(void *arg) +{ + (void)arg; + + const char *protocols = "Link=1"; + int is_supported = protocol_list_supports_protocol(protocols, PRT_LINK, 1); + tt_int_op(is_supported, OP_EQ, 1); + + done: + ; +} + +static void +test_protover_list_supports_protocol_for_unsupported_returns_false(void *arg) +{ + (void)arg; + + const char *protocols = "Link=1"; + int is_supported = protocol_list_supports_protocol(protocols, PRT_LINK, 10); + tt_int_op(is_supported, OP_EQ, 0); + + done: + ; +} + #define PV_TEST(name, flags) \ { #name, test_protover_ ##name, (flags), NULL, NULL } @@ -190,6 +229,8 @@ struct testcase_t protover_tests[] = { PV_TEST(parse_fail, 0), PV_TEST(vote, 0), PV_TEST(all_supported, 0), + PV_TEST(list_supports_protocol_for_unsupported_returns_false, 0), + PV_TEST(list_supports_protocol_returns_true, 0), END_OF_TESTCASES }; diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 3b0e943ce5..05d0820d83 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -6,13 +6,17 @@ #include <time.h> #define DIRVOTE_PRIVATE +#define ENTRYNODES_PRIVATE +#define DIRECTORY_PRIVATE #define NETWORKSTATUS_PRIVATE +#define CONNECTION_PRIVATE #define ROUTERLIST_PRIVATE #define TOR_UNIT_TESTING #include "or.h" #include "config.h" #include "connection.h" #include "container.h" +#include "control.h" #include "directory.h" #include "dirvote.h" #include "entrynodes.h" @@ -22,10 +26,13 @@ #include "policies.h" #include "router.h" #include "routerlist.h" +#include "routerset.h" #include "routerparse.h" #include "shared_random.h" +#include "statefile.h" #include "test.h" #include "test_dir_common.h" +#include "log_test_helpers.h" void construct_consensus(char **consensus_text_md); @@ -411,6 +418,109 @@ test_router_pick_directory_server_impl(void *arg) networkstatus_vote_free(con_md); } +static or_state_t *dummy_state = NULL; +static or_state_t * +get_or_state_replacement(void) +{ + return dummy_state; +} + +static void +mock_directory_initiate_request(directory_request_t *req) +{ + (void)req; + return; +} + +static circuit_guard_state_t * +mock_circuit_guard_state_new(entry_guard_t *guard, unsigned state, + entry_guard_restriction_t *rst) +{ + (void) guard; + (void) state; + (void) rst; + return NULL; +} + +/** Test that we will use our directory guards to fetch mds even if we don't + * have any dirinfo (tests bug #23862). */ +static void +test_directory_guard_fetch_with_no_dirinfo(void *arg) +{ + int retval; + char *consensus_text_md = NULL; + or_options_t *options = get_options_mutable(); + + (void) arg; + + /* Initialize the SRV subsystem */ + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + sr_init(0); + UNMOCK(get_my_v3_authority_cert); + + /* Initialize the entry node configuration from the ticket */ + options->UseEntryGuards = 1; + options->StrictNodes = 1; + get_options_mutable()->EntryNodes = routerset_new(); + routerset_parse(get_options_mutable()->EntryNodes, + "2121212121212121212121212121212121212121", "foo"); + + /* Mock some functions */ + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + MOCK(get_or_state, get_or_state_replacement); + MOCK(directory_initiate_request, mock_directory_initiate_request); + /* we need to mock this one to avoid memleaks */ + MOCK(circuit_guard_state_new, mock_circuit_guard_state_new); + + /* Call guards_update_all() to simulate loading our state file (see + * entry_guards_load_guards_from_state() and ticket #23989). */ + guards_update_all(); + + /* Test logic: Simulate the arrival of a new consensus when we have no + * dirinfo at all. Tor will need to fetch the mds from the consensus. Make + * sure that Tor will use the specified entry guard instead of relying on the + * fallback directories. */ + + /* Fixup the dirconn that will deliver the consensus */ + dir_connection_t *conn = dir_connection_new(AF_INET); + tor_addr_from_ipv4h(&conn->base_.addr, 0x7f000001); + conn->base_.port = 8800; + TO_CONN(conn)->address = tor_strdup("127.0.0.1"); + conn->base_.purpose = DIR_PURPOSE_FETCH_CONSENSUS; + conn->requested_resource = tor_strdup("ns"); + + /* Construct a consensus */ + construct_consensus(&consensus_text_md); + tt_assert(consensus_text_md); + + /* Place the consensus in the dirconn */ + response_handler_args_t args; + memset(&args, 0, sizeof(response_handler_args_t)); + args.status_code = 200; + args.body = consensus_text_md; + args.body_len = strlen(consensus_text_md); + + /* Update approx time so that the consensus is considered live */ + update_approx_time(time(NULL)+1010); + + setup_capture_of_logs(LOG_DEBUG); + + /* Now handle the consensus */ + retval = handle_response_fetch_consensus(conn, &args); + tt_int_op(retval, OP_EQ, 0); + + /* Make sure that our primary guard was chosen */ + expect_log_msg_containing("Selected primary guard router3"); + + done: + tor_free(consensus_text_md); + tor_free(dummy_state); + connection_free_(TO_CONN(conn)); + entry_guards_free_all(); + teardown_capture_of_logs(); +} + static connection_t *mocked_connection = NULL; /* Mock connection_get_by_type_addr_port_purpose by returning @@ -494,6 +604,8 @@ struct testcase_t routerlist_tests[] = { NODE(launch_descriptor_downloads, 0), NODE(router_is_already_dir_fetching, TT_FORK), ROUTER(pick_directory_server_impl, TT_FORK), + { "directory_guard_fetch_with_no_dirinfo", + test_directory_guard_fetch_with_no_dirinfo, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_routerset.c b/src/test/test_routerset.c index c9c69911da..6da2275c35 100644 --- a/src/test/test_routerset.c +++ b/src/test/test_routerset.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #define ROUTERSET_PRIVATE #include "or.h" diff --git a/src/test/test_rust.c b/src/test/test_rust.c deleted file mode 100644 index 6ad57d6fcb..0000000000 --- a/src/test/test_rust.c +++ /dev/null @@ -1,31 +0,0 @@ -/* Copyright (c) 2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -#include "orconfig.h" -#include "compat_rust.h" -#include "test.h" -#include "util.h" - -static void -test_welcome_string(void *arg) -{ - (void)arg; - rust_str_t s = rust_welcome_string(); - const char *c_str = rust_str_get(s); - tt_assert(c_str); - size_t len = strlen(c_str); -#ifdef HAVE_RUST - tt_assert(len > 0); -#else - tt_assert(len == 0); -#endif - - done: - rust_str_free(s); -} - -struct testcase_t rust_tests[] = { - { "welcome_string", test_welcome_string, 0, NULL, NULL }, - END_OF_TESTCASES -}; - diff --git a/src/test/test_rust.sh b/src/test/test_rust.sh index d559f94ce0..8d7900e1df 100755 --- a/src/test/test_rust.sh +++ b/src/test/test_rust.sh @@ -1,13 +1,16 @@ #!/bin/sh -# Test all the Rust crates we're using +# Test all Rust crates -crates=tor_util +crates="protover tor_util smartlist tor_allocate" exitcode=0 +set -e + for crate in $crates; do cd "${abs_top_srcdir:-.}/src/rust/${crate}" - CARGO_TARGET_DIR="${abs_top_builddir}/src/rust/target" CARGO_HOME="${abs_top_builddir}/src/rust" "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} || exitcode=1 + CARGO_TARGET_DIR="${abs_top_builddir:-../../..}/src/rust/target" CARGO_HOME="${abs_top_builddir:-../../..}/src/rust" "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} || exitcode=1 + cd - done exit $exitcode diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index a9d58e6b8b..80494f64c0 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #define SHARED_RANDOM_PRIVATE #define SHARED_RANDOM_STATE_PRIVATE #define CONFIG_PRIVATE diff --git a/src/test/test_status.c b/src/test/test_status.c index f86f8e3b9e..50ea203e4d 100644 --- a/src/test/test_status.c +++ b/src/test/test_status.c @@ -1,3 +1,6 @@ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + #define STATUS_PRIVATE #define HIBERNATE_PRIVATE #define LOG_PRIVATE diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 7e9c47b48d..e43fa46c84 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -3,12 +3,6 @@ * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -extern const char tor_git_revision[]; - -/* Ordinarily defined in tor_main.c; this bit is just here to provide one - * since we're not linking to tor_main.c */ -const char tor_git_revision[] = ""; - /** * \file test_common.c * \brief Common pieces to implement unit tests. diff --git a/src/tools/include.am b/src/tools/include.am index 37936c742f..92cc3f10a2 100644 --- a/src/tools/include.am +++ b/src/tools/include.am @@ -45,3 +45,8 @@ src_tools_tor_cov_gencert_LDADD = src/common/libor-testing.a \ endif EXTRA_DIST += src/tools/tor-fw-helper/README + +if BUILD_LIBTORRUNNER +noinst_LIBRARIES += src/tools/libtorrunner.a +src_tools_libtorrunner_a_SOURCES = src/tools/tor_runner.c src/or/tor_api.c +endif diff --git a/src/tools/tor_runner.c b/src/tools/tor_runner.c new file mode 100644 index 0000000000..9ed2ee5775 --- /dev/null +++ b/src/tools/tor_runner.c @@ -0,0 +1,101 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file tor_runner.c + * @brief Experimental module to emulate tor_run_main() API with fork+exec + * + * The functions here are meant to allow the application developer to + * use the tor_run_main() API without having to care whether Tor is + * running in-process or out-of-process. For in-process usage, the + * developer can link Tor as a library and call tor_run_main(); for + * out-of-process usage, the developer can link this library instead. + * + * This interface is EXPERIMENTAL; please let us know if you would like + * to depend on it. We don't know yet whether it will be reliable in + * practice. + */ + +/* NOTE: This module is supposed to work without the standard Tor utility + * functions. Don't add more dependencies! + */ + +#include "tor_api.h" +#include "tor_api_internal.h" + +#include "orconfig.h" +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <stdlib.h> +#include <string.h> + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +static void child(const tor_main_configuration_t *cfg) + __attribute__((noreturn)); + +int +tor_run_main(const tor_main_configuration_t *cfg) +{ + pid_t pid = fork(); + if (pid == 0) { + child(cfg); + exit(0); /* Unreachable */ + } + + pid_t stopped_pid; + int status = 0; + do { + stopped_pid = waitpid(pid, &status, 0); + } while (stopped_pid == -1); + + /* Note: these return values are not documented. No return value is + * documented! */ + + if (stopped_pid != pid) { + return -99999; + } + if (WIFSTOPPED(status)) { + return WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + return -WTERMSIG(status); + } + + return -999988; +} + +/* circumlocution to avoid getting warned about calling calloc instead of + * tor_calloc. */ +#define real_calloc calloc + +static void +child(const tor_main_configuration_t *cfg) +{ + /* XXXX Close unused file descriptors. */ + + char **args = real_calloc(cfg->argc+1, sizeof(char *)); + memcpy(args, cfg->argv, cfg->argc * sizeof(char *)); + args[cfg->argc] = NULL; + + int rv = execv(BINDIR "/tor", args); + + if (rv < 0) { + exit(254); + } else { + abort(); /* Unreachable */ + } +} + diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index 38c6d5118e..5e3c5d87fe 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -218,7 +218,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.3.2.3-alpha-dev" +#define VERSION "0.3.3.0-alpha-dev" |