diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/Makefile.am | 44 | ||||
-rw-r--r-- | src/common/address.c | 15 | ||||
-rw-r--r-- | src/common/address.h | 3 | ||||
-rw-r--r-- | src/common/aes.c | 19 | ||||
-rw-r--r-- | src/common/compat.c | 109 | ||||
-rw-r--r-- | src/common/compat.h | 7 | ||||
-rw-r--r-- | src/common/compat_libevent.c | 54 | ||||
-rw-r--r-- | src/common/compat_libevent.h | 15 | ||||
-rw-r--r-- | src/common/container.c | 1 | ||||
-rw-r--r-- | src/common/crypto.c | 6 | ||||
-rw-r--r-- | src/common/crypto.h | 3 | ||||
-rw-r--r-- | src/common/log.c | 29 | ||||
-rw-r--r-- | src/common/mempool.c | 2 | ||||
-rw-r--r-- | src/common/torlog.h | 5 | ||||
-rw-r--r-- | src/common/tortls.c | 355 | ||||
-rw-r--r-- | src/common/tortls.h | 15 | ||||
-rw-r--r-- | src/common/util.c | 479 | ||||
-rw-r--r-- | src/common/util.h | 17 |
18 files changed, 1031 insertions, 147 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 6952591d67..48218491b5 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -11,12 +11,48 @@ else libor_extra_source= endif -libor_a_SOURCES = address.c log.c util.c compat.c container.c mempool.c \ - memarea.c di_ops.c procmon.c util_codedigest.c $(libor_extra_source) -libor_crypto_a_SOURCES = crypto.c aes.c tortls.c torgzip.c +libor_a_SOURCES = \ + address.c \ + compat.c \ + container.c \ + di_ops.c \ + log.c \ + memarea.c \ + mempool.c \ + procmon.c \ + util.c \ + util_codedigest.c \ + $(libor_extra_source) + +libor_crypto_a_SOURCES = \ + aes.c \ + crypto.c \ + torgzip.c \ + tortls.c + libor_event_a_SOURCES = compat_libevent.c -noinst_HEADERS = address.h torlog.h crypto.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc compat_libevent.h tortls_states.h di_ops.h procmon.h +noinst_HEADERS = \ + address.h \ + aes.h \ + ciphers.inc \ + compat.h \ + compat_libevent.h \ + container.h \ + crypto.h \ + di_ops.h \ + ht.h \ + memarea.h \ + mempool.h \ + procmon.h \ + strlcat.c \ + strlcpy.c \ + torgzip.h \ + torint.h \ + torlog.h \ + tortls.h \ + tortls_states.h \ + util.h common_sha1.i: $(libor_SOURCES) $(libor_crypto_a_SOURCES) $(noinst_HEADERS) if test "@SHA1SUM@" != none; then \ diff --git a/src/common/address.c b/src/common/address.c index 1c725393d9..d0c2d5e15c 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -343,7 +343,7 @@ tor_addr_is_internal(const tor_addr_t *addr, int for_listening) * brackets. */ const char * -tor_addr_to_str(char *dest, const tor_addr_t *addr, int len, int decorate) +tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate) { const char *ptr; tor_assert(addr && dest); @@ -962,6 +962,19 @@ fmt_addr(const tor_addr_t *addr) return buf; } +/** Like fmt_addr(), but takes <b>addr</b> as a host-order IPv4 + * addresses. Also not thread-safe, also clobbers its return buffer on + * repeated calls. */ +const char * +fmt_addr32(uint32_t addr) +{ + static char buf[INET_NTOA_BUF_LEN]; + struct in_addr in; + in.s_addr = htonl(addr); + tor_inet_ntoa(&in, buf, sizeof(buf)); + return buf; +} + /** Convert the string in <b>src</b> to a tor_addr_t <b>addr</b>. The string * may be an IPv4 address, an IPv6 address, or an IPv6 address surrounded by * square brackets. diff --git a/src/common/address.h b/src/common/address.h index 9a7656f69b..e41e4c2ba4 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -127,6 +127,7 @@ tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u) int tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr_out); char *tor_dup_addr(const tor_addr_t *addr) ATTR_MALLOC; const char *fmt_addr(const tor_addr_t *addr); +const char * fmt_addr32(uint32_t addr); int get_interface_address6(int severity, sa_family_t family, tor_addr_t *addr); /** Flag to specify how to do a comparison between addresses. In an "exact" @@ -163,7 +164,7 @@ int tor_addr_port_parse(const char *s, tor_addr_t *addr_out, int tor_addr_parse_mask_ports(const char *s, tor_addr_t *addr_out, maskbits_t *mask_out, uint16_t *port_min_out, uint16_t *port_max_out); -const char * tor_addr_to_str(char *dest, const tor_addr_t *addr, int len, +const char * tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate); int tor_addr_from_str(tor_addr_t *addr, const char *src); void tor_addr_copy(tor_addr_t *dest, const tor_addr_t *src); diff --git a/src/common/aes.c b/src/common/aes.c index c2fdeb594a..81091e9f02 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -291,11 +291,20 @@ void aes_crypt(aes_cnt_cipher_t *cipher, const char *input, size_t len, char *output) { - - /* XXXX This function is up to 5% of our runtime in some profiles; - * we should look into unrolling some of the loops; taking advantage - * of alignment, using a bigger buffer, and so on. Not till after 0.1.2.x, - * though. */ + /* This function alone is up to 5% of our runtime in some profiles; anything + * we could do to make it faster would be great. + * + * Experimenting suggests that unrolling the inner loop into a switch + * statement doesn't help. What does seem to help is making the input and + * output buffers word aligned, and never crypting anything besides an + * integer number of words at a time -- it shaves maybe 4-5% of the per-byte + * encryption time measured by bench_aes. We can't do that with the current + * Tor protocol, though: Tor really likes to crypt things in 509-byte + * chunks. + * + * If we were really ambitous, we'd force len to be a multiple of the block + * size, and shave maybe another 4-5% off. + */ int c = cipher->pos; if (PREDICT_UNLIKELY(!len)) return; diff --git a/src/common/compat.c b/src/common/compat.c index 9377959eb4..61a4a6f9be 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -103,6 +103,35 @@ #include "strlcat.c" #endif +/** As open(path, flags, mode), but return an fd with the close-on-exec mode + * set. */ +int +tor_open_cloexec(const char *path, int flags, unsigned mode) +{ +#ifdef O_CLOEXEC + return open(path, flags|O_CLOEXEC, mode); +#else + int fd = open(path, flags, mode); +#ifdef FD_CLOEXEC + if (fd >= 0) + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + return fd; +#endif +} + +/** DOCDOC */ +FILE * +tor_fopen_cloexec(const char *path, const char *mode) +{ + FILE *result = fopen(path, mode); +#ifdef FD_CLOEXEC + if (result != NULL) + fcntl(fileno(result), F_SETFD, FD_CLOEXEC); +#endif + return result; +} + #ifdef HAVE_SYS_MMAN_H /** Try to create a memory mapping for <b>filename</b> and return it. On * failure, return NULL. Sets errno properly, using ERANGE to mean @@ -118,7 +147,7 @@ tor_mmap_file(const char *filename) tor_assert(filename); - fd = open(filename, O_RDONLY, 0); + fd = tor_open_cloexec(filename, O_RDONLY, 0); if (fd<0) { int save_errno = errno; int severity = (errno == ENOENT) ? LOG_INFO : LOG_WARN; @@ -695,7 +724,7 @@ tor_lockfile_lock(const char *filename, int blocking, int *locked_out) *locked_out = 0; log_info(LD_FS, "Locking \"%s\"", filename); - fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); + fd = tor_open_cloexec(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); if (fd < 0) { log_warn(LD_FS,"Couldn't open \"%s\" for locking: %s", filename, strerror(errno)); @@ -922,8 +951,16 @@ mark_socket_open(tor_socket_t s) tor_socket_t tor_open_socket(int domain, int type, int protocol) { - tor_socket_t s = socket(domain, type, protocol); + tor_socket_t s; +#ifdef SOCK_CLOEXEC +#define LINUX_CLOEXEC_OPEN_SOCKET + type |= SOCK_CLOEXEC; +#endif + s = socket(domain, type, protocol); if (SOCKET_OK(s)) { +#if !defined(LINUX_CLOEXEC_OPEN_SOCKET) && defined(FD_CLOEXEC) + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); ++n_sockets_open; mark_socket_open(s); @@ -936,8 +973,17 @@ tor_open_socket(int domain, int type, int protocol) tor_socket_t tor_accept_socket(int sockfd, struct sockaddr *addr, socklen_t *len) { - tor_socket_t s = accept(sockfd, addr, len); + tor_socket_t s; +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) +#define LINUX_CLOEXEC_ACCEPT + s = accept4(sockfd, addr, len, SOCK_CLOEXEC); +#else + s = accept(sockfd, addr, len); +#endif if (SOCKET_OK(s)) { +#if !defined(LINUX_CLOEXEC_ACCEPT) && defined(FD_CLOEXEC) + fcntl(s, F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); ++n_sockets_open; mark_socket_open(s); @@ -993,8 +1039,17 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) //don't use win32 socketpairs (they are always bad) #if defined(HAVE_SOCKETPAIR) && !defined(MS_WINDOWS) int r; +#ifdef SOCK_CLOEXEC + type |= SOCK_CLOEXEC; +#endif r = socketpair(family, type, protocol, fd); if (r == 0) { +#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) + if (fd[0] >= 0) + fcntl(fd[0], F_SETFD, FD_CLOEXEC); + if (fd[1] >= 0) + fcntl(fd[1], F_SETFD, FD_CLOEXEC); +#endif socket_accounting_lock(); if (fd[0] >= 0) { ++n_sockets_open; @@ -1982,6 +2037,52 @@ spawn_exit(void) #endif } +/** Implementation logic for compute_num_cpus(). */ +static int +compute_num_cpus_impl(void) +{ +#ifdef MS_WINDOWS + SYSTEM_INFO info; + memset(&info, 0, sizeof(info)); + GetSystemInfo(&info); + if (info.dwNumberOfProcessors >= 1 && info.dwNumberOfProcessors < INT_MAX) + return (int)info.dwNumberOfProcessors; + else + return -1; +#elif defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_CONF) + long cpus = sysconf(_SC_NPROCESSORS_CONF); + if (cpus >= 1 && cpus < INT_MAX) + return (int)cpus; + else + return -1; +#else + return -1; +#endif +} + +#define MAX_DETECTABLE_CPUS 16 + +/** Return how many CPUs we are running with. We assume that nobody is + * using hot-swappable CPUs, so we don't recompute this after the first + * time. Return -1 if we don't know how to tell the number of CPUs on this + * system. + */ +int +compute_num_cpus(void) +{ + static int num_cpus = -2; + if (num_cpus == -2) { + num_cpus = compute_num_cpus_impl(); + tor_assert(num_cpus != -2); + if (num_cpus > MAX_DETECTABLE_CPUS) + log_notice(LD_GENERAL, "Wow! I detected that you have %d CPUs. I " + "will not autodetect any more than %d, though. If you " + "want to configure more, set NumCPUs in your torrc", + num_cpus, MAX_DETECTABLE_CPUS); + } + return num_cpus; +} + /** Set *timeval to the current time of day. On error, log and terminate. * (Same as gettimeofday(timeval,NULL), but never returns -1.) */ diff --git a/src/common/compat.h b/src/common/compat.h index eb79b04449..094036dff2 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -51,6 +51,8 @@ #include <netinet6/in6.h> #endif +#include <stdio.h> + #if defined (WINCE) #include <fcntl.h> #include <io.h> @@ -367,6 +369,9 @@ struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); #endif /* ===== File compatibility */ +int tor_open_cloexec(const char *path, int flags, unsigned mode); +FILE *tor_fopen_cloexec(const char *path, const char *mode); + int replace_file(const char *from, const char *to); int touch_file(const char *fname); @@ -577,6 +582,8 @@ void spawn_exit(void) ATTR_NORETURN; #undef TOR_IS_MULTITHREADED #endif +int compute_num_cpus(void); + /* Because we use threads instead of processes on most platforms (Windows, * Linux, etc), we need locking for them. On platforms with poor thread * support or broken gethostbyname_r, these functions are no-ops. */ diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 6d89be804b..e0c7e3a2da 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -165,9 +165,11 @@ struct event_base *the_event_base = NULL; /** Initialize the Libevent library and set up the event base. */ void -tor_libevent_initialize(void) +tor_libevent_initialize(tor_libevent_cfg *torcfg) { tor_assert(the_event_base == NULL); + /* some paths below don't use torcfg, so avoid unused variable warnings */ + (void)torcfg; #ifdef __APPLE__ if (MACOSX_KQUEUE_IS_BROKEN || @@ -177,7 +179,29 @@ tor_libevent_initialize(void) #endif #ifdef HAVE_EVENT2_EVENT_H - the_event_base = event_base_new(); + { + struct event_config *cfg = event_config_new(); + +#if defined(MS_WINDOWS) && defined(USE_BUFFEREVENTS) + if (! torcfg->disable_iocp) + event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP); +#endif + +#if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= V(2,0,7) + if (torcfg->num_cpus > 0) + event_config_set_num_cpus_hint(cfg, torcfg->num_cpus); +#endif + +#if LIBEVENT_VERSION_NUMBER >= V(2,0,9) + /* We can enable changelist support with epoll, since we don't give + * Libevent any dup'd fds. This lets us avoid some syscalls. */ + event_config_set_flag(cfg, EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST); +#endif + + the_event_base = event_base_new_with_config(cfg); + + event_config_free(cfg); + } #else the_event_base = event_init(); #endif @@ -552,3 +576,29 @@ periodic_timer_free(periodic_timer_t *timer) tor_free(timer); } +#ifdef USE_BUFFEREVENTS +static const struct timeval *one_tick = NULL; +/** + * Return a special timeout to be passed whenever libevent's O(1) timeout + * implementation should be used. Only use this when the timer is supposed + * to fire after 1 / TOR_LIBEVENT_TICKS_PER_SECOND seconds have passed. +*/ +const struct timeval * +tor_libevent_get_one_tick_timeout(void) +{ + if (PREDICT_UNLIKELY(one_tick == NULL)) { + struct event_base *base = tor_libevent_get_base(); + struct timeval tv; + if (TOR_LIBEVENT_TICKS_PER_SECOND == 1) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } else { + tv.tv_sec = 0; + tv.tv_usec = 1000000 / TOR_LIBEVENT_TICKS_PER_SECOND; + } + one_tick = event_base_init_common_timeout(base, &tv); + } + return one_tick; +} +#endif + diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 89b256396b..8669fd4e0b 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -8,6 +8,9 @@ struct event; struct event_base; +#ifdef USE_BUFFEREVENTS +struct bufferevent; +#endif #ifdef HAVE_EVENT2_EVENT_H #include <event2/util.h> @@ -54,7 +57,12 @@ struct timeval; int tor_event_base_loopexit(struct event_base *base, struct timeval *tv); #endif -void tor_libevent_initialize(void); +typedef struct tor_libevent_cfg { + int disable_iocp; + int num_cpus; +} tor_libevent_cfg; + +void tor_libevent_initialize(tor_libevent_cfg *cfg); struct event_base *tor_libevent_get_base(void); const char *tor_libevent_get_method(void); void tor_check_libevent_version(const char *m, int server, @@ -62,5 +70,10 @@ void tor_check_libevent_version(const char *m, int server, void tor_check_libevent_header_compatibility(void); const char *tor_libevent_get_version_str(void); +#ifdef USE_BUFFEREVENTS +#define TOR_LIBEVENT_TICKS_PER_SECOND 3 +const struct timeval *tor_libevent_get_one_tick_timeout(void); +#endif + #endif diff --git a/src/common/container.c b/src/common/container.c index da44b7fe68..8635409d81 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -287,7 +287,6 @@ smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2) /** Remove the <b>idx</b>th element of sl; if idx is not the last * element, swap the last element of sl into the <b>idx</b>th space. - * Return the old value of the <b>idx</b>th element. */ void smartlist_del(smartlist_t *sl, int idx) diff --git a/src/common/crypto.c b/src/common/crypto.c index 1ecc24ce23..684ede06a6 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -937,7 +937,7 @@ crypto_pk_public_checksig_digest(crypto_pk_env_t *env, const char *data, log_warn(LD_BUG, "couldn't compute digest"); return -1; } - buflen = crypto_pk_keysize(env)+1; + buflen = crypto_pk_keysize(env); buf = tor_malloc(buflen); r = crypto_pk_public_checksig(env,buf,buflen,sig,siglen); if (r != DIGEST_LEN) { @@ -1122,8 +1122,8 @@ crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env, warnOnFailure); } - buf = tor_malloc(pkeylen+1); - outlen = crypto_pk_private_decrypt(env,buf,pkeylen+1,from,pkeylen,padding, + buf = tor_malloc(pkeylen); + outlen = crypto_pk_private_decrypt(env,buf,pkeylen,from,pkeylen,padding, warnOnFailure); if (outlen<0) { log_fn(warnOnFailure?LOG_WARN:LOG_DEBUG, LD_CRYPTO, diff --git a/src/common/crypto.h b/src/common/crypto.h index 54c7a67a3b..f28087589e 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -244,7 +244,8 @@ void secret_to_key(char *key_out, size_t key_out_len, const char *secret, size_t secret_len, const char *s2k_specifier); #ifdef CRYPTO_PRIVATE -/* Prototypes for private functions only used by tortls.c and crypto.c */ +/* Prototypes for private functions only used by tortls.c, crypto.c, and the + * unit tests. */ struct rsa_st; struct evp_pkey_st; struct dh_st; diff --git a/src/common/log.c b/src/common/log.c index ac98f13539..abdce142ad 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -154,6 +154,17 @@ log_set_application_name(const char *name) appname = name ? tor_strdup(name) : NULL; } +/** Log time granularity in milliseconds. */ +static int log_time_granularity = 1; + +/** Define log time granularity for all logs to be <b>granularity_msec</b> + * milliseconds. */ +void +set_log_time_granularity(int granularity_msec) +{ + log_time_granularity = granularity_msec; +} + /** Helper: Write the standard prefix for log lines to a * <b>buf_len</b> character buffer in <b>buf</b>. */ @@ -164,14 +175,22 @@ _log_prefix(char *buf, size_t buf_len, int severity) struct timeval now; struct tm tm; size_t n; - int r; + int r, ms; tor_gettimeofday(&now); t = (time_t)now.tv_sec; + ms = (int)now.tv_usec / 1000; + if (log_time_granularity >= 1000) { + t -= t % (log_time_granularity / 1000); + ms = 0; + } else { + ms -= ((int)now.tv_usec / 1000) % log_time_granularity; + } n = strftime(buf, buf_len, "%b %d %H:%M:%S", tor_localtime_r(&t, &tm)); - r = tor_snprintf(buf+n, buf_len-n, ".%.3i [%s] ", - (int)now.tv_usec / 1000, sev_to_string(severity)); + r = tor_snprintf(buf+n, buf_len-n, ".%.3i [%s] ", ms, + sev_to_string(severity)); + if (r<0) return buf_len-1; else @@ -703,7 +722,7 @@ change_callback_log_severity(int loglevelMin, int loglevelMax, UNLOCK_LOGS(); } -/** If there are any log messages that were genered with LD_NOCB waiting to +/** If there are any log messages that were generated with LD_NOCB waiting to * be sent to callback-based loggers, send them now. */ void flush_pending_log_callbacks(void) @@ -803,7 +822,7 @@ add_file_log(const log_severity_list_t *severity, const char *filename) int fd; logfile_t *lf; - fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); + fd = tor_open_cloexec(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); if (fd<0) return -1; if (tor_fd_seekend(fd)<0) diff --git a/src/common/mempool.c b/src/common/mempool.c index c444923189..30d7788043 100644 --- a/src/common/mempool.c +++ b/src/common/mempool.c @@ -137,7 +137,7 @@ struct mp_chunk_t { int capacity; /**< Number of items that can be fit into this chunk. */ size_t mem_size; /**< Number of usable bytes in mem. */ char *next_mem; /**< Pointer into part of <b>mem</b> not yet carved up. */ - char mem[1]; /**< Storage for this chunk. (Not actual size.) */ + char mem[FLEXIBLE_ARRAY_MEMBER]; /**< Storage for this chunk. */ }; /** Number of extra bytes needed beyond mem_size to allocate a chunk. */ diff --git a/src/common/torlog.h b/src/common/torlog.h index 541a0d1738..4c5729ef53 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -92,8 +92,10 @@ #define LD_HIST (1u<<18) /** OR handshaking */ #define LD_HANDSHAKE (1u<<19) +/** Heartbeat messages */ +#define LD_HEARTBEAT (1u<<20) /** Number of logging domains in the code. */ -#define N_LOGGING_DOMAINS 20 +#define N_LOGGING_DOMAINS 21 /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ @@ -145,6 +147,7 @@ void change_callback_log_severity(int loglevelMin, int loglevelMax, log_callback cb); void flush_pending_log_callbacks(void); void log_set_application_name(const char *name); +void set_log_time_granularity(int granularity_msec); void tor_log(int severity, log_domain_mask_t domain, const char *format, ...) CHECK_PRINTF(3,4); diff --git a/src/common/tortls.c b/src/common/tortls.c index 10f4440cb4..aaf2fdaacb 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -44,14 +44,20 @@ #error "We require OpenSSL >= 0.9.7" #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent_ssl.h> +#include <event2/buffer.h> +#include "compat_libevent.h" +#endif + #define CRYPTO_PRIVATE /* to import prototypes from crypto.h */ +#define TORTLS_PRIVATE #include "crypto.h" #include "tortls.h" #include "util.h" #include "torlog.h" #include "container.h" -#include "ht.h" #include <string.h> /* Enable the "v2" TLS handshake. @@ -97,11 +103,13 @@ typedef struct tor_tls_context_t { crypto_pk_env_t *key; } tor_tls_context_t; +#define TOR_TLS_MAGIC 0x71571571 + /** Holds a SSL object and its associated data. Members are only * accessed from within tortls.c. */ struct tor_tls_t { - HT_ENTRY(tor_tls_t) node; + uint32_t magic; tor_tls_context_t *context; /** A link to the context object for this tls. */ SSL *ssl; /**< An OpenSSL SSL object. */ int socket; /**< The underlying file descriptor for this TLS connection. */ @@ -109,6 +117,7 @@ struct tor_tls_t { enum { TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, + TOR_TLS_ST_BUFFEREVENT } state : 3; /**< The current SSL state, depending on which operations have * completed successfully. */ unsigned int isServer:1; /**< True iff this is a server-side connection */ @@ -117,8 +126,10 @@ struct tor_tls_t { * of the connection protocol (client sends * different cipher list, server sends only * one certificate). */ - /** True iff we should call negotiated_callback when we're done reading. */ + /** True iff we should call negotiated_callback when we're done reading. */ unsigned int got_renegotiate:1; + /** Incremented every time we start the server side of a handshake. */ + uint8_t server_handshake_count; size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last * time. */ /** Last values retrieved from BIO_number_read()/write(); see @@ -143,42 +154,29 @@ static SSL_CIPHER *CLIENT_CIPHER_DUMMIES = NULL; static STACK_OF(SSL_CIPHER) *CLIENT_CIPHER_STACK = NULL; #endif -/** Helper: compare tor_tls_t objects by its SSL. */ -static INLINE int -tor_tls_entries_eq(const tor_tls_t *a, const tor_tls_t *b) -{ - return a->ssl == b->ssl; -} +/** The ex_data index in which we store a pointer to an SSL object's + * corresponding tor_tls_t object. */ +static int tor_tls_object_ex_data_index = -1; -/** Helper: return a hash value for a tor_tls_t by its SSL. */ -static INLINE unsigned int -tor_tls_entry_hash(const tor_tls_t *a) +/** Helper: Allocate tor_tls_object_ex_data_index. */ +static void +tor_tls_allocate_tor_tls_object_ex_data_index(void) { -#if SIZEOF_INT == SIZEOF_VOID_P - return ((unsigned int)(uintptr_t)a->ssl); -#else - return (unsigned int) ((((uint64_t)a->ssl)>>2) & UINT_MAX); -#endif + if (tor_tls_object_ex_data_index == -1) { + tor_tls_object_ex_data_index = + SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + tor_assert(tor_tls_object_ex_data_index != -1); + } } -/** Map from SSL* pointers to tor_tls_t objects using those pointers. - */ -static HT_HEAD(tlsmap, tor_tls_t) tlsmap_root = HT_INITIALIZER(); - -HT_PROTOTYPE(tlsmap, tor_tls_t, node, tor_tls_entry_hash, - tor_tls_entries_eq) -HT_GENERATE(tlsmap, tor_tls_t, node, tor_tls_entry_hash, - tor_tls_entries_eq, 0.6, malloc, realloc, free) - /** Helper: given a SSL* pointer, return the tor_tls_t object using that * pointer. */ static INLINE tor_tls_t * tor_tls_get_by_ssl(const SSL *ssl) { - tor_tls_t search, *result; - memset(&search, 0, sizeof(search)); - search.ssl = (SSL*)ssl; - result = HT_FIND(tlsmap, &tlsmap_root, &search); + tor_tls_t *result = SSL_get_ex_data(ssl, tor_tls_object_ex_data_index); + if (result) + tor_assert(result->magic == TOR_TLS_MAGIC); return result; } @@ -189,7 +187,7 @@ static X509* tor_tls_create_certificate(crypto_pk_env_t *rsa, const char *cname, const char *cname_sign, unsigned int lifetime); -static void tor_tls_unblock_renegotiation(tor_tls_t *tls); + static int tor_tls_context_init_one(tor_tls_context_t **ppcontext, crypto_pk_env_t *identity, unsigned int key_lifetime); @@ -200,6 +198,7 @@ static tor_tls_context_t *tor_tls_context_new(crypto_pk_env_t *identity, * to touch them. */ static tor_tls_context_t *server_tls_context = NULL; static tor_tls_context_t *client_tls_context = NULL; + /** True iff tor_tls_init() has been called. */ static int tls_library_is_initialized = 0; @@ -223,36 +222,46 @@ ssl_state_to_string(int ssl_state) return buf; } +void +tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing) +{ + const char *state = NULL, *addr; + const char *msg, *lib, *func; + int st; + + st = (tls && tls->ssl) ? tls->ssl->state : -1; + state = (st>=0)?ssl_state_to_string(st):"---"; + + addr = tls ? tls->address : NULL; + + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (doing) { + log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", + doing, addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } else { + log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", + addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } +} + /** Log all pending tls errors at level <b>severity</b>. Use * <b>doing</b> to describe our current activities. */ static void tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) { - const char *state = NULL; - int st; unsigned long err; - const char *msg, *lib, *func, *addr; - addr = tls ? tls->address : NULL; - st = (tls && tls->ssl) ? tls->ssl->state : -1; + while ((err = ERR_get_error()) != 0) { - msg = (const char*)ERR_reason_error_string(err); - lib = (const char*)ERR_lib_error_string(err); - func = (const char*)ERR_func_error_string(err); - if (!state) - state = (st>=0)?ssl_state_to_string(st):"---"; - if (!msg) msg = "(null)"; - if (!lib) lib = "(null)"; - if (!func) func = "(null)"; - if (doing) { - log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", - doing, addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } else { - log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", - addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } + tor_tls_log_one_error(tls, err, severity, domain, doing); } } @@ -427,6 +436,8 @@ tor_tls_init(void) SSLeay_version(SSLEAY_VERSION), version); } + tor_tls_allocate_tor_tls_object_ex_data_index(); + tls_library_is_initialized = 1; } } @@ -445,10 +456,6 @@ tor_tls_free_all(void) client_tls_context = NULL; tor_tls_context_decref(ctx); } - if (!HT_EMPTY(&tlsmap_root)) { - log_warn(LD_MM, "Still have entries in the tlsmap at shutdown."); - } - HT_CLEAR(tlsmap, &tlsmap_root); #ifdef V2_HANDSHAKE_CLIENT if (CLIENT_CIPHER_DUMMIES) tor_free(CLIENT_CIPHER_DUMMIES); @@ -913,8 +920,11 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) /* Check whether we're watching for renegotiates. If so, this is one! */ if (tls->negotiated_callback) tls->got_renegotiate = 1; + if (tls->server_handshake_count < 127) /*avoid any overflow possibility*/ + ++tls->server_handshake_count; } else { log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + return; } /* Now check the cipher list. */ @@ -931,6 +941,10 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) if (tls) { tls->wasV2Handshake = 1; +#ifdef USE_BUFFEREVENTS + if (use_unsafe_renegotiation_flag) + tls->ssl->s3->flags |= SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#endif } else { log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); } @@ -1018,6 +1032,7 @@ tor_tls_new(int sock, int isServer) tor_tls_t *result = tor_malloc_zero(sizeof(tor_tls_t)); tor_tls_context_t *context = isServer ? server_tls_context : client_tls_context; + result->magic = TOR_TLS_MAGIC; tor_assert(context); /* make sure somebody made it first */ if (!(result->ssl = SSL_new(context->ctx))) { @@ -1058,7 +1073,14 @@ tor_tls_new(int sock, int isServer) tor_free(result); return NULL; } - HT_INSERT(tlsmap, &tlsmap_root, result); + { + int set_worked = + SSL_set_ex_data(result->ssl, tor_tls_object_ex_data_index, result); + if (!set_worked) { + log_warn(LD_BUG, + "Couldn't set the tls for an SSL*; connection will fail"); + } + } SSL_set_bio(result->ssl, bio, bio); tor_tls_context_incref(context); result->context = context; @@ -1117,7 +1139,7 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls, /** If this version of openssl requires it, turn on renegotiation on * <b>tls</b>. */ -static void +void tor_tls_unblock_renegotiation(tor_tls_t *tls) { /* Yes, we know what we are doing here. No, we do not treat a renegotiation @@ -1141,6 +1163,19 @@ tor_tls_block_renegotiation(tor_tls_t *tls) tls->ssl->s3->flags &= ~SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; } +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ + if (use_unsafe_renegotiation_flag) { + tor_assert(0 != (tls->ssl->s3->flags & + SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); + } + if (use_unsafe_renegotiation_op) { + long options = SSL_get_options(tls->ssl); + tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); + } +} + /** Return whether this tls initiated the connect (client) or * received it (server). */ int @@ -1156,14 +1191,9 @@ tor_tls_is_server(tor_tls_t *tls) void tor_tls_free(tor_tls_t *tls) { - tor_tls_t *removed; if (!tls) return; tor_assert(tls->ssl); - removed = HT_REMOVE(tlsmap, &tlsmap_root, tls); - if (!removed) { - log_warn(LD_BUG, "Freeing a TLS that was not in the ssl->tls map."); - } #ifdef SSL_set_tlsext_host_name SSL_set_tlsext_host_name(tls->ssl, NULL); #endif @@ -1173,6 +1203,7 @@ tor_tls_free(tor_tls_t *tls) if (tls->context) tor_tls_context_decref(tls->context); tor_free(tls->address); + tls->magic = 0x99999999; tor_free(tls); } @@ -1285,56 +1316,86 @@ tor_tls_handshake(tor_tls_t *tls) } if (r == TOR_TLS_DONE) { tls->state = TOR_TLS_ST_OPEN; - if (tls->isServer) { - SSL_set_info_callback(tls->ssl, NULL); - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); - /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */ - tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN; + return tor_tls_finish_handshake(tls); + } + return r; +} + +/** Perform the final part of the intial TLS handshake on <b>tls</b>. This + * should be called for the first handshake only: it determines whether the v1 + * or the v2 handshake was used, and adjusts things for the renegotiation + * handshake as appropriate. + * + * tor_tls_handshake() calls this on its own; you only need to call this if + * bufferevent is doing the handshake for you. + */ +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + int r = TOR_TLS_DONE; + if (tls->isServer) { + SSL_set_info_callback(tls->ssl, NULL); + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); + /* There doesn't seem to be a clear OpenSSL API to clear mode flags. */ + tls->ssl->mode &= ~SSL_MODE_NO_AUTO_CHAIN; #ifdef V2_HANDSHAKE_SERVER - if (tor_tls_client_is_using_v2_ciphers(tls->ssl, ADDR(tls))) { - /* This check is redundant, but back when we did it in the callback, - * we might have not been able to look up the tor_tls_t if the code - * was buggy. Fixing that. */ - if (!tls->wasV2Handshake) { - log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" - " get set. Fixing that."); - } - tls->wasV2Handshake = 1; - log_debug(LD_HANDSHAKE, - "Completed V2 TLS handshake with client; waiting " - "for renegotiation."); - } else { - tls->wasV2Handshake = 0; + if (tor_tls_client_is_using_v2_ciphers(tls->ssl, ADDR(tls))) { + /* This check is redundant, but back when we did it in the callback, + * we might have not been able to look up the tor_tls_t if the code + * was buggy. Fixing that. */ + if (!tls->wasV2Handshake) { + log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" + " get set. Fixing that."); } -#endif + tls->wasV2Handshake = 1; + log_debug(LD_HANDSHAKE, "Completed V2 TLS handshake with client; waiting" + " for renegotiation."); } else { + tls->wasV2Handshake = 0; + } +#endif + } else { #ifdef V2_HANDSHAKE_CLIENT - /* If we got no ID cert, we're a v2 handshake. */ - X509 *cert = SSL_get_peer_certificate(tls->ssl); - STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl); - int n_certs = sk_X509_num(chain); - if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) { - log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it " - "looks like a v1 handshake on %p", tls); - tls->wasV2Handshake = 0; - } else { - log_debug(LD_HANDSHAKE, - "Server sent back a single certificate; looks like " - "a v2 handshake on %p.", tls); - tls->wasV2Handshake = 1; - } - if (cert) - X509_free(cert); + /* If we got no ID cert, we're a v2 handshake. */ + X509 *cert = SSL_get_peer_certificate(tls->ssl); + STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl); + int n_certs = sk_X509_num(chain); + if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) { + log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it " + "looks like a v1 handshake on %p", tls); + tls->wasV2Handshake = 0; + } else { + log_debug(LD_HANDSHAKE, + "Server sent back a single certificate; looks like " + "a v2 handshake on %p.", tls); + tls->wasV2Handshake = 1; + } + if (cert) + X509_free(cert); #endif - if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { - tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); - r = TOR_TLS_ERROR_MISC; - } + if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { + tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); + r = TOR_TLS_ERROR_MISC; } } return r; } +#ifdef USE_BUFFEREVENTS +/** Put <b>tls</b>, which must be a client connection, into renegotiation + * mode. */ +int +tor_tls_start_renegotiating(tor_tls_t *tls) +{ + int r = SSL_renegotiate(tls->ssl); + if (r <= 0) { + return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN, + LD_HANDSHAKE); + } + return 0; +} +#endif + /** Client only: Renegotiate a TLS session. When finished, returns * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, or * TOR_TLS_WANTWRITE. @@ -1551,6 +1612,8 @@ tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_env_t **identity_key) log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found"); goto done; } + tls_log_errors(tls, severity, LD_HANDSHAKE, "before verifying certificate"); + if (!(id_pkey = X509_get_pubkey(id_cert)) || X509_verify(cert, id_pkey) <= 0) { log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0"); @@ -1700,6 +1763,22 @@ tor_tls_used_v1_handshake(tor_tls_t *tls) return 1; } +/** Return the number of server handshakes that we've noticed doing on + * <b>tls</b>. */ +int +tor_tls_get_num_server_handshakes(tor_tls_t *tls) +{ + return tls->server_handshake_count; +} + +/** Return true iff the server TLS connection <b>tls</b> got the renegotiation + * request it was waiting for. */ +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + return tls->got_renegotiate; +} + /** Examine the amount of memory used and available for buffers in <b>tls</b>. * Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read * buffer and *<b>rbuf_bytes</b> to the amount actually used. @@ -1722,3 +1801,71 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls, *wbuf_bytes = tls->ssl->s3->wbuf.left; } +#ifdef USE_BUFFEREVENTS +/** Construct and return an TLS-encrypting bufferevent to send data over + * <b>socket</b>, which must match the socket of the underlying bufferevent + * <b>bufev_in</b>. The TLS object <b>tls</b> is used for encryption. + * + * This function will either create a filtering bufferevent that wraps around + * <b>bufev_in</b>, or it will free bufev_in and return a new bufferevent that + * uses the <b>tls</b> to talk to the network directly. Do not use + * <b>bufev_in</b> after calling this function. + * + * The connection will start out doing a server handshake if <b>receiving</b> + * is strue, and a client handshake otherwise. + * + * Returns NULL on failure. + */ +struct bufferevent * +tor_tls_init_bufferevent(tor_tls_t *tls, struct bufferevent *bufev_in, + evutil_socket_t socket, int receiving, + int filter) +{ + struct bufferevent *out; + const enum bufferevent_ssl_state state = receiving ? + BUFFEREVENT_SSL_ACCEPTING : BUFFEREVENT_SSL_CONNECTING; + + if (filter) { + /* Grab an extra reference to the SSL, since BEV_OPT_CLOSE_ON_FREE + means that the SSL will get freed too. + + This increment makes our SSL usage not-threadsafe, BTW. We should + see if we're allowed to use CRYPTO_add from outside openssl. */ + tls->ssl->references += 1; + out = bufferevent_openssl_filter_new(tor_libevent_get_base(), + bufev_in, + tls->ssl, + state, + BEV_OPT_DEFER_CALLBACKS| + BEV_OPT_CLOSE_ON_FREE); + } else { + if (bufev_in) { + evutil_socket_t s = bufferevent_getfd(bufev_in); + tor_assert(s == -1 || s == socket); + tor_assert(evbuffer_get_length(bufferevent_get_input(bufev_in)) == 0); + tor_assert(evbuffer_get_length(bufferevent_get_output(bufev_in)) == 0); + tor_assert(BIO_number_read(SSL_get_rbio(tls->ssl)) == 0); + tor_assert(BIO_number_written(SSL_get_rbio(tls->ssl)) == 0); + bufferevent_free(bufev_in); + } + + /* Current versions (as of 2.0.x) of Libevent need to defer + * bufferevent_openssl callbacks, or else our callback functions will + * get called reentrantly, which is bad for us. + */ + out = bufferevent_openssl_socket_new(tor_libevent_get_base(), + socket, + tls->ssl, + state, + BEV_OPT_DEFER_CALLBACKS); + } + tls->state = TOR_TLS_ST_BUFFEREVENT; + + /* Unblock _after_ creating the bufferevent, since accept/connect tend to + * clear flags. */ + tor_tls_unblock_renegotiation(tls); + + return out; +} +#endif + diff --git a/src/common/tortls.h b/src/common/tortls.h index 55fee81aea..ecb5bd2fbe 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -67,8 +67,11 @@ int tor_tls_check_lifetime(tor_tls_t *tls, int tolerance); int tor_tls_read(tor_tls_t *tls, char *cp, size_t len); int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); int tor_tls_handshake(tor_tls_t *tls); +int tor_tls_finish_handshake(tor_tls_t *tls); int tor_tls_renegotiate(tor_tls_t *tls); +void tor_tls_unblock_renegotiation(tor_tls_t *tls); void tor_tls_block_renegotiation(tor_tls_t *tls); +void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls); int tor_tls_shutdown(tor_tls_t *tls); int tor_tls_get_pending_bytes(tor_tls_t *tls); size_t tor_tls_get_forced_write_size(tor_tls_t *tls); @@ -81,12 +84,24 @@ void tor_tls_get_buffer_sizes(tor_tls_t *tls, size_t *wbuf_capacity, size_t *wbuf_bytes); int tor_tls_used_v1_handshake(tor_tls_t *tls); +int tor_tls_get_num_server_handshakes(tor_tls_t *tls); +int tor_tls_server_got_renegotiate(tor_tls_t *tls); /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack. */ #define check_no_tls_errors() _check_no_tls_errors(__FILE__,__LINE__) void _check_no_tls_errors(const char *fname, int line); +void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing); + +#ifdef USE_BUFFEREVENTS +int tor_tls_start_renegotiating(tor_tls_t *tls); +struct bufferevent *tor_tls_init_bufferevent(tor_tls_t *tls, + struct bufferevent *bufev_in, + evutil_socket_t socket, int receiving, + int filter); +#endif #endif diff --git a/src/common/util.c b/src/common/util.c index 6f323dd20c..a5a6ea3e8b 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -14,6 +14,7 @@ #define _GNU_SOURCE #include "orconfig.h" +#define UTIL_PRIVATE #include "util.h" #include "torlog.h" #undef log @@ -952,7 +953,7 @@ esc_for_log(const char *s) char *result, *outp; size_t len = 3; if (!s) { - return tor_strdup(""); + return tor_strdup("(null)"); } for (cp = s; *cp; ++cp) { @@ -1869,7 +1870,7 @@ start_writing_to_file(const char *fname, int open_flags, int mode, if (open_flags & O_BINARY) new_file->binary = 1; - new_file->fd = open(open_name, open_flags, mode); + new_file->fd = tor_open_cloexec(open_name, open_flags, mode); if (new_file->fd < 0) { log_warn(LD_FS, "Couldn't open \"%s\" (%s) for writing: %s", open_name, fname, strerror(errno)); @@ -2090,7 +2091,7 @@ read_file_to_str(const char *filename, int flags, struct stat *stat_out) tor_assert(filename); - fd = open(filename,O_RDONLY|(bin?O_BINARY:O_TEXT),0); + fd = tor_open_cloexec(filename,O_RDONLY|(bin?O_BINARY:O_TEXT),0); if (fd<0) { int severity = LOG_WARN; int save_errno = errno; @@ -2476,18 +2477,21 @@ digit_to_num(char d) * success, store the result in <b>out</b>, advance bufp to the next * character, and return 0. On failure, return -1. */ static int -scan_unsigned(const char **bufp, unsigned *out, int width) +scan_unsigned(const char **bufp, unsigned *out, int width, int base) { unsigned result = 0; int scanned_so_far = 0; + const int hex = base==16; + tor_assert(base == 10 || base == 16); if (!bufp || !*bufp || !out) return -1; if (width<0) width=MAX_SCANF_WIDTH; - while (**bufp && TOR_ISDIGIT(**bufp) && scanned_so_far < width) { - int digit = digit_to_num(*(*bufp)++); - unsigned new_result = result * 10 + digit; + while (**bufp && (hex?TOR_ISXDIGIT(**bufp):TOR_ISDIGIT(**bufp)) + && scanned_so_far < width) { + int digit = hex?hex_decode_digit(*(*bufp)++):digit_to_num(*(*bufp)++); + unsigned new_result = result * base + digit; if (new_result > UINT32_MAX || new_result < result) return -1; /* over/underflow. */ result = new_result; @@ -2549,11 +2553,12 @@ tor_vsscanf(const char *buf, const char *pattern, va_list ap) if (!width) /* No zero-width things. */ return -1; } - if (*pattern == 'u') { + if (*pattern == 'u' || *pattern == 'x') { unsigned *u = va_arg(ap, unsigned *); + const int base = (*pattern == 'u') ? 10 : 16; if (!*buf) return n_matched; - if (scan_unsigned(&buf, u, width)<0) + if (scan_unsigned(&buf, u, width, base)<0) return n_matched; ++pattern; ++n_matched; @@ -2590,9 +2595,9 @@ tor_vsscanf(const char *buf, const char *pattern, va_list ap) /** Minimal sscanf replacement: parse <b>buf</b> according to <b>pattern</b> * and store the results in the corresponding argument fields. Differs from - * sscanf in that it: Only handles %u and %Ns. Does not handle arbitrarily - * long widths. %u does not consume any space. Is locale-independent. - * Returns -1 on malformed patterns. + * sscanf in that it: Only handles %u and %x and %Ns. Does not handle + * arbitrarily long widths. %u and %x do not consume any space. Is + * locale-independent. Returns -1 on malformed patterns. * * (As with other locale-independent functions, we need this to parse data that * is in ASCII without worrying that the C library's locale-handling will make @@ -2786,7 +2791,7 @@ finish_daemon(const char *desired_cwd) exit(1); } - nullfd = open("/dev/null", O_RDWR); + nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0); if (nullfd < 0) { log_err(LD_GENERAL,"/dev/null can't be opened. Exiting."); exit(1); @@ -2857,3 +2862,451 @@ load_windows_system_library(const TCHAR *library_name) } #endif +/** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in + * <b>hex_errno</b>. Called between fork and _exit, so must be signal-handler + * safe. + * + * <b>hex_errno</b> must have at least HEX_ERRNO_SIZE bytes available. + * + * The format of <b>hex_errno</b> is: "CHILD_STATE/ERRNO\n", left-padded + * with spaces. Note that there is no trailing \0. CHILD_STATE indicates where + * in the processs of starting the child process did the failure occur (see + * CHILD_STATE_* macros for definition), and SAVED_ERRNO is the value of + * errno when the failure occurred. + */ + +void +format_helper_exit_status(unsigned char child_state, int saved_errno, + char *hex_errno) +{ + unsigned int unsigned_errno; + char *cur; + size_t i; + + /* Fill hex_errno with spaces, and a trailing newline (memset may + not be signal handler safe, so we can't use it) */ + for (i = 0; i < (HEX_ERRNO_SIZE - 1); i++) + hex_errno[i] = ' '; + hex_errno[HEX_ERRNO_SIZE - 1] = '\n'; + + /* Convert errno to be unsigned for hex conversion */ + if (saved_errno < 0) { + unsigned_errno = (unsigned int) -saved_errno; + } else { + unsigned_errno = (unsigned int) saved_errno; + } + + /* Convert errno to hex (start before \n) */ + cur = hex_errno + HEX_ERRNO_SIZE - 2; + + /* Check for overflow on first iteration of the loop */ + if (cur < hex_errno) + return; + + do { + *cur-- = "0123456789ABCDEF"[unsigned_errno % 16]; + unsigned_errno /= 16; + } while (unsigned_errno != 0 && cur >= hex_errno); + + /* Prepend the minus sign if errno was negative */ + if (saved_errno < 0 && cur >= hex_errno) + *cur-- = '-'; + + /* Leave a gap */ + if (cur >= hex_errno) + *cur-- = '/'; + + /* Check for overflow on first iteration of the loop */ + if (cur < hex_errno) + return; + + /* Convert child_state to hex */ + do { + *cur-- = "0123456789ABCDEF"[child_state % 16]; + child_state /= 16; + } while (child_state != 0 && cur >= hex_errno); +} + +/* Maximum number of file descriptors, if we cannot get it via sysconf() */ +#define DEFAULT_MAX_FD 256 + +#define CHILD_STATE_INIT 0 +#define CHILD_STATE_PIPE 1 +#define CHILD_STATE_MAXFD 2 +#define CHILD_STATE_FORK 3 +#define CHILD_STATE_DUPOUT 4 +#define CHILD_STATE_DUPERR 5 +#define CHILD_STATE_REDIRECT 6 +#define CHILD_STATE_CLOSEFD 7 +#define CHILD_STATE_EXEC 8 +#define CHILD_STATE_FAILEXEC 9 + +#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code " + +/** Start a program in the background. If <b>filename</b> contains a '/', + * then it will be treated as an absolute or relative path. Otherwise the + * system path will be searched for <b>filename</b>. The strings in + * <b>argv</b> will be passed as the command line arguments of the child + * program (following convention, argv[0] should normally be the filename of + * the executable). The last element of argv must be NULL. If the child + * program is launched, the PID will be returned and <b>stdout_read</b> and + * <b>stdout_err</b> will be set to file descriptors from which the stdout + * and stderr, respectively, output of the child program can be read, and the + * stdin of the child process shall be set to /dev/null. Otherwise returns + * -1. Some parts of this code are based on the POSIX subprocess module from + * Python. + */ +int +tor_spawn_background(const char *const filename, int *stdout_read, + int *stderr_read, const char **argv) +{ +#ifdef MS_WINDOWS + (void) filename; (void) stdout_read; (void) stderr_read; (void) argv; + log_warn(LD_BUG, "not yet implemented on Windows."); + return -1; +#else + pid_t pid; + int stdout_pipe[2]; + int stderr_pipe[2]; + int fd, retval; + ssize_t nbytes; + + const char *error_message = SPAWN_ERROR_MESSAGE; + size_t error_message_length; + + /* Represents where in the process of spawning the program is; + this is used for printing out the error message */ + unsigned char child_state = CHILD_STATE_INIT; + + char hex_errno[HEX_ERRNO_SIZE]; + + static int max_fd = -1; + + /* We do the strlen here because strlen() is not signal handler safe, + and we are not allowed to use unsafe functions between fork and exec */ + error_message_length = strlen(error_message); + + child_state = CHILD_STATE_PIPE; + + /* Set up pipe for redirecting stdout and stderr of child */ + retval = pipe(stdout_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stdout communication with child process: %s", + strerror(errno)); + return -1; + } + + retval = pipe(stderr_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stderr communication with child process: %s", + strerror(errno)); + return -1; + } + + child_state = CHILD_STATE_MAXFD; + +#ifdef _SC_OPEN_MAX + if (-1 != max_fd) { + max_fd = (int) sysconf(_SC_OPEN_MAX); + if (max_fd == -1) + max_fd = DEFAULT_MAX_FD; + log_warn(LD_GENERAL, + "Cannot find maximum file descriptor, assuming %d", max_fd); + } +#else + max_fd = DEFAULT_MAX_FD; +#endif + + child_state = CHILD_STATE_FORK; + + pid = fork(); + if (0 == pid) { + /* In child */ + + child_state = CHILD_STATE_DUPOUT; + + /* Link child stdout to the write end of the pipe */ + retval = dup2(stdout_pipe[1], STDOUT_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_DUPERR; + + /* Link child stderr to the write end of the pipe */ + retval = dup2(stderr_pipe[1], STDERR_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_REDIRECT; + + /* Link stdin to /dev/null */ + fd = open("/dev/null", O_RDONLY); /* NOT cloexec, obviously. */ + if (fd != -1) + dup2(fd, STDIN_FILENO); + else + goto error; + + child_state = CHILD_STATE_CLOSEFD; + + close(stderr_pipe[0]); + close(stderr_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(fd); + + /* Close all other fds, including the read end of the pipe */ + /* XXX: We should now be doing enough FD_CLOEXEC setting to make + * this needless. */ + for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) { + close(fd); + } + + child_state = CHILD_STATE_EXEC; + + /* Call the requested program. We need the cast because + execvp doesn't define argv as const, even though it + does not modify the arguments */ + execvp(filename, (char *const *) argv); + + /* If we got here, the exec or open(/dev/null) failed */ + + child_state = CHILD_STATE_FAILEXEC; + + error: + /* XXX: are we leaking fds from the pipe? */ + + format_helper_exit_status(child_state, errno, hex_errno); + + /* Write the error message. GCC requires that we check the return + value, but there is nothing we can do if it fails */ + nbytes = write(STDOUT_FILENO, error_message, error_message_length); + nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno)); + + (void) nbytes; + + _exit(255); + return -1; /* Never reached, but avoids compiler warning */ + } + + /* In parent */ + + if (-1 == pid) { + log_warn(LD_GENERAL, "Failed to fork child process: %s", strerror(errno)); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return -1; + } + + /* Return read end of the pipes to caller, and close write end */ + *stdout_read = stdout_pipe[0]; + retval = close(stdout_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stdout pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + *stderr_read = stderr_pipe[0]; + retval = close(stderr_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stderr pipe in parent process: %s", + strerror(errno)); + /* Do not return -1, because the child is running, so the parent + needs to know about the pid in order to reap it later */ + } + + return pid; +#endif +} + +/** Read from stream, and send lines to log at the specified log level. + * Returns 1 if stream is closed normally, -1 if there is a error reading, and + * 0 otherwise. Handles lines from tor-fw-helper and + * tor_spawn_background() specially. + */ +static int +log_from_pipe(FILE *stream, int severity, const char *executable, + int *child_status) +{ + char buf[256]; + + for (;;) { + char *retval; + retval = fgets(buf, sizeof(buf), stream); + + if (NULL == retval) { + if (feof(stream)) { + /* Program has closed stream (probably it exited) */ + /* TODO: check error */ + fclose(stream); + return 1; + } else { + if (EAGAIN == errno) { + /* Nothing more to read, try again next time */ + return 0; + } else { + /* There was a problem, abandon this child process */ + fclose(stream); + return -1; + } + } + } else { + /* We have some data, log it and keep asking for more */ + size_t len; + + len = strlen(buf); + if (buf[len - 1] == '\n') { + /* Remove the trailing newline */ + buf[len - 1] = '\0'; + } else { + /* No newline; check whether we overflowed the buffer */ + if (!feof(stream)) + log_warn(LD_GENERAL, + "Line from port forwarding helper was truncated: %s", buf); + /* TODO: What to do with this error? */ + } + + /* Check if buf starts with SPAWN_ERROR_MESSAGE */ + if (strcmpstart(buf, SPAWN_ERROR_MESSAGE) == 0) { + /* Parse error message */ + int retval, child_state, saved_errno; + retval = tor_sscanf(buf, SPAWN_ERROR_MESSAGE "%x/%x", + &child_state, &saved_errno); + if (retval == 2) { + log_warn(LD_GENERAL, + "Failed to start child process \"%s\" in state %d: %s", + executable, child_state, strerror(saved_errno)); + if (child_status) + *child_status = 1; + } else { + /* Failed to parse message from child process, log it as a + warning */ + log_warn(LD_GENERAL, + "Unexpected message from port forwarding helper \"%s\": %s", + executable, buf); + } + } else { + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf); + } + } + } + + /* We should never get here */ + return -1; +} + +void +tor_check_port_forwarding(const char *filename, int dir_port, int or_port, + time_t now) +{ +#ifdef MS_WINDOWS + (void) filename; (void) dir_port; (void) or_port; (void) now; + (void) tor_spawn_background; + (void) log_from_pipe; + log_warn(LD_GENERAL, "Sorry, port forwarding is not yet supported " + "on windows."); +#else +/* When fw-helper succeeds, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_SUCCESS 300 +/* When fw-helper fails, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_FAIL 60 + + static int child_pid = -1; + static FILE *stdout_read = NULL; + static FILE *stderr_read = NULL; + static time_t time_to_run_helper = 0; + int stdout_status, stderr_status, retval; + const char *argv[10]; + char s_dirport[6], s_orport[6]; + + tor_assert(filename); + + /* Set up command line for tor-fw-helper */ + snprintf(s_dirport, sizeof s_dirport, "%d", dir_port); + snprintf(s_orport, sizeof s_orport, "%d", or_port); + + /* TODO: Allow different internal and external ports */ + argv[0] = filename; + argv[1] = "--internal-or-port"; + argv[2] = s_orport; + argv[3] = "--external-or-port"; + argv[4] = s_orport; + argv[5] = "--internal-dir-port"; + argv[6] = s_dirport; + argv[7] = "--external-dir-port"; + argv[8] = s_dirport; + argv[9] = NULL; + + /* Start the child, if it is not already running */ + if (-1 == child_pid && + time_to_run_helper < now) { + int fd_out=-1, fd_err=-1; + + /* Assume tor-fw-helper will succeed, start it later*/ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS; + + child_pid = tor_spawn_background(filename, &fd_out, &fd_err, argv); + if (child_pid < 0) { + log_warn(LD_GENERAL, "Failed to start port forwarding helper %s", + filename); + child_pid = -1; + return; + } + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(fd_out, F_SETFL, O_NONBLOCK); + fcntl(fd_err, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + stdout_read = fdopen(fd_out, "r"); + stderr_read = fdopen(fd_err, "r"); + + log_info(LD_GENERAL, + "Started port forwarding helper (%s) with pid %d", filename, child_pid); + } + + /* If child is running, read from its stdout and stderr) */ + if (child_pid > 0) { + /* Read from stdout/stderr and log result */ + retval = 0; + stdout_status = log_from_pipe(stdout_read, LOG_INFO, filename, &retval); + stderr_status = log_from_pipe(stderr_read, LOG_WARN, filename, &retval); + if (retval) { + /* There was a problem in the child process */ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; + } + + /* Combine the two statuses in order of severity */ + if (-1 == stdout_status || -1 == stderr_status) + /* There was a failure */ + retval = -1; + else if (1 == stdout_status || 1 == stderr_status) + /* stdout or stderr was closed */ + retval = 1; + else + /* Both are fine */ + retval = 0; + + /* If either pipe indicates a failure, act on it */ + if (0 != retval) { + if (1 == retval) { + log_info(LD_GENERAL, "Port forwarding helper terminated"); + } else { + log_warn(LD_GENERAL, "Failed to read from port forwarding helper"); + } + + /* TODO: The child might not actually be finished (maybe it failed or + closed stdout/stderr), so maybe we shouldn't start another? */ + child_pid = -1; + } + } +#endif +} + diff --git a/src/common/util.h b/src/common/util.h index d657db674e..2974ab7538 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -336,10 +336,27 @@ void start_daemon(void); void finish_daemon(const char *desired_cwd); void write_pidfile(char *filename); +/* Port forwarding */ +void tor_check_port_forwarding(const char *filename, + int dir_port, int or_port, time_t now); + #ifdef MS_WINDOWS HANDLE load_windows_system_library(const TCHAR *library_name); #endif +#ifdef UTIL_PRIVATE +/* Prototypes for private functions only used by util.c (and unit tests) */ +int tor_spawn_background(const char *const filename, int *stdout_read, + int *stderr_read, const char **argv); +void format_helper_exit_status(unsigned char child_state, + int saved_errno, char *hex_errno); + +/* Space for hex values of child state, a slash, saved_errno (with + leading minus) and newline (no null) */ +#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \ + 1 + sizeof(int) * 2 + 1) +#endif + const char *libor_get_digests(void); #endif |