diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/Makefile.am | 46 | ||||
-rw-r--r-- | src/common/Makefile.nmake | 20 | ||||
-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 | 13 | ||||
-rw-r--r-- | src/common/compat_libevent.c | 98 | ||||
-rw-r--r-- | src/common/compat_libevent.h | 22 | ||||
-rw-r--r-- | src/common/container.c | 1 | ||||
-rw-r--r-- | src/common/crypto.c | 10 | ||||
-rw-r--r-- | src/common/crypto.h | 3 | ||||
-rw-r--r-- | src/common/log.c | 31 | ||||
-rw-r--r-- | src/common/mempool.c | 2 | ||||
-rw-r--r-- | src/common/torgzip.c | 4 | ||||
-rw-r--r-- | src/common/torint.h | 9 | ||||
-rw-r--r-- | src/common/torlog.h | 5 | ||||
-rw-r--r-- | src/common/tortls.c | 431 | ||||
-rw-r--r-- | src/common/tortls.h | 16 | ||||
-rw-r--r-- | src/common/util.c | 1112 | ||||
-rw-r--r-- | src/common/util.h | 67 |
21 files changed, 1875 insertions, 161 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 6952591d67..2244fe58d3 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -1,7 +1,7 @@ noinst_LIBRARIES = libor.a libor-crypto.a libor-event.a -EXTRA_DIST = common_sha1.i sha256.c +EXTRA_DIST = common_sha1.i sha256.c Makefile.nmake #CFLAGS = -Wall -Wpointer-arith -O2 @@ -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/Makefile.nmake b/src/common/Makefile.nmake new file mode 100644 index 0000000000..c8b5988666 --- /dev/null +++ b/src/common/Makefile.nmake @@ -0,0 +1,20 @@ +all: libor.lib libor-crypto.lib libor-event.lib
+
+CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include
+
+LIBOR_OBJECTS = address.obj compat.obj container.obj di_ops.obj \
+ log.obj memarea.obj mempool.obj procmon.obj util.obj \
+ util_codedigest.obj
+
+LIBOR_CRYPTO_OBJECTS = aes.obj crypto.obj torgzip.obj tortls.obj
+
+LIBOR_EVENT_OBJECTS = compat_libevent.obj
+
+libor.lib: $(LIBOR_OBJECTS)
+ lib $(LIBOR_OBJECTS) /out:libor.lib
+
+libor-crypto.lib: $(LIBOR_CRYPTO_OBJECTS)
+ lib $(LIBOR_CRYPTO_OBJECTS) /out:libor-crypto.lib
+
+libor-event.lib: $(LIBOR_EVENT_OBJECTS)
+ lib $(LIBOR_EVENT_OBJECTS) /out:libor-event.lib
diff --git a/src/common/address.c b/src/common/address.c index 7fc7301051..26a59e923e 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); @@ -964,6 +964,19 @@ fmt_addr(const tor_addr_t *addr) return "???"; } +/** 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 80b4907b81..330c432284 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; @@ -1994,6 +2049,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..8e271ba905 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -9,8 +9,12 @@ #include "orconfig.h" #include "torint.h" #ifdef MS_WINDOWS +#ifndef WIN32_WINNT #define WIN32_WINNT 0x400 +#endif +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x400 +#endif #define WIN32_LEAN_AND_MEAN #if defined(_MSC_VER) && (_MSC_VER < 1300) #include <winsock.h> @@ -51,6 +55,8 @@ #include <netinet6/in6.h> #endif +#include <stdio.h> + #if defined (WINCE) #include <fcntl.h> #include <io.h> @@ -367,6 +373,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); @@ -392,7 +401,7 @@ typedef int socklen_t; #ifdef MS_WINDOWS #define tor_socket_t intptr_t -#define SOCKET_OK(s) ((s) != INVALID_SOCKET) +#define SOCKET_OK(s) ((unsigned)(s) != INVALID_SOCKET) #else #define tor_socket_t int #define SOCKET_OK(s) ((s) >= 0) @@ -577,6 +586,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..beae9502da 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -19,6 +19,10 @@ #ifdef HAVE_EVENT2_EVENT_H #include <event2/event.h> +#include <event2/thread.h> +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#endif #else #include <event.h> #endif @@ -163,11 +167,23 @@ struct event_base *the_event_base = NULL; #endif #endif +#ifdef USE_BUFFEREVENTS +static int using_iocp_bufferevents = 0; + +int +tor_libevent_using_iocp_bufferevents(void) +{ + return using_iocp_bufferevents; +} +#endif + /** 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 +193,32 @@ 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) { + evthread_use_windows_threads(); + event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP); + using_iocp_bufferevents = 1; + } +#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 @@ -240,7 +281,7 @@ tor_decode_libevent_version(const char *v) /* Try the new preferred "1.4.11-stable" format. * Also accept "1.4.14b-stable". */ - fields = sscanf(v, "%u.%u.%u%c%c", &major, &minor, &patchlevel, &c, &e); + fields = tor_sscanf(v, "%u.%u.%u%c%c", &major, &minor, &patchlevel, &c, &e); if (fields == 3 || ((fields == 4 || fields == 5 ) && (c == '-' || c == '_')) || (fields == 5 && TOR_ISALPHA(c) && (e == '-' || e == '_'))) { @@ -248,7 +289,7 @@ tor_decode_libevent_version(const char *v) } /* Try the old "1.3e" format. */ - fields = sscanf(v, "%u.%u%c%c", &major, &minor, &c, &extra); + fields = tor_sscanf(v, "%u.%u%c%c", &major, &minor, &c, &extra); if (fields == 3 && TOR_ISALPHA(c)) { return V_OLD(major, minor, c); } else if (fields == 2) { @@ -552,3 +593,52 @@ 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; +} + +static struct bufferevent * +tor_get_root_bufferevent(struct bufferevent *bev) +{ + struct bufferevent *u; + while ((u = bufferevent_get_underlying(bev)) != NULL) + bev = u; + return bev; +} + +int +tor_set_bufferevent_rate_limit(struct bufferevent *bev, + struct ev_token_bucket_cfg *cfg) +{ + return bufferevent_set_rate_limit(tor_get_root_bufferevent(bev), cfg); +} + +int +tor_add_bufferevent_to_rate_limit_group(struct bufferevent *bev, + struct bufferevent_rate_limit_group *g) +{ + return bufferevent_add_to_rate_limit_group(tor_get_root_bufferevent(bev), g); +} +#endif + diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 89b256396b..15b0fc273b 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -8,6 +8,11 @@ struct event; struct event_base; +#ifdef USE_BUFFEREVENTS +struct bufferevent; +struct ev_token_bucket_cfg; +struct bufferevent_rate_limit_group; +#endif #ifdef HAVE_EVENT2_EVENT_H #include <event2/util.h> @@ -54,7 +59,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 +72,15 @@ 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); +int tor_libevent_using_iocp_bufferevents(void); +int tor_set_bufferevent_rate_limit(struct bufferevent *bev, + struct ev_token_bucket_cfg *cfg); +int tor_add_bufferevent_to_rate_limit_group(struct bufferevent *bev, + struct bufferevent_rate_limit_group *g); +#endif + #endif diff --git a/src/common/container.c b/src/common/container.c index 1515c387ad..92bfd2ec89 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 851f11bf3b..9ad7575a7e 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -13,8 +13,12 @@ #include "orconfig.h" #ifdef MS_WINDOWS +#ifndef WIN32_WINNT #define WIN32_WINNT 0x400 +#endif +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x400 +#endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <wincrypt.h> @@ -948,7 +952,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) { @@ -1133,8 +1137,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 1a8c81f837..9b4eee622b 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -245,7 +245,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..97400623e5 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) @@ -880,7 +899,7 @@ log_level_to_string(int level) static const char *domain_list[] = { "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", - "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", NULL + "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", NULL }; /** Return a bitmask for the log domain for which <b>domain</b> is the name, 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/torgzip.c b/src/common/torgzip.c index 2937c67de2..ae7d7cfc09 100644 --- a/src/common/torgzip.c +++ b/src/common/torgzip.c @@ -43,11 +43,7 @@ #define off64_t int64_t #endif -#ifdef _MSC_VER -#include "..\..\contrib\zlib\zlib.h" -#else #include <zlib.h> -#endif /** Set to 1 if zlib is a version that supports gzip; set to 0 if it doesn't; * set to -1 if we haven't checked yet. */ diff --git a/src/common/torint.h b/src/common/torint.h index 0b5c29adc0..af975471f1 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -111,6 +111,15 @@ typedef signed int int32_t; typedef unsigned int uint32_t; #define HAVE_UINT32_T #endif +#ifndef UINT16_MAX +#define UINT16_MAX 0xffffu +#endif +#ifndef INT16_MAX +#define INT16_MAX 0x7fff +#endif +#ifndef INT16_MIN +#define INT16_MIN (-INT16_MAX-1) +#endif #ifndef UINT32_MAX #define UINT32_MAX 0xffffffffu #endif 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..1bb9c74efa 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -22,8 +22,12 @@ #include <assert.h> #ifdef MS_WINDOWS /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/ + #ifndef WIN32_WINNT #define WIN32_WINNT 0x400 + #endif + #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x400 + #endif #define WIN32_LEAN_AND_MEAN #if defined(_MSC_VER) && (_MSC_VER < 1300) #include <winsock.h> @@ -44,14 +48,21 @@ #error "We require OpenSSL >= 0.9.7" #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent_ssl.h> +#include <event2/buffer.h> +#include <event2/event.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 +108,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 +122,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 +131,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 +159,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 +192,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 +203,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 +227,96 @@ ssl_state_to_string(int ssl_state) return buf; } +/** Write a description of the current state of <b>tls</b> into the + * <b>sz</b>-byte buffer at <b>buf</b>. */ +void +tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) +{ + const char *ssl_state; + const char *tortls_state; + + if (PREDICT_UNLIKELY(!tls || !tls->ssl)) { + strlcpy(buf, "(No SSL object)", sz); + return; + } + + ssl_state = ssl_state_to_string(tls->ssl->state); + switch (tls->state) { +#define CASE(st) case TOR_TLS_ST_##st: tortls_state = " in "#st ; break + CASE(HANDSHAKE); + CASE(OPEN); + CASE(GOTCLOSE); + CASE(SENTCLOSE); + CASE(CLOSED); + CASE(RENEGOTIATE); +#undef CASE + case TOR_TLS_ST_BUFFEREVENT: + tortls_state = ""; + break; + default: + tortls_state = " in unknown TLS state"; + break; + } + + tor_snprintf(buf, sz, "%s%s", ssl_state, tortls_state); +} + +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; + + /* Some errors are known-benign, meaning they are the fault of the other + * side of the connection. The caller doesn't know this, so override the + * priority for those cases. */ + switch (ERR_GET_REASON(err)) { + case SSL_R_HTTP_REQUEST: + case SSL_R_HTTPS_PROXY_REQUEST: + case SSL_R_RECORD_LENGTH_MISMATCH: + case SSL_R_RECORD_TOO_LARGE: + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_UNSUPPORTED_PROTOCOL: + severity = LOG_INFO; + break; + default: + break; + } + + 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 +491,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 +511,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); @@ -892,6 +954,13 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl, const char *address) return 1; } +static void +tor_tls_debug_state_callback(const SSL *ssl, int type, int val) +{ + log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", + ssl, ssl_state_to_string(ssl->state), type, val); +} + /** Invoked when we're accepting a connection on <b>ssl</b>, and the connection * changes state. We use this: * <ul><li>To alter the state of the handshake partway through, so we @@ -903,6 +972,9 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) { tor_tls_t *tls; (void) val; + + tor_tls_debug_state_callback(ssl, type, val); + if (type != SSL_CB_ACCEPT_LOOP) return; if (ssl->state != SSL3_ST_SW_SRVR_HELLO_A) @@ -913,8 +985,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 +1006,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 +1097,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 +1138,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; @@ -1074,8 +1161,11 @@ tor_tls_new(int sock, int isServer) #ifdef V2_HANDSHAKE_SERVER if (isServer) { SSL_set_info_callback(result->ssl, tor_tls_server_info_callback); - } + } else #endif + { + SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback); + } /* Not expected to get called. */ tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object"); @@ -1109,7 +1199,7 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls, if (cb) { SSL_set_info_callback(tls->ssl, tor_tls_server_info_callback); } else { - SSL_set_info_callback(tls->ssl, NULL); + SSL_set_info_callback(tls->ssl, tor_tls_debug_state_callback); } #endif } @@ -1117,7 +1207,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 +1231,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 +1259,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 +1271,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 +1384,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 +1680,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 +1831,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 +1869,75 @@ 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 || tor_libevent_using_iocp_bufferevents()) { + /* 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); + /* Tell the underlying bufferevent when to accept more data from the SSL + filter (only when it's got less than 32K to write), and when to notify + the SSL filter that it could write more (when it drops under 24K). */ + bufferevent_setwatermark(bufev_in, EV_WRITE, 24*1024, 32*1024); + } 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..9b8108b42b 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -48,6 +48,7 @@ typedef struct tor_tls_t tor_tls_t; #define TOR_TLS_IS_ERROR(rv) ((rv) < TOR_TLS_CLOSE) const char *tor_tls_err_to_string(int err); +void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz); void tor_tls_free_all(void); int tor_tls_context_init(int is_public_server, @@ -67,8 +68,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 +85,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 ee0acbbb07..db6b00f085 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -14,6 +14,10 @@ #define _GNU_SOURCE #include "orconfig.h" +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#define UTIL_PRIVATE #include "util.h" #include "torlog.h" #undef log @@ -67,9 +71,6 @@ #ifdef HAVE_SYS_FCNTL_H #include <sys/fcntl.h> #endif -#ifdef HAVE_FCNTL_H -#include <fcntl.h> -#endif #ifdef HAVE_TIME_H #include <time.h> #endif @@ -87,6 +88,9 @@ #ifdef HAVE_MALLOC_NP_H #include <malloc_np.h> #endif +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif /* ===== * Memory management @@ -415,6 +419,32 @@ round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor) return number; } +/** Return the number of bits set in <b>v</b>. */ +int +n_bits_set_u8(uint8_t v) +{ + static const int nybble_table[] = { + 0, /* 0000 */ + 1, /* 0001 */ + 1, /* 0010 */ + 2, /* 0011 */ + 1, /* 0100 */ + 2, /* 0101 */ + 2, /* 0110 */ + 3, /* 0111 */ + 1, /* 1000 */ + 2, /* 1001 */ + 2, /* 1010 */ + 3, /* 1011 */ + 2, /* 1100 */ + 3, /* 1101 */ + 3, /* 1110 */ + 4, /* 1111 */ + }; + + return nybble_table[v & 15] + nybble_table[v>>4]; +} + /* ===== * String manipulation * ===== */ @@ -498,6 +528,23 @@ tor_strisnonupper(const char *s) return 1; } +/** As strcmp, except that either string may be NULL. The NULL string is + * considered to be before any non-NULL string. */ +int +strcmp_opt(const char *s1, const char *s2) +{ + if (!s1) { + if (!s2) + return 0; + else + return -1; + } else if (!s2) { + return 1; + } else { + return strcmp(s1, s2); + } +} + /** Compares the first strlen(s2) characters of s1 with s2. Returns as for * strcmp. */ @@ -956,7 +1003,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) { @@ -1696,6 +1743,8 @@ check_private_dir(const char *dirname, cpd_check_t check, struct passwd *pw = NULL; uid_t running_uid; gid_t running_gid; +#else + (void)effective_user; #endif tor_assert(dirname); @@ -1895,7 +1944,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)); @@ -2116,7 +2165,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; @@ -2502,18 +2551,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; @@ -2575,11 +2627,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; @@ -2616,9 +2669,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 @@ -2635,6 +2688,30 @@ tor_sscanf(const char *buf, const char *pattern, ...) return r; } +/** Append the string produced by tor_asprintf(<b>pattern</b>, <b>...</b>) + * to <b>sl</b>. */ +void +smartlist_asprintf_add(struct smartlist_t *sl, const char *pattern, ...) +{ + va_list ap; + va_start(ap, pattern); + smartlist_vasprintf_add(sl, pattern, ap); + va_end(ap); +} + +/** va_list-based backend of smartlist_asprintf_add. */ +void +smartlist_vasprintf_add(struct smartlist_t *sl, const char *pattern, + va_list args) +{ + char *str = NULL; + + tor_vasprintf(&str, pattern, args); + tor_assert(str != NULL); + + smartlist_add(sl, str); +} + /** Return a new list containing the filenames in the directory <b>dirname</b>. * Return NULL on error or if <b>dirname</b> is not a directory. */ @@ -2812,7 +2889,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); @@ -2883,3 +2960,1006 @@ load_windows_system_library(const TCHAR *library_name) } #endif +/** Format a single argument for being put on a Windows command line. + * Returns a newly allocated string */ +static char * +format_win_cmdline_argument(const char *arg) +{ + char *formatted_arg; + char need_quotes; + const char *c; + int i; + int bs_counter = 0; + /* Backslash we can point to when one is inserted into the string */ + const char backslash = '\\'; + + /* Smartlist of *char */ + smartlist_t *arg_chars; + arg_chars = smartlist_create(); + + /* Quote string if it contains whitespace or is empty */ + need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]); + + /* Build up smartlist of *chars */ + for (c=arg; *c != '\0'; c++) { + if ('"' == *c) { + /* Double up backslashes preceding a quote */ + for (i=0; i<(bs_counter*2); i++) + smartlist_add(arg_chars, (void*)&backslash); + bs_counter = 0; + /* Escape the quote */ + smartlist_add(arg_chars, (void*)&backslash); + smartlist_add(arg_chars, (void*)c); + } else if ('\\' == *c) { + /* Count backslashes until we know whether to double up */ + bs_counter++; + } else { + /* Don't double up slashes preceding a non-quote */ + for (i=0; i<bs_counter; i++) + smartlist_add(arg_chars, (void*)&backslash); + bs_counter = 0; + smartlist_add(arg_chars, (void*)c); + } + } + /* Don't double up trailing backslashes */ + for (i=0; i<bs_counter; i++) + smartlist_add(arg_chars, (void*)&backslash); + + /* Allocate space for argument, quotes (if needed), and terminator */ + formatted_arg = tor_malloc(sizeof(char) * + (smartlist_len(arg_chars) + (need_quotes?2:0) + 1)); + + /* Add leading quote */ + i=0; + if (need_quotes) + formatted_arg[i++] = '"'; + + /* Add characters */ + SMARTLIST_FOREACH(arg_chars, char*, c, + { + formatted_arg[i++] = *c; + }); + + /* Add trailing quote */ + if (need_quotes) + formatted_arg[i++] = '"'; + formatted_arg[i] = '\0'; + + smartlist_free(arg_chars); + return formatted_arg; +} + +/** Format a command line for use on Windows, which takes the command as a + * string rather than string array. Follows the rules from "Parsing C++ + * Command-Line Arguments" in MSDN. Algorithm based on list2cmdline in the + * Python subprocess module. Returns a newly allocated string */ +char * +tor_join_win_cmdline(const char *argv[]) +{ + smartlist_t *argv_list; + char *joined_argv; + int i; + + /* Format each argument and put the result in a smartlist */ + argv_list = smartlist_create(); + for (i=0; argv[i] != NULL; i++) { + smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i])); + } + + /* Join the arguments with whitespace */ + joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL); + + /* Free the newly allocated arguments, and the smartlist */ + SMARTLIST_FOREACH(argv_list, char *, arg, + { + tor_free(arg); + }); + smartlist_free(argv_list); + + return joined_argv; +} + +/** 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, on + * non-Windows systems, the system path will be searched for <b>filename</b>. + * On Windows, only the current directory will be searched. Here, to search the + * system path (as well as the application directory, current working + * directory, and system directories), set filename to NULL. + * + * 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, and this must be the case if <b>filename</b> is + * NULL). The last element of argv must be NULL. A handle to the child process + * will be returned in process_handle (which must be non-NULL). Read + * process_handle.status to find out if the process was successfully launched. + * For convenience, process_handle.status is returned by this function. + * + * Some parts of this code are based on the POSIX subprocess module from + * Python, and example code from + * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx. + */ + +int +tor_spawn_background(const char *const filename, const char **argv, + process_handle_t *process_handle) +{ +#ifdef MS_WINDOWS + HANDLE stdout_pipe_read = NULL; + HANDLE stdout_pipe_write = NULL; + HANDLE stderr_pipe_read = NULL; + HANDLE stderr_pipe_write = NULL; + + STARTUPINFO siStartInfo; + BOOL retval = FALSE; + + SECURITY_ATTRIBUTES saAttr; + char *joined_argv; + + /* process_handle must not be NULL */ + tor_assert(process_handle != NULL); + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + saAttr.lpSecurityDescriptor = NULL; + + /* Assume failure to start process */ + memset(process_handle, 0, sizeof(process_handle_t)); + process_handle->status = PROCESS_STATUS_ERROR; + + /* Set up pipe for stdout */ + if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stdout communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle->status; + } + if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stdout communication with child " + "process: %s", format_win32_error(GetLastError())); + return process_handle->status; + } + + /* Set up pipe for stderr */ + if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stderr communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle->status; + } + if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stderr communication with child " + "process: %s", format_win32_error(GetLastError())); + return process_handle->status; + } + + /* Create the child process */ + + /* Windows expects argv to be a whitespace delimited string, so join argv up + */ + joined_argv = tor_join_win_cmdline(argv); + + ZeroMemory(&(process_handle->pid), sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = stderr_pipe_write; + siStartInfo.hStdOutput = stdout_pipe_write; + siStartInfo.hStdInput = NULL; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* Create the child process */ + + retval = CreateProcess(filename, // module name + joined_argv, // command line + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess() + * work?) */ + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &(process_handle->pid)); // receives PROCESS_INFORMATION + + tor_free(joined_argv); + + if (!retval) { + log_warn(LD_GENERAL, + "Failed to create child process %s: %s", filename?filename:argv[0], + format_win32_error(GetLastError())); + } else { + /* TODO: Close hProcess and hThread in process_handle->pid? */ + process_handle->stdout_pipe = stdout_pipe_read; + process_handle->stderr_pipe = stderr_pipe_read; + process_handle->status = PROCESS_STATUS_RUNNING; + } + + /* TODO: Close pipes on exit */ + + return process_handle->status; +#else // MS_WINDOWS + 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; + + /* Assume failure to start */ + memset(process_handle, 0, sizeof(process_handle_t)); + process_handle->status = PROCESS_STATUS_ERROR; + + /* 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 process_handle->status; + } + + 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 process_handle->status; + } + + 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 */ + /* TODO: Don't use STDOUT, use a pipe set up just for this purpose */ + nbytes = write(STDOUT_FILENO, error_message, error_message_length); + nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno)); + + (void) nbytes; + + _exit(255); + /* Never reached, but avoids compiler warning */ + return process_handle->status; + } + + /* 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 process_handle->status; + } + + process_handle->pid = pid; + + /* TODO: If the child process forked but failed to exec, waitpid it */ + + /* Return read end of the pipes to caller, and close write end */ + process_handle->stdout_pipe = 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)); + } + + process_handle->stderr_pipe = 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)); + } + + process_handle->status = PROCESS_STATUS_RUNNING; + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(process_handle->stdout_pipe, F_SETFL, O_NONBLOCK); + fcntl(process_handle->stderr_pipe, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + process_handle->stdout_handle = fdopen(process_handle->stdout_pipe, "r"); + process_handle->stderr_handle = fdopen(process_handle->stderr_pipe, "r"); + + return process_handle->status; +#endif // MS_WINDOWS +} + +/** Get the exit code of a process specified by <b>process_handle</b> and store + * it in <b>exit_code</b>, if set to a non-NULL value. If <b>block</b> is set + * to true, the call will block until the process has exited. Otherwise if + * the process is still running, the function will return + * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns + * PROCESS_EXIT_EXITED if the process did exit. If there is a failure, + * PROCESS_EXIT_ERROR will be returned and the contents of exit_code (if + * non-NULL) will be undefined. N.B. Under *nix operating systems, this will + * probably not work in Tor, because waitpid() is called in main.c to reap any + * terminated child processes.*/ +int +tor_get_exit_code(const process_handle_t process_handle, + int block, int *exit_code) +{ +#ifdef MS_WINDOWS + DWORD retval; + BOOL success; + + if (block) { + /* Wait for the process to exit */ + retval = WaitForSingleObject(process_handle.pid.hProcess, INFINITE); + if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } else { + retval = WaitForSingleObject(process_handle.pid.hProcess, 0); + if (WAIT_TIMEOUT == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } + + if (exit_code != NULL) { + success = GetExitCodeProcess(process_handle.pid.hProcess, + (PDWORD)exit_code); + if (!success) { + log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s", + format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } +#else + int stat_loc; + int retval; + + retval = waitpid(process_handle.pid, &stat_loc, block?0:WNOHANG); + if (!block && 0 == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != process_handle.pid) { + log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s", process_handle.pid, + strerror(errno)); + return PROCESS_EXIT_ERROR; + } + + if (!WIFEXITED(stat_loc)) { + log_warn(LD_GENERAL, "Process %d did not exit normally", + process_handle.pid); + return PROCESS_EXIT_ERROR; + } + + if (exit_code != NULL) + *exit_code = WEXITSTATUS(stat_loc); +#endif // MS_WINDOWS + + return PROCESS_EXIT_EXITED; +} + +#ifdef MS_WINDOWS +/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>hProcess</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle + * to the process owning the <b>h</b>. In this case, the function will exit + * only once the process has exited, or <b>count</b> bytes are read. Returns + * the number of bytes read, or -1 on error. */ +ssize_t +tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process) +{ + size_t numread = 0; + BOOL retval; + DWORD byte_count; + BOOL process_exited = FALSE; + + if (count > SIZE_T_CEILING || count > SSIZE_T_MAX) + return -1; + + while (numread != count) { + /* Check if there is anything to read */ + retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL); + if (!retval) { + log_warn(LD_GENERAL, + "Failed to peek from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* Nothing available: process exited or it is busy */ + + /* Exit if we don't know whether the process is running */ + if (NULL == process) + break; + + /* The process exited and there's nothing left to read from it */ + if (process_exited) + break; + + /* If process is not running, check for output one more time in case + it wrote something after the peek was performed. Otherwise keep on + waiting for output */ + tor_assert(process != NULL); + byte_count = WaitForSingleObject(process->pid.hProcess, 0); + if (WAIT_TIMEOUT != byte_count) + process_exited = TRUE; + + continue; + } + + /* There is data to read; read it */ + retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL); + tor_assert(byte_count + numread <= count); + if (!retval) { + log_warn(LD_GENERAL, "Failed to read from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* End of file */ + break; + } + numread += byte_count; + } + return (ssize_t)numread; +} +#else +/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>process</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise data will be read until end of file, or + * <b>count</b> bytes are read. Returns the number of bytes read, or -1 on + * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the + * file has been reached. */ +ssize_t +tor_read_all_handle(FILE *h, char *buf, size_t count, + const process_handle_t *process, + int *eof) +{ + size_t numread = 0; + char *retval; + + if (eof) + *eof = 0; + + if (count > SIZE_T_CEILING || count > SSIZE_T_MAX) + return -1; + + while (numread != count) { + /* Use fgets because that is what we use in log_from_pipe() */ + retval = fgets(buf+numread, (int)(count-numread), h); + if (NULL == retval) { + if (feof(h)) { + log_debug(LD_GENERAL, "fgets() reached end of file"); + fclose(h); + if (eof) + *eof = 1; + break; + } else { + if (EAGAIN == errno) { + if (process) + continue; + else + break; + } else { + log_warn(LD_GENERAL, "fgets() from handle failed: %s", + strerror(errno)); + fclose(h); + return -1; + } + } + } + tor_assert(retval != NULL); + tor_assert(strlen(retval) + numread <= count); + numread += strlen(retval); + } + + log_debug(LD_GENERAL, "fgets() read %d bytes from handle", (int)numread); + return (ssize_t)numread; +} +#endif + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stdout(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle->stdout_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stdout_handle, buf, count, + process_handle, NULL); +#endif +} + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stderr(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle->stderr_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stderr_handle, buf, count, + process_handle, NULL); +#endif +} + +/** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be + * modified. The resulting smartlist will consist of pointers to buf, so there + * is no need to free the contents of sl. <b>buf</b> must be a NUL-terminated + * string. <b>len</b> should be set to the length of the buffer excluding the + * NUL. Non-printable characters (including NUL) will be replaced with "." */ +int +tor_split_lines(smartlist_t *sl, char *buf, int len) +{ + /* Index in buf of the start of the current line */ + int start = 0; + /* Index in buf of the current character being processed */ + int cur = 0; + /* Are we currently in a line */ + char in_line = 0; + + /* Loop over string */ + while (cur < len) { + /* Loop until end of line or end of string */ + for (; cur < len; cur++) { + if (in_line) { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* End of line */ + buf[cur] = '\0'; + /* Point cur to the next line */ + cur++; + /* Line starts at start and ends with a nul */ + break; + } else { + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } else { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* Skip leading vertical space */ + ; + } else { + in_line = 1; + start = cur; + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } + } + /* We are at the end of the line or end of string. If in_line is true there + * is a line which starts at buf+start and ends at a NUL. cur points to + * the character after the NUL. */ + if (in_line) + smartlist_add(sl, (void *)(buf+start)); + in_line = 0; + } + return smartlist_len(sl); +} + +#ifdef MS_WINDOWS +/** Read from stream, and send lines to log at the specified log level. + * Returns -1 if there is a error reading, and 0 otherwise. + * If the generated stream is flushed more often than on new lines, or + * a read exceeds 256 bytes, lines will be truncated. This should be fixed, + * along with the corresponding problem on *nix (see bug #2045). + */ +static int +log_from_handle(HANDLE *pipe, int severity) +{ + char buf[256]; + int pos; + smartlist_t *lines; + + pos = tor_read_all_handle(pipe, buf, sizeof(buf) - 1, NULL); + if (pos < 0) { + /* Error */ + log_warn(LD_GENERAL, "Failed to read data from subprocess"); + return -1; + } + + if (0 == pos) { + /* There's nothing to read (process is busy or has exited) */ + log_debug(LD_GENERAL, "Subprocess had nothing to say"); + return 0; + } + + /* End with a null even if there isn't a \r\n at the end */ + /* TODO: What if this is a partial line? */ + buf[pos] = '\0'; + log_debug(LD_GENERAL, "Subprocess had %d bytes to say", pos); + + /* Split up the buffer */ + lines = smartlist_create(); + tor_split_lines(lines, buf, pos); + + /* Log each line */ + SMARTLIST_FOREACH(lines, char *, line, + { + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", line); + }); + smartlist_free(lines); + + return 0; +} + +#else +/** 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; +} +#endif + +void +tor_check_port_forwarding(const char *filename, int dir_port, int or_port, + time_t now) +{ +/* When fw-helper succeeds, how long do we wait until running it again */ +#define TIME_TO_EXEC_FWHELPER_SUCCESS 300 +/* When fw-helper failed to start, how long do we wait until running it again + */ +#define TIME_TO_EXEC_FWHELPER_FAIL 60 + + /* Static variables are initialized to zero, so child_handle.status=0 + * which corresponds to it not running on startup */ + static process_handle_t child_handle; + + 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 (child_handle.status != PROCESS_STATUS_RUNNING && + time_to_run_helper < now) { + /* Assume tor-fw-helper will succeed, start it later*/ + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS; + +#ifdef MS_WINDOWS + /* Passing NULL as lpApplicationName makes Windows search for the .exe */ + tor_spawn_background(NULL, argv, &child_handle); +#else + tor_spawn_background(filename, argv, &child_handle); +#endif + if (PROCESS_STATUS_ERROR == child_handle.status) { + log_warn(LD_GENERAL, "Failed to start port forwarding helper %s", + filename); + time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; + return; + } +#ifdef MS_WINDOWS + log_info(LD_GENERAL, + "Started port forwarding helper (%s)", filename); +#else + log_info(LD_GENERAL, + "Started port forwarding helper (%s) with pid %d", filename, + child_handle.pid); +#endif + } + + /* If child is running, read from its stdout and stderr) */ + if (PROCESS_STATUS_RUNNING == child_handle.status) { + /* Read from stdout/stderr and log result */ + retval = 0; +#ifdef MS_WINDOWS + stdout_status = log_from_handle(child_handle.stdout_pipe, LOG_INFO); + stderr_status = log_from_handle(child_handle.stderr_pipe, LOG_WARN); + /* If we got this far (on Windows), the process started */ + retval = 0; +#else + stdout_status = log_from_pipe(child_handle.stdout_handle, + LOG_INFO, filename, &retval); + stderr_status = log_from_pipe(child_handle.stderr_handle, + LOG_WARN, filename, &retval); +#endif + 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; +#ifdef MS_WINDOWS + else if (tor_get_exit_code(child_handle, 0, NULL) != + PROCESS_EXIT_RUNNING) { + /* process has exited or there was an error */ + /* TODO: Do something with the process return value */ + /* TODO: What if the process output something since + * between log_from_handle and tor_get_exit_code? */ + retval = 1; + } +#else + else if (1 == stdout_status || 1 == stderr_status) + /* stdout or stderr was closed, the process probably + * exited. It will be reaped by waitpid() in main.c */ + /* TODO: Do something with the process return value */ + retval = 1; +#endif + 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"); + child_handle.status = PROCESS_STATUS_NOTRUNNING; + } else { + log_warn(LD_GENERAL, "Failed to read from port forwarding helper"); + child_handle.status = PROCESS_STATUS_ERROR; + } + + /* TODO: The child might not actually be finished (maybe it failed or + closed stdout/stderr), so maybe we shouldn't start another? */ + } + } +} + diff --git a/src/common/util.h b/src/common/util.h index b9db25ca73..c8cce39f3c 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -160,6 +160,7 @@ uint64_t round_to_power_of_2(uint64_t u64); unsigned round_to_next_multiple_of(unsigned number, unsigned divisor); uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor); uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor); +int n_bits_set_u8(uint8_t v); /* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b> * and positive <b>b</b>. Works on integer types only. Not defined if a+b can @@ -174,6 +175,7 @@ void tor_strlower(char *s) ATTR_NONNULL((1)); void tor_strupper(char *s) ATTR_NONNULL((1)); int tor_strisprint(const char *s) ATTR_PURE ATTR_NONNULL((1)); int tor_strisnonupper(const char *s) ATTR_PURE ATTR_NONNULL((1)); +int strcmp_opt(const char *s1, const char *s2) ATTR_PURE; int strcmpstart(const char *s1, const char *s2) ATTR_PURE ATTR_NONNULL((1,2)); int strcmp_len(const char *s1, const char *s2, size_t len) ATTR_PURE ATTR_NONNULL((1,2)); @@ -218,6 +220,11 @@ int tor_sscanf(const char *buf, const char *pattern, ...) #endif ; +void smartlist_asprintf_add(struct smartlist_t *sl, const char *pattern, ...) + CHECK_PRINTF(2, 3); +void smartlist_vasprintf_add(struct smartlist_t *sl, const char *pattern, + va_list args); + int hex_decode_digit(char c); void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen); int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen); @@ -337,10 +344,70 @@ 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) */ + +/* Values of process_handle_t.status. PROCESS_STATUS_NOTRUNNING must be + * 0 because tor_check_port_forwarding depends on this being the initial + * statue of the static instance of process_handle_t */ +#define PROCESS_STATUS_NOTRUNNING 0 +#define PROCESS_STATUS_RUNNING 1 +#define PROCESS_STATUS_ERROR -1 +typedef struct process_handle_s { + int status; +#ifdef MS_WINDOWS + HANDLE stdout_pipe; + HANDLE stderr_pipe; + PROCESS_INFORMATION pid; +#else + int stdout_pipe; + int stderr_pipe; + FILE *stdout_handle; + FILE *stderr_handle; + pid_t pid; +#endif // MS_WINDOWS +} process_handle_t; + +int tor_spawn_background(const char *const filename, const char **argv, + process_handle_t *process_handle); + +/* Return values of tor_get_exit_code() */ +#define PROCESS_EXIT_RUNNING 1 +#define PROCESS_EXIT_EXITED 0 +#define PROCESS_EXIT_ERROR -1 +int tor_get_exit_code(const process_handle_t process_handle, + int block, int *exit_code); +int tor_split_lines(struct smartlist_t *sl, char *buf, int len); +#ifdef MS_WINDOWS +ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process); +#else +ssize_t tor_read_all_handle(FILE *h, char *buf, size_t count, + const process_handle_t *process, + int *eof); +#endif +ssize_t tor_read_all_from_process_stdout( + const process_handle_t *process_handle, char *buf, size_t count); +ssize_t tor_read_all_from_process_stderr( + const process_handle_t *process_handle, char *buf, size_t count); +char *tor_join_win_cmdline(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 |