aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/compat.c46
-rw-r--r--src/common/compat.h2
-rw-r--r--src/common/compat_libevent.c46
-rw-r--r--src/common/compat_libevent.h15
-rw-r--r--src/common/crypto.h3
-rw-r--r--src/common/tortls.c179
-rw-r--r--src/common/tortls.h9
-rw-r--r--src/common/util.c409
-rw-r--r--src/common/util.h15
9 files changed, 678 insertions, 46 deletions
diff --git a/src/common/compat.c b/src/common/compat.c
index 20394b4c5..b7f4f17ca 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -1898,6 +1898,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 7d59501e2..5b6cfc40b 100644
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@ -522,6 +522,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 250fa2bdb..d94f615f7 100644
--- a/src/common/compat_libevent.c
+++ b/src/common/compat_libevent.c
@@ -159,9 +159,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 ||
@@ -171,7 +173,21 @@ 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
+
+ the_event_base = event_base_new_with_config(cfg);
+ }
#else
the_event_base = event_init();
#endif
@@ -551,3 +567,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 fdf5e0a18..ecf25806d 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>
@@ -53,7 +56,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,
@@ -61,5 +69,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/crypto.h b/src/common/crypto.h
index a30e5bcba..c433938d5 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -238,7 +238,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/tortls.c b/src/common/tortls.c
index 99f3f4abb..7568a6b47 100644
--- a/src/common/tortls.c
+++ b/src/common/tortls.c
@@ -44,7 +44,14 @@
#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"
@@ -109,6 +116,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 */
@@ -189,7 +197,6 @@ 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);
/** Global tls context. We keep it here because nobody else needs to
* touch it. */
@@ -1026,7 +1033,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
@@ -1194,56 +1201,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.
@@ -1460,6 +1497,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");
@@ -1631,3 +1670,59 @@ 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)
+{
+ struct bufferevent *out;
+ const enum bufferevent_ssl_state state = receiving ?
+ BUFFEREVENT_SSL_ACCEPTING : BUFFEREVENT_SSL_CONNECTING;
+
+#if 0
+ (void) socket;
+ out = bufferevent_openssl_filter_new(tor_libevent_get_base(),
+ bufev_in,
+ tls->ssl,
+ state,
+ BEV_OPT_DEFER_CALLBACKS);
+#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);
+ }
+ tls->state = TOR_TLS_ST_BUFFEREVENT;
+
+ /* Current versions (as of 2.0.7-rc) 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);
+#endif
+ return out;
+}
+#endif
+
diff --git a/src/common/tortls.h b/src/common/tortls.h
index e4b1ad65f..f8603b529 100644
--- a/src/common/tortls.h
+++ b/src/common/tortls.h
@@ -64,7 +64,9 @@ 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);
int tor_tls_shutdown(tor_tls_t *tls);
int tor_tls_get_pending_bytes(tor_tls_t *tls);
@@ -85,5 +87,12 @@ int tor_tls_used_v1_handshake(tor_tls_t *tls);
void _check_no_tls_errors(const char *fname, int line);
+#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);
+#endif
+
#endif
diff --git a/src/common/util.c b/src/common/util.c
index dafe12071..b5a3ade2b 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
@@ -2878,3 +2879,411 @@ load_windows_system_library(const TCHAR *library_name)
}
#endif
+/** Format child_state and saved_errno as a hex string placed in hex_errno.
+ * Called between fork and _exit, so must be signal-handler safe */
+void
+format_helper_exit_status(unsigned char child_state, int saved_errno,
+ char *hex_errno)
+{
+ /* Convert errno to be unsigned for hex conversion */
+ unsigned int unsigned_errno;
+ char *cur;
+
+ /* If errno is negative, negate it */
+ 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;
+
+ do {
+ *cur-- = "0123456789ABCDEF"[unsigned_errno % 16];
+ unsigned_errno /= 16;
+ } while (unsigned_errno != 0 && cur >= hex_errno);
+
+ /* Add on the minus side if errno was negative */
+ if (saved_errno < 0)
+ *cur-- = '-';
+
+ /* Leave a gap */
+ *cur-- = '/';
+
+ /* 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>. Returns pid on success, otherwise
+ * returns -1.
+ * Some parts of this code are based on the POSIX subprocess module from Python
+ */
+static 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);
+
+ /* Fill hex_errno with spaces, and a trailing newline */
+ memset(hex_errno, ' ', sizeof(hex_errno) - 1);
+ hex_errno[sizeof(hex_errno) - 1] = '\n';
+
+ child_state = CHILD_STATE_PIPE;
+
+ /* Set up pipe for redirecting stdout and stderr of child */
+ retval = pipe(stdout_pipe);
+ if (-1 == retval) {
+ log_err(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_err(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);
+ if (fd != -1)
+ dup2(STDIN_FILENO, fd);
+ else
+ goto error;
+
+ child_state = CHILD_STATE_CLOSEFD;
+
+ /* Close all other fds, including the read end of the pipe */
+ /* TODO: use closefrom if available */
+ 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:
+ /* TODO: 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));
+
+ _exit(255);
+ return -1; /* Never reached, but avoids compiler warning */
+ }
+
+ /* In parent */
+
+ if (-1 == pid) {
+ log_err(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_err(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_err(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_err(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 (strstr(buf, SPAWN_ERROR_MESSAGE) == buf) {
+ /* Parse error message */
+ int retval, child_state, saved_errno;
+ retval = sscanf(buf, SPAWN_ERROR_MESSAGE "%d/%d",
+ &child_state, &saved_errno);
+ if (retval == 2) {
+ log_err(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 error */
+ log_err(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_err(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_ERR, 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_err(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 833fd904c..86555eeb1 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -340,10 +340,25 @@ 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) */
+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