From 709c21c48ca541c75bf803e6801c335d8ae3d043 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 28 Jul 2009 04:03:57 +0000 Subject: [PATCH] Bufferevent support for openssl. This code adds a new Bufferevent type that is only compiled when the openssl library is present. It supports using an SSL object and an event alert mechanism, which can either be an fd or an underlying bufferevent. There is still more work to do: the unit tests are incomplete, and we need to support flush and shutdown much better. Sometimes events are generated needlessly: this will hose performance. There's a new encrypting proxy in sample/le-proxy.c. This code has only been tested on OSX, and nowhere else. svn:r1382 --- ChangeLog | 3 + Makefile.am | 9 + bufferevent-internal.h | 5 + bufferevent.c | 24 +- bufferevent_filter.c | 7 +- bufferevent_openssl.c | 1138 ++++++++++++++++++++++++++++++ bufferevent_sock.c | 48 +- configure.in | 23 +- evutil.c | 32 + include/Makefile.am | 44 +- include/event2/bufferevent_ssl.h | 70 ++ sample/Makefile.am | 6 + sample/le-proxy.c | 210 ++++++ test/Makefile.am | 6 + test/regress.h | 1 + test/regress_main.c | 3 + test/regress_ssl.c | 278 ++++++++ util-internal.h | 3 + 18 files changed, 1850 insertions(+), 60 deletions(-) create mode 100644 bufferevent_openssl.c create mode 100644 include/event2/bufferevent_ssl.h create mode 100644 sample/le-proxy.c create mode 100644 test/regress_ssl.c diff --git a/ChangeLog b/ChangeLog index 83a3c590..85141a6d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +Changes in 2.0.3-alpha: + o Add a new code to support SSL/TLS on bufferevents, using the OpenSSL library (where available). + Changes in 2.0.2-alpha: o Add a new flag to bufferevents to make all callbacks automatically deferred. o Make evdns functionality locked, and automatically defer dns callbacks. diff --git a/Makefile.am b/Makefile.am index ae7584bf..fd5179a7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,6 +67,9 @@ lib_LTLIBRARIES = libevent.la libevent_core.la libevent_extra.la if PTHREADS lib_LTLIBRARIES += libevent_pthreads.la endif +if OPENSSL +lib_LTLIBRARIES += libevent_openssl.la +endif SUBDIRS = . include sample test @@ -128,6 +131,12 @@ libevent_extra_la_SOURCES = $(EXTRA_SRC) libevent_extra_la_LIBADD = libevent_extra_la_LDFLAGS = -version-info $(VERSION_INFO) +if OPENSSL +libevent_openssl_la_SOURCES = bufferevent_openssl.c +libevent_openssl_la_LIBADD = -lcrypto -lssl +libevent_openssl_la_LDFLAGS = -release $(RELEASE) -version-info $(VERSION_INFO) +endif + noinst_HEADERS = util-internal.h mm-internal.h ipv6-internal.h \ evrpc-internal.h strlcpy-internal.h evbuffer-internal.h \ bufferevent-internal.h http-internal.h event-internal.h \ diff --git a/bufferevent-internal.h b/bufferevent-internal.h index 3a3b0f7c..a0747002 100644 --- a/bufferevent-internal.h +++ b/bufferevent-internal.h @@ -173,6 +173,10 @@ void _bufferevent_run_writecb(struct bufferevent *bufev); * it to run with events "what". Otherwise just run the eventcb. */ void _bufferevent_run_eventcb(struct bufferevent *bufev, short what); +/** Internal: Add the event 'ev' with timeout tv, unless tv is set to 0, in + * which case add ev with no timeout. */ +int _bufferevent_add_event(struct event *ev, const struct timeval *tv); + /* ========= * These next functions implement timeouts for bufferevents that aren't doing * anything else with ev_read and ev_write, to handle timeouts. @@ -227,4 +231,5 @@ void _bufferevent_generic_adj_timeouts(struct bufferevent *bev); } #endif + #endif /* _BUFFEREVENT_INTERNAL_H_ */ diff --git a/bufferevent.c b/bufferevent.c index f79ff540..6d9f67ec 100644 --- a/bufferevent.c +++ b/bufferevent.c @@ -325,7 +325,7 @@ bufferevent_enable(struct bufferevent *bufev, short event) short impl_events = event; int r = 0; - BEV_LOCK(bufev); + _bufferevent_incref_and_lock(bufev); if (bufev_private->read_suspended) impl_events &= ~EV_READ; @@ -334,7 +334,7 @@ bufferevent_enable(struct bufferevent *bufev, short event) if (bufev->be_ops->enable(bufev, impl_events) < 0) r = -1; - BEV_UNLOCK(bufev); + _bufferevent_decref_and_unlock(bufev); return r; } @@ -525,10 +525,17 @@ bufferevent_enable_locking(struct bufferevent *bufev, void *lock) #ifdef _EVENT_DISABLE_THREAD_SUPPORT return -1; #else + struct bufferevent *underlying; + if (BEV_UPCAST(bufev)->lock) return -1; + underlying = bufferevent_get_underlying(bufev); - if (!lock) { + if (!lock && underlying && BEV_UPCAST(underlying)->lock) { + lock = BEV_UPCAST(underlying)->lock; + BEV_UPCAST(bufev)->lock = lock; + BEV_UPCAST(bufev)->own_lock = 0; + } else if (!lock) { EVTHREAD_ALLOC_LOCK(lock); if (!lock) return -1; @@ -541,6 +548,9 @@ bufferevent_enable_locking(struct bufferevent *bufev, void *lock) evbuffer_enable_locking(bufev->input, lock); evbuffer_enable_locking(bufev->output, lock); + if (underlying && !BEV_UPCAST(underlying)->lock) + bufferevent_enable_locking(underlying, lock); + return 0; #endif } @@ -632,3 +642,11 @@ _bufferevent_generic_adj_timeouts(struct bufferevent *bev) event_del(&bev->ev_write); } +int +_bufferevent_add_event(struct event *ev, const struct timeval *tv) +{ + if (tv->tv_sec == 0 && tv->tv_usec == 0) + return event_add(ev, NULL); + else + return event_add(ev, tv); +} diff --git a/bufferevent_filter.c b/bufferevent_filter.c index a77a9524..5da8aaf6 100644 --- a/bufferevent_filter.c +++ b/bufferevent_filter.c @@ -187,12 +187,7 @@ bufferevent_filter_new(struct bufferevent *underlying, return NULL; } if (options & BEV_OPT_THREADSAFE) { - void *lock = BEV_UPCAST(underlying)->lock; - if (!lock) { - bufferevent_enable_locking(underlying, NULL); - lock = BEV_UPCAST(underlying)->lock; - } - bufferevent_enable_locking(downcast(bufev_f), lock); + bufferevent_enable_locking(downcast(bufev_f), NULL); } bufev_f->underlying = underlying; diff --git a/bufferevent_openssl.c b/bufferevent_openssl.c new file mode 100644 index 00000000..b856cacf --- /dev/null +++ b/bufferevent_openssl.c @@ -0,0 +1,1138 @@ +/* + * Copyright (c) 2009 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "event-config.h" +#endif + +#ifdef _EVENT_HAVE_SYS_TIME_H +#include +#endif + +#include +#include +#include +#include +#include +#ifdef _EVENT_HAVE_STDARG_H +#include +#endif +#ifdef _EVENT_HAVE_UNISTD_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include "event2/bufferevent.h" +#include "event2/bufferevent_struct.h" +#include "event2/bufferevent_ssl.h" +#include "event2/buffer.h" +#include "event2/event.h" + +#include "mm-internal.h" +#include "bufferevent-internal.h" +#include "log-internal.h" + +#include +#include +#include + +/* + * Define an OpenSSL bio that targets a bufferevent. + */ + +/* -------------------- + A BIO is an OpenSSL abstraction that handles reading and writing data. The + library will happily speak SSL over anything that implements a BIO + interface. + + Here we define a BIO implementation that directs its output to a + bufferevent. We'll want to use this only when none of OpenSSL's built-in + IO mechinsms work for us. + -------------------- */ + +/* every BIO type needs its own integer type value. */ +#define BIO_TYPE_LIBEVENT 57 +/* ???? Arguably, we should set BIO_TYPE_FILTER or BIO_TYPE_SOURCE_SINK on + * this. */ + +#if 0 +static void +print_err(int val) +{ + int err; + printf("Error was %d\n", val); + + while ((err = ERR_get_error())) { + const char *msg = (const char*)ERR_reason_error_string(err); + const char *lib = (const char*)ERR_lib_error_string(err); + const char *func = (const char*)ERR_func_error_string(err); + + printf("%s in %s %s\n", msg, lib, func); + } +} +#else +#define print_err(v) ((void)0) +#endif + +/* Called to initialize a new BIO */ +static int +bio_bufferevent_new(BIO *b) +{ + b->init = 0; + b->num = -1; + b->ptr = NULL; /* We'll be putting the bufferevent in this field.*/ + b->flags = 0; + return 1; +} + +/* Called to uninitialize the BIO. */ +static int +bio_bufferevent_free(BIO *b) +{ + if (!b) + return 0; + if (b->shutdown) { + if (b->init && b->ptr) + bufferevent_free(b->ptr); + b->init = 0; + b->flags = 0; + b->ptr = NULL; + } + return 1; +} + +/* Called to extract data from the BIO. */ +static int +bio_bufferevent_read(BIO *b, char *out, int outlen) +{ + int r = 0; + struct evbuffer *input; + + BIO_clear_retry_flags(b); + + if (!out) + return 0; + if (!b->ptr) + return -1; + + input = bufferevent_get_input(b->ptr); + if (evbuffer_get_length(input) == 0) { + /* If there's no data to read, say so. */ + BIO_set_retry_read(b); + return -1; + } else { + r = evbuffer_remove(input, out, outlen); + } + + return r; +} + +/* Called to write data info the BIO */ +static int +bio_bufferevent_write(BIO *b, const char *in, int inlen) +{ + struct bufferevent *bufev = b->ptr; + struct evbuffer *output; + size_t outlen; + + BIO_clear_retry_flags(b); + + if (!b->ptr) + return -1; + + output = bufferevent_get_output(bufev); + outlen = evbuffer_get_length(output); + + /* Copy only as much data onto the output buffer as can fit under the + * high-water mark. */ + if (bufev->wm_write.high && bufev->wm_write.high >= (outlen+inlen)) { + if (bufev->wm_write.high >= outlen) { + /* If no data can fit, we'll need to retry later. */ + BIO_set_retry_write(b); + return -1; + } + inlen = bufev->wm_write.high - outlen; + } + + assert(inlen > 0); + evbuffer_add(output, in, inlen); + return inlen; +} + +/* Called to handle various requests */ +static long +bio_bufferevent_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + struct bufferevent *bufev = b->ptr; + long ret = 1; + + switch (cmd) { + case BIO_CTRL_GET_CLOSE: + ret = b->shutdown; + break; + case BIO_CTRL_SET_CLOSE: + b->shutdown = (int)num; + break; + case BIO_CTRL_PENDING: + ret = evbuffer_get_length(bufferevent_get_input(bufev)) != 0; + break; + case BIO_CTRL_WPENDING: + ret = evbuffer_get_length(bufferevent_get_output(bufev)) != 0; + break; + /* XXXX These two are given a special-case treatment because + * of cargo-cultism. I should come up with a better reason. */ + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + default: + ret = 0; + break; + } + return ret; +} + +/* Called to write a string to the BIO */ +static int +bio_bufferevent_puts(BIO *b, const char *s) +{ + return bio_bufferevent_write(b, s, strlen(s)); +} + +/* Method table for the bufferevent BIO */ +static BIO_METHOD methods_bufferevent = { + BIO_TYPE_LIBEVENT, "bufferevent", + bio_bufferevent_write, + bio_bufferevent_read, + bio_bufferevent_puts, + NULL /* bio_bufferevent_gets */, + bio_bufferevent_ctrl, + bio_bufferevent_new, + bio_bufferevent_free, + NULL /* callback_ctrl */, +}; + +/* Return the method table for the bufferevents BIO */ +static BIO_METHOD * +BIO_s_bufferevent(void) +{ + return &methods_bufferevent; +} + +/* Create a new BIO to wrap communication around a bufferevent. If close_flag + * is true, the bufferevent will be freed when the BIO is closed. */ +static BIO * +BIO_new_bufferevent(struct bufferevent *bufferevent, int close_flag) +{ + BIO *result; + if (!bufferevent) + return NULL; + if (!(result = BIO_new(BIO_s_bufferevent()))) + return NULL; + result->init = 1; + result->ptr = bufferevent; + result->shutdown = close_flag ? 1 : 0; + return result; +} + +/* -------------------- + Now, here's the openssl-based implementation of bufferevent. + + The implementation comes in two flavors: one that connects its SSL object + to an underlying bufferevent using a BIO_bufferevent, and one that has the + SSL object connect to a socket directly. The latter should generally be + faster, except on Windows, where your best bet is using a + bufferevent_async. + + (OpenSSL supports many other BIO types, too. But we can't use any unless + we have a good way to get notified when they become readable/writable.) + -------------------- */ + +struct bufferevent_openssl { + /* Shared fields with common bufferevet implementation code. + If we were set up with an underlying bufferevent, we use the + events here as timers only. If we have an SSL, then we use + the events as socket events. + */ + struct bufferevent_private bev; + /* An underlying bufferevent that we're directing our output to. + If it's NULL, then we're connected to an fd, not an evbuffer. */ + struct bufferevent *underlying; + /* The SSL object doing our encryption. */ + SSL *ssl; + + /* A callback that's invoked when data arrives on our outbuf so we + know to write data to the SSL. */ + struct evbuffer_cb_entry *outbuf_cb; + + /* When we next get available space, we should say "read" instead of + "write". This can happen if there's a renegotiation during a read + operation. */ + unsigned read_blocked_on_write : 1; + /* When we next get data, we should say "write" instead of "read". */ + unsigned write_blocked_on_read : 1; + /* XXX */ + unsigned allow_dirty_shutdown : 1; + /* XXXX */ + unsigned fd_is_set : 1; + + /* Are we currently connecting, accepting, or doing IO? */ + unsigned state : 2; +}; + +static int be_openssl_enable(struct bufferevent *, short); +static int be_openssl_disable(struct bufferevent *, short); +static void be_openssl_destruct(struct bufferevent *); +static void be_openssl_adj_timeouts(struct bufferevent *); +static int be_openssl_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode); +static int be_openssl_ctrl(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *); + +const struct bufferevent_ops bufferevent_ops_openssl = { + "ssl", + evutil_offsetof(struct bufferevent_openssl, bev), + be_openssl_enable, + be_openssl_disable, + be_openssl_destruct, + be_openssl_adj_timeouts, + be_openssl_flush, + be_openssl_ctrl, +}; + +/* Given a bufferevent, return a pointer to the bufferevent_openssl that + * countains it, if any. */ +static inline struct bufferevent_openssl * +upcast(struct bufferevent *bev) +{ + struct bufferevent_openssl *bev_o; + if (bev->be_ops != &bufferevent_ops_openssl) + return NULL; + bev_o = (void*)( ((char*)bev) - + evutil_offsetof(struct bufferevent_openssl, bev.bev)); + assert(bev_o->bev.bev.be_ops == &bufferevent_ops_openssl); + return bev_o; +} + +/* Have the base communications channel (either the underlying bufferevent or + * ev_read and ev_write) start reading. Take the read-blocked-on-write flag + * into account. */ +static void +start_reading(struct bufferevent_openssl *bev_ssl) +{ + if (bev_ssl->underlying) { + short e = EV_READ; + if (bev_ssl->read_blocked_on_write) + e |= EV_WRITE; + bufferevent_enable(bev_ssl->underlying, e); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + _bufferevent_add_event(&bev->ev_read, &bev->timeout_read); + if (bev_ssl->read_blocked_on_write) + _bufferevent_add_event(&bev->ev_write, + &bev->timeout_write); + } +} + +/* Have the base communications channel (either the underlying bufferevent or + * ev_read and ev_write) start writing. Take the write-blocked-on-read flag + * into account. */ +static void +start_writing(struct bufferevent_openssl *bev_ssl) +{ + if (bev_ssl->underlying) { + short e = EV_WRITE; + if (bev_ssl->write_blocked_on_read) + e |= EV_READ; + bufferevent_enable(bev_ssl->underlying, e); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + _bufferevent_add_event(&bev->ev_write, &bev->timeout_write); + if (bev_ssl->write_blocked_on_read) + _bufferevent_add_event(&bev->ev_read, + &bev->timeout_read); + } +} + +static void +stop_reading(struct bufferevent_openssl *bev_ssl) +{ + if (bev_ssl->write_blocked_on_read) + return; + if (bev_ssl->underlying) + bufferevent_disable(bev_ssl->underlying, EV_READ); + else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_read); + } +} + +static void +stop_writing(struct bufferevent_openssl *bev_ssl) +{ + if (bev_ssl->read_blocked_on_write) + return; + if (bev_ssl->underlying) + bufferevent_disable(bev_ssl->underlying, EV_WRITE); + else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_write); + } +} + +static void +set_rbow(struct bufferevent_openssl *bev_ssl) +{ + stop_reading(bev_ssl); + bev_ssl->read_blocked_on_write = 1; + start_writing(bev_ssl); +} + +static void +set_wbor(struct bufferevent_openssl *bev_ssl) +{ + stop_writing(bev_ssl); + bev_ssl->write_blocked_on_read = 1; + start_reading(bev_ssl); +} + +static void +clear_rbow(struct bufferevent_openssl *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + bev_ssl->read_blocked_on_write = 0; + if (!(bev->enabled & EV_WRITE)) + stop_writing(bev_ssl); + if (bev->enabled & EV_READ) + start_reading(bev_ssl); +} + + +static void +clear_wbor(struct bufferevent_openssl *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + bev_ssl->write_blocked_on_read = 0; + if (!(bev->enabled & EV_READ)) + stop_reading(bev_ssl); + if (bev->enabled & EV_WRITE) + start_writing(bev_ssl); +} + +static void +conn_closed(struct bufferevent_openssl *bev_ssl, int errcode, int ret) +{ + int event = BEV_EVENT_ERROR; + int dirty_shutdown = 0; + + switch (errcode) { + case SSL_ERROR_ZERO_RETURN: + /* Possibly a clean shutdown. */ + if (SSL_get_shutdown(bev_ssl->ssl) & SSL_RECEIVED_SHUTDOWN) + event = BEV_EVENT_EOF; + else + dirty_shutdown = 1; + break; + case SSL_ERROR_SYSCALL: + /* IO error; possibly a dirty shutdown. */ + if (ret == 0 && ERR_get_error() == 0) + dirty_shutdown = 1; + break; + case SSL_ERROR_SSL: + /* Protocol error. */ + break; + case SSL_ERROR_WANT_X509_LOOKUP: + /* XXXX handle this. */ + break; + case SSL_ERROR_NONE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + default: + /* should be impossible */ + event_errx(1, "Unexpected OpenSSL error code %d", errcode); + } + + if (dirty_shutdown && bev_ssl->allow_dirty_shutdown) + event = BEV_EVENT_EOF; + + _bufferevent_run_eventcb(&bev_ssl->bev.bev, event); + + stop_reading(bev_ssl); + stop_writing(bev_ssl); +} + +/* returns -1 on internal error, 0 on stall, 1 on progress */ +static int +do_read(struct bufferevent_openssl *bev_ssl, int n_to_read) +{ + /* Requires lock */ + struct bufferevent *bev = &bev_ssl->bev.bev; + struct evbuffer *input = bev->input; + int r, n, i, n_used = 0, blocked = 0; + struct evbuffer_iovec space[2]; + + n = evbuffer_reserve_space(input, n_to_read, space, 2); + if (n < 0) + return -1; + + for (i=0; issl, space[i].iov_base, space[i].iov_len); + if (r>0) { + if (bev_ssl->read_blocked_on_write) + clear_rbow(bev_ssl); + ++n_used; + space[i].iov_len = r; + } else { + int err = SSL_get_error(bev_ssl->ssl, r); + print_err(err); + switch (err) { + case SSL_ERROR_WANT_READ: + /* Can't read until underlying has more data. */ + if (bev_ssl->read_blocked_on_write) + clear_rbow(bev_ssl); + break; + case SSL_ERROR_WANT_WRITE: + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->read_blocked_on_write) + set_rbow(bev_ssl); + break; + default: + conn_closed(bev_ssl, err, r); + break; + } + blocked = 1; + break; /* out of the loop */ + } + } + + if (n_used) { + evbuffer_commit_space(input, space, n_used); + if (bev_ssl->underlying) + BEV_RESET_GENERIC_READ_TIMEOUT(bev); + + if (evbuffer_get_length(input) >= bev->wm_read.low && + bev->readcb) + _bufferevent_run_readcb(bev); + } + + return blocked ? 0 : 1; +} + +static int +do_write(struct bufferevent_openssl *bev_ssl, int atmost) +{ + int i, r, n, n_written = 0, blocked=0; + struct bufferevent *bev = &bev_ssl->bev.bev; + struct evbuffer *output = bev->output; + struct evbuffer_iovec space[8]; + + n = evbuffer_peek(output, atmost, NULL, space, 8); + if (n < 0) + return -1; + + for (i=0; i < n; ++i) { + r = SSL_write(bev_ssl->ssl, space[i].iov_base, + space[i].iov_len); + if (r > 0) { + if (bev_ssl->write_blocked_on_read) + clear_wbor(bev_ssl); + n_written += r; + } else { + int err = SSL_get_error(bev_ssl->ssl, r); + print_err(err); + switch (err) { + case SSL_ERROR_WANT_WRITE: + /* Can't read until underlying has more data. */ + if (bev_ssl->write_blocked_on_read) + clear_wbor(bev_ssl); + break; + case SSL_ERROR_WANT_READ: + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->write_blocked_on_read) + set_wbor(bev_ssl); + break; + default: + conn_closed(bev_ssl, err, r); + break; + } + blocked = 1; + break; + } + } + if (n_written) { + evbuffer_drain(output, n_written); + if (bev_ssl->underlying) + BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); + + if (bev->writecb && + evbuffer_get_length(output) <= bev->wm_write.low) + _bufferevent_run_writecb(bev); + } + return blocked ? 0 : 1; +} + +#define WRITE_FRAME 15000 + +#define READ_DEFAULT 4096 + +/* Things look readable. If write is blocked on read, write till it isn't. + * Read from the underlying buffer until we block or we hit our high-water + * mark. + */ +static void +consider_reading(struct bufferevent_openssl *bev_ssl) +{ + int r; + struct evbuffer *input = bev_ssl->bev.bev.input; + struct event_watermark *wm = &bev_ssl->bev.bev.wm_read; + + while (bev_ssl->write_blocked_on_read) { + r = do_write(bev_ssl, WRITE_FRAME); + if (r <= 0) + break; + } + if (bev_ssl->write_blocked_on_read) + return; + while ((bev_ssl->bev.bev.enabled & EV_READ) && + (! wm->high || evbuffer_get_length(input) < wm->high)) { + int n_to_read = + wm->high ? wm->high - evbuffer_get_length(input) + : READ_DEFAULT; + r = do_read(bev_ssl, n_to_read); + if (r <= 0) + break; + } +} + +static void +consider_writing(struct bufferevent_openssl *bev_ssl) +{ + int r; + struct evbuffer *output = bev_ssl->bev.bev.output; + struct evbuffer *target = NULL; + struct event_watermark *wm = NULL; + + while (bev_ssl->read_blocked_on_write) { + r = do_read(bev_ssl, 1024); /* XXXX 1024 is a hack */ + if (r <= 0) + break; + } + if (bev_ssl->read_blocked_on_write) + return; + if (bev_ssl->underlying) { + target = bev_ssl->underlying->output; + wm = &bev_ssl->underlying->wm_write; + } + while ((bev_ssl->bev.bev.enabled & EV_WRITE) && + evbuffer_get_length(output) && + (!target || (! wm->high || evbuffer_get_length(target) < wm->high))) { + int n_to_write; + if (wm && wm->high) + n_to_write = wm->high - evbuffer_get_length(target); + else + n_to_write = WRITE_FRAME; + r = do_write(bev_ssl, n_to_write); + if (r <= 0) + break; + } + + if (!bev_ssl->underlying && !evbuffer_get_length(output)) + event_del(&bev_ssl->bev.bev.ev_write); +} + +static void +be_openssl_readcb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_openssl *bev_ssl = ctx; + consider_reading(bev_ssl); +} + +static void +be_openssl_writecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_openssl *bev_ssl = ctx; + consider_writing(bev_ssl); +} + +static void +be_openssl_eventcb(struct bufferevent *bev_base, short what, void *ctx) +{ + struct bufferevent_openssl *bev_ssl = ctx; + int event = 0; + + if (what & BEV_EVENT_EOF) { + if (bev_ssl->allow_dirty_shutdown) + event = BEV_EVENT_EOF; + else + event = BEV_EVENT_ERROR; + } else if (what & BEV_EVENT_TIMEOUT) { + /* We sure didn't set this. Propagate it to the user. */ + event = what; + } else if (what & BEV_EVENT_CONNECTED) { + /* Ignore it. We're saying SSL_connect() already, which will + eat it. */ + } + if (event) + _bufferevent_run_eventcb(&bev_ssl->bev.bev, event); +} + +static void +be_openssl_readeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_openssl *bev_ssl = ptr; + _bufferevent_incref_and_lock(&bev_ssl->bev.bev); + if (what & EV_TIMEOUT) { + _bufferevent_run_eventcb(&bev_ssl->bev.bev, + BEV_EVENT_TIMEOUT|BEV_EVENT_READING); + } else + consider_reading(bev_ssl); + _bufferevent_decref_and_unlock(&bev_ssl->bev.bev); +} + +static void +be_openssl_writeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_openssl *bev_ssl = ptr; + _bufferevent_incref_and_lock(&bev_ssl->bev.bev); + if (what & EV_TIMEOUT) { + _bufferevent_run_eventcb(&bev_ssl->bev.bev, + BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING); + } + consider_writing(bev_ssl); + _bufferevent_decref_and_unlock(&bev_ssl->bev.bev); +} + +static void +set_open_callbacks(struct bufferevent_openssl *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_openssl_readcb, be_openssl_writecb, be_openssl_eventcb, + bev_ssl); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + int rpending=0, wpending=0; + if (fd < 0 && bev_ssl->fd_is_set) + fd = event_get_fd(&bev->ev_read); + if (bev_ssl->fd_is_set) { + rpending = event_pending(&bev->ev_read, EV_READ, NULL); + wpending = event_pending(&bev->ev_write, EV_WRITE, NULL); + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + event_assign(&bev->ev_read, bev->ev_base, fd, + EV_READ|EV_PERSIST, be_openssl_readeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST, be_openssl_writeeventcb, bev_ssl); + if (rpending) + _bufferevent_add_event(&bev->ev_read, &bev->timeout_read); + if (wpending) + _bufferevent_add_event(&bev->ev_write, &bev->timeout_write); + if (fd >= 0) { + bev_ssl->fd_is_set = 1; + } + } +} + +static int +do_handshake(struct bufferevent_openssl *bev_ssl) +{ + int r; + + switch (bev_ssl->state) { + default: + case BUFFEREVENT_SSL_OPEN: + assert(0); + break; + case BUFFEREVENT_SSL_CONNECTING: + r = SSL_connect(bev_ssl->ssl); + break; + case BUFFEREVENT_SSL_ACCEPTING: + r = SSL_accept(bev_ssl->ssl); + break; + } + + if (r==1) { + /* We're done! */ + _bufferevent_run_eventcb(&bev_ssl->bev.bev, + BEV_EVENT_CONNECTED); + bev_ssl->state = BUFFEREVENT_SSL_OPEN; + set_open_callbacks(bev_ssl, -1); + /* Call do_read and do_write as needed */ + bufferevent_enable(&bev_ssl->bev.bev, bev_ssl->bev.bev.enabled); + return 1; + } else { + int err = SSL_get_error(bev_ssl->ssl, r); + print_err(err); + switch (err) { + case SSL_ERROR_WANT_WRITE: + /* XXXX we only want to do this for the socket case. + stop_reading(bev_ssl); + start_writing(bev_ssl); + */ + return 0; + case SSL_ERROR_WANT_READ: + /* XXXX we only want to do this for the socket case. + stop_reading(bev_ssl); + start_writing(bev_ssl); + */ + return 0; + default: + conn_closed(bev_ssl, err, r); + return -1; + } + } +} + +static void +be_openssl_handshakecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_openssl *bev_ssl = ctx; + do_handshake(bev_ssl); +} +static void +be_openssl_handshakeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_openssl *bev_ssl = ptr; + + _bufferevent_incref_and_lock(&bev_ssl->bev.bev); + if (what & EV_TIMEOUT) { + _bufferevent_run_eventcb(&bev_ssl->bev.bev, BEV_EVENT_TIMEOUT); + } else + do_handshake(bev_ssl); + _bufferevent_decref_and_unlock(&bev_ssl->bev.bev); +} + +static void +set_handshake_callbacks(struct bufferevent_openssl *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_openssl_handshakecb, be_openssl_handshakecb, + be_openssl_eventcb, + bev_ssl); + do_handshake(bev_ssl); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + if (fd < 0 && bev_ssl->fd_is_set) + fd = event_get_fd(&bev->ev_read); + if (bev_ssl->fd_is_set) { + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + event_assign(&bev->ev_read, bev->ev_base, fd, + EV_READ|EV_PERSIST, be_openssl_handshakeeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST, be_openssl_handshakeeventcb, bev_ssl); + if (fd >= 0) { + _bufferevent_add_event(&bev->ev_read, &bev->timeout_read); + _bufferevent_add_event(&bev->ev_write, &bev->timeout_write); + bev_ssl->fd_is_set = 1; + } + } +} + + +static void +be_openssl_outbuf_cb(struct evbuffer *buf, + const struct evbuffer_cb_info *cbinfo, void *arg) +{ + struct bufferevent_openssl *bev_ssl = arg; + /* XXX need to hold a reference here. */ + + if (cbinfo->n_added && bev_ssl->state == BUFFEREVENT_SSL_OPEN) { + if (cbinfo->orig_size == 0) + _bufferevent_add_event(&bev_ssl->bev.bev.ev_write, + &bev_ssl->bev.bev.timeout_write); + consider_writing(bev_ssl); + } +} + + +static int +be_openssl_enable(struct bufferevent *bev, short events) +{ + struct bufferevent_openssl *bev_ssl = upcast(bev); + + if (bev_ssl->state != BUFFEREVENT_SSL_OPEN) + return 0; + + if (events & EV_READ) + start_reading(bev_ssl); + if (events & EV_WRITE) + start_writing(bev_ssl); + + if (bev_ssl->underlying) { + _bufferevent_generic_adj_timeouts(bev); + + if (events & EV_READ) + consider_reading(bev_ssl); + if (events & EV_WRITE) + consider_writing(bev_ssl); + } + return 0; +} + +static int +be_openssl_disable(struct bufferevent *bev, short events) +{ + struct bufferevent_openssl *bev_ssl = upcast(bev); + if (bev_ssl->state != BUFFEREVENT_SSL_OPEN) + return 0; + + if (events & EV_READ) + stop_reading(bev_ssl); + if (events & EV_WRITE) + stop_writing(bev_ssl); + + if (bev_ssl->underlying) + _bufferevent_generic_adj_timeouts(bev); + return 0; +} + +static void +be_openssl_destruct(struct bufferevent *bev) +{ + struct bufferevent_openssl *bev_ssl = upcast(bev); + + if (bev_ssl->underlying) { + _bufferevent_del_generic_timeout_cbs(bev); + } else { + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { + if (bev_ssl->underlying) { + bufferevent_free(bev_ssl->underlying); + bev_ssl->underlying = NULL; + } + SSL_free(bev_ssl->ssl); + } +} + +static void +be_openssl_adj_timeouts(struct bufferevent *bev) +{ + struct bufferevent_openssl *bev_ssl = upcast(bev); + + if (bev_ssl->underlying) + _bufferevent_generic_adj_timeouts(bev); + else { + if (event_pending(&bev->ev_read, EV_READ, NULL)) + _bufferevent_add_event(&bev->ev_read, &bev->timeout_read); + if (event_pending(&bev->ev_write, EV_WRITE, NULL)) + _bufferevent_add_event(&bev->ev_write, &bev->timeout_write); + } +} + +static int +be_openssl_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode) +{ + /* XXXX Implement this. */ + return 0; +} + +static int +be_openssl_ctrl(struct bufferevent *bev, + enum bufferevent_ctrl_op op, union bufferevent_ctrl_data *data) +{ + struct bufferevent_openssl *bev_ssl = upcast(bev); + switch (op) { + case BEV_CTRL_SET_FD: + if (bev_ssl->underlying) + return -1; + { + int flag = 0; + BIO *bio; + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) + flag = 1; + bio = BIO_new_socket(data->fd, flag); + SSL_set_bio(bev_ssl->ssl, bio, bio); + bev_ssl->fd_is_set = 1; + } + if (bev_ssl->state == BUFFEREVENT_SSL_OPEN) + set_open_callbacks(bev_ssl, data->fd); + else { + set_handshake_callbacks(bev_ssl, data->fd); + } + return 0; + case BEV_CTRL_GET_FD: + if (bev_ssl->underlying) + return -1; + if (!bev_ssl->fd_is_set) + return -1; + data->fd = event_get_fd(&bev->ev_read); + return 0; + case BEV_CTRL_GET_UNDERLYING: + if (!bev_ssl->underlying) + return -1; + data->ptr = bev_ssl->underlying; + return 0; + default: + return -1; + } +} + +static struct bufferevent * +bufferevent_openssl_new_impl(struct event_base *base, + struct bufferevent *underlying, + evutil_socket_t fd, + SSL *ssl, + enum bufferevent_ssl_state state, + enum bufferevent_options options) +{ + struct bufferevent_openssl *bev_ssl = NULL; + struct bufferevent_private *bev_p = NULL; + enum bufferevent_options tmp_options = options & ~BEV_OPT_THREADSAFE; + + if (underlying != NULL && fd >= 0) + return NULL; /* Only one can be set. */ + + if (!(bev_ssl = mm_calloc(1, sizeof(struct bufferevent_openssl)))) + goto err; + + bev_p = &bev_ssl->bev; + + if (bufferevent_init_common(bev_p, base, + &bufferevent_ops_openssl, tmp_options) < 0) + goto err; + + bev_ssl->underlying = underlying; + bev_ssl->ssl = ssl; + + bev_ssl->outbuf_cb = evbuffer_add_cb(bev_p->bev.output, + be_openssl_outbuf_cb, bev_ssl); + + if (options & BEV_OPT_THREADSAFE) + bufferevent_enable_locking(&bev_ssl->bev.bev, NULL); + + if (underlying) + _bufferevent_init_generic_timeout_cbs(&bev_ssl->bev.bev); + + bev_ssl->state = state; + + switch (state) { + case BUFFEREVENT_SSL_ACCEPTING: + set_handshake_callbacks(bev_ssl, fd); + break; + case BUFFEREVENT_SSL_CONNECTING: + set_handshake_callbacks(bev_ssl, fd); + break; + case BUFFEREVENT_SSL_OPEN: + set_open_callbacks(bev_ssl, fd); + break; + default: + goto err; + } + + if (underlying) + bufferevent_enable(underlying, EV_READ|EV_WRITE); + else { + bev_ssl->bev.bev.enabled = EV_READ|EV_WRITE; + /* XXX Is this quite right? */ + event_add(&bev_ssl->bev.bev.ev_read, NULL); + event_add(&bev_ssl->bev.bev.ev_write, NULL); + } + + return &bev_ssl->bev.bev; +err: + if (bev_ssl) + bufferevent_free(&bev_ssl->bev.bev); + return NULL; +} + +struct bufferevent * +bufferevent_openssl_filter_new(struct event_base *base, + struct bufferevent *underlying, + SSL *ssl, + enum bufferevent_ssl_state state, + enum bufferevent_options options) +{ + int close_flag = options & BEV_OPT_CLOSE_ON_FREE; + BIO *bio; + if (!underlying) + return NULL; + if (!(bio = BIO_new_bufferevent(underlying, close_flag))) + return NULL; + + SSL_set_bio(ssl, bio, bio); + + return bufferevent_openssl_new_impl( + base, underlying, -1, ssl, state, options); +} + +struct bufferevent * +bufferevent_openssl_socket_new(struct event_base *base, + evutil_socket_t fd, + SSL *ssl, + enum bufferevent_ssl_state state, + enum bufferevent_options options) +{ + /* Does the SSL already have an fd? */ + BIO *bio = SSL_get_wbio(ssl); + long have_fd = -1; + if (bio) + have_fd = BIO_get_fd(bio, NULL); + + if (have_fd >= 0) { + /* The SSL is already configured with an fd. */ + if (fd < 0) { + /* We should learn the fd from the SSL. */ + fd = (evutil_socket_t) have_fd; + } else if (have_fd == (long)fd) { + /* We already know the fd from the SSL; do nothing */ + } else { + /* We specified an fd different from that of the SSL. + This is probably an error on our part. Fail. */ + return NULL; + } + } else { + /* The SSL isn't configured with a BIO with an fd. */ + if (fd >= 0) { + /* ... and we have an fd we want to use. */ + int shutdown_flag = 0; + if (options & BEV_OPT_CLOSE_ON_FREE) + shutdown_flag = 1; + bio = BIO_new_socket(fd, shutdown_flag); + SSL_set_bio(ssl, bio, bio); + } else { + /* Leave the fd unset. */ + } + } + + return bufferevent_openssl_new_impl( + base, NULL, fd, ssl, state, options); +} diff --git a/bufferevent_sock.c b/bufferevent_sock.c index e0b3c5d3..50c139c6 100644 --- a/bufferevent_sock.c +++ b/bufferevent_sock.c @@ -88,14 +88,8 @@ const struct bufferevent_ops bufferevent_ops_socket = { be_socket_ctrl, }; -static int -be_socket_add(struct event *ev, const struct timeval *tv) -{ - if (tv->tv_sec == 0 && tv->tv_usec == 0) - return event_add(ev, NULL); - else - return event_add(ev, tv); -} +#define be_socket_add(ev, t) \ + _bufferevent_add_event((ev), (t)) static void bufferevent_socket_outbuf_cb(struct evbuffer *buf, @@ -285,45 +279,35 @@ bufferevent_socket_connect(struct bufferevent *bev, struct bufferevent_private *bufev_p = EVUTIL_UPCAST(bev, struct bufferevent_private, bev); - int family = sa->sa_family; evutil_socket_t fd; - int made_socket = 0; - int result = -1; + int r; + int result=-1; _bufferevent_incref_and_lock(bev); if (!bufev_p) goto done; - fd = event_get_fd(&bev->ev_read); - if (fd < 0) { - made_socket = 1; - if ((fd = socket(family, SOCK_STREAM, 0)) < 0) - goto done; - if (evutil_make_socket_nonblocking(fd) < 0) { - EVUTIL_CLOSESOCKET(fd); - goto done; - } - be_socket_setfd(bev, fd); + fd = bufferevent_getfd(bev); + r = evutil_socket_connect(&fd, sa, socklen); + if (r < 0) { + _bufferevent_run_eventcb(bev, BEV_EVENT_ERROR); + /* do something about the error? */ + goto done; } - if (connect(fd, sa, socklen)<0) { - int e = evutil_socket_geterror(fd); - if (EVUTIL_ERR_CONNECT_RETRIABLE(e)) { - if (! be_socket_enable(bev, EV_WRITE)) { - bufev_p->connecting = 1; - result = 0; - goto done; - } + bufferevent_setfd(bev, fd); + if (r == 0) { + if (! bufferevent_enable(bev, EV_WRITE)) { + bufev_p->connecting = 1; + result = 0; + goto done; } - _bufferevent_run_eventcb(bev, BEV_EVENT_ERROR); - /* do something about the error? */ } else { /* The connect succeeded already. How odd. */ _bufferevent_run_eventcb(bev, BEV_EVENT_CONNECTED); } - result = 0; done: _bufferevent_decref_and_unlock(bev); return result; diff --git a/configure.in b/configure.in index cee16e86..a9b70889 100644 --- a/configure.in +++ b/configure.in @@ -36,6 +36,10 @@ AC_ARG_ENABLE(thread-support, AC_ARG_ENABLE(malloc-replacement, AS_HELP_STRING(--disable-malloc-replacement, disable support for replacing the memory mgt functions), [], [enable_malloc_replacement=yes]) +AC_ARG_ENABLE(openssl, + AS_HELP_STRING(--disable-openssl, disable support for openssl encryption), + [], [enable_openssl=yes]) + AC_PROG_LIBTOOL dnl Uncomment "AC_DISABLE_SHARED" to make shared librraries not get @@ -64,6 +68,18 @@ LIBS="$save_LIBS" AC_SUBST(ZLIB_LIBS) AM_CONDITIONAL(ZLIB_REGRESS, [test "$have_zlib" = "yes"]) +dnl See if we have openssl. This doesn't go in LIBS either. +save_LIBS="$LIBS" +LIBS="" +OPENSSL_LIBS="" +have_openssl=no +AC_SEARCH_LIBS([SSL_new], [ssl], + [have_openssl=yes + OPENSSL_LIBS="$LIBS" + AC_DEFINE(HAVE_OPENSSL, 1, [Define if the system has openssl])]) +LIBS="$save_LIBS" +AC_SUBST(OPENSSL_LIBS) + dnl Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h stddef.h poll.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/param.h sys/ioctl.h sys/select.h sys/devpoll.h port.h netinet/in.h netinet/in6.h sys/socket.h sys/uio.h arpa/inet.h sys/eventfd.h sys/mman.h sys/sendfile.h) @@ -414,7 +430,7 @@ AC_TRY_COMPILE([], [Define to appropriate substitue if compiler doesnt have __func__]))) -# check if we can compile with pthreads for the unittests +# check if we can compile with pthreads have_pthreads=no ACX_PTHREAD([ AC_DEFINE(HAVE_PTHREADS, 1, @@ -422,7 +438,7 @@ ACX_PTHREAD([ have_pthreads=yes]) AM_CONDITIONAL(PTHREADS, [test "$have_pthreads" != "no" && test "$enable_thread_support" != "no"]) -# check if we should compile locking into the library +# check if we should compile locking into the library if test x$enable_thread_support = xno; then AC_DEFINE(DISABLE_THREAD_SUPPORT, 1, [Define if libevent should not be compiled with thread support]) @@ -434,6 +450,9 @@ if test x$enable_malloc_replacement = xno; then [Define if libevent should not allow replacing the mm functions]) fi +# check if we have and should use openssl +AM_CONDITIONAL(OPENSSL, [test "$enable_openssl" != "no" && test "$have_openssl" = "yes"]) + # Add some more warnings which we use in development but not in the # released versions. (Some relevant gcc versions can't handle these.) if test x$enable_gcc_warnings = xyes; then diff --git a/evutil.c b/evutil.c index 39495ec1..3b6d3ca6 100644 --- a/evutil.c +++ b/evutil.c @@ -265,6 +265,38 @@ evutil_socket_geterror(evutil_socket_t sock) } #endif +/* 1 for connected, 0 for not yet, -1 for error. */ +int +evutil_socket_connect(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen) +{ + int made_fd = 0; + + if (*fd_ptr < 0) { + made_fd = 1; + if ((*fd_ptr = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + goto err; + if (evutil_make_socket_nonblocking(*fd_ptr) < 0) { + goto err; + } + } + + if (connect(*fd_ptr, sa, socklen) < 0) { + int e = evutil_socket_geterror(*fd_ptr); + if (EVUTIL_ERR_CONNECT_RETRIABLE(e)) + return 0; + goto err; + } else { + return 1; + } + +err: + if (made_fd) { + EVUTIL_CLOSESOCKET(*fd_ptr); + *fd_ptr = -1; + } + return -1; +} + #ifdef WIN32 #define E(code, s) { code, (s " [" #code " ]") } static struct { int code; const char *msg; } windows_socket_errors[] = { diff --git a/include/Makefile.am b/include/Makefile.am index 529eff1f..9b21abc5 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,21 +1,31 @@ AUTOMAKE_OPTIONS = foreign -# XXXX Don't do all this twice. +EVENT2_EXPORT = \ + event2/buffer.h \ + event2/buffer_compat.h \ + event2/bufferevent.h \ + event2/bufferevent_compat.h \ + event2/bufferevent_ssl.h \ + event2/bufferevent_struct.h \ + event2/dns.h \ + event2/dns_compat.h \ + event2/dns_struct.h \ + event2/event.h \ + event2/event_compat.h \ + event2/event_struct.h \ + event2/http.h \ + event2/http_compat.h \ + event2/http_struct.h \ + event2/listener.h \ + event2/rpc.h \ + event2/rpc_compat.h \ + event2/rpc_struct.h \ + event2/tag.h \ + event2/tag_compat.h \ + event2/thread.h \ + event2/util.h -EXTRA_SRC = event2/buffer.h event2/buffer_compat.h \ - event2/thread.h event2/bufferevent.h event2/bufferevent_compat.h \ - event2/bufferevent_struct.h event2/event.h event2/event_compat.h \ - event2/event_struct.h event2/tag.h event2/tag_compat.h event2/util.h \ - event2/http.h event2/http_struct.h event2/http_compat.h \ - event2/listener.h +EXTRA_SRC = $(EVENT2_EXPORT) + +nobase_include_HEADERS = $(EVENT2_EXPORT) -nobase_include_HEADERS = \ - event2/buffer.h event2/buffer_compat.h \ - event2/thread.h event2/bufferevent.h \ - event2/bufferevent_compat.h \ - event2/bufferevent_struct.h event2/event.h event2/event_compat.h \ - event2/event_struct.h event2/tag.h event2/tag_compat.h event2/util.h \ - event2/http.h event2/http_struct.h event2/http_compat.h \ - event2/rpc.h event2/rpc_struct.h event2/rpc_compat.h \ - event2/dns.h event2/dns_struct.h event2/dns_compat.h \ - event2/listener.h diff --git a/include/event2/bufferevent_ssl.h b/include/event2/bufferevent_ssl.h new file mode 100644 index 00000000..060d6aeb --- /dev/null +++ b/include/event2/bufferevent_ssl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVENT2_BUFFEREVENT_SSL_H_ +#define _EVENT2_BUFFEREVENT_SSL_H_ + +/** @file bufferevent_ssl.h + + OpenSSL support for bufferevents. + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssl_st; + +enum bufferevent_ssl_state { + BUFFEREVENT_SSL_OPEN = 0, + BUFFEREVENT_SSL_CONNECTING = 1, + BUFFEREVENT_SSL_ACCEPTING = 2 +}; + +#ifdef _EVENT_HAVE_OPENSSL +struct bufferevent * +bufferevent_openssl_filter_new(struct event_base *base, + struct bufferevent *underlying, + struct ssl_st *ssl, + enum bufferevent_ssl_state state, + enum bufferevent_options options); + +struct bufferevent * +bufferevent_openssl_socket_new(struct event_base *base, + evutil_socket_t fd, + struct ssl_st *ssl, + enum bufferevent_ssl_state state, + enum bufferevent_options options); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _EVENT2_BUFFEREVENT_SSL_H_ */ diff --git a/sample/Makefile.am b/sample/Makefile.am index 47254094..0998a2cd 100644 --- a/sample/Makefile.am +++ b/sample/Makefile.am @@ -9,6 +9,12 @@ event_test_sources = event-test.c time_test_sources = time-test.c signal_test_sources = signal-test.c +if OPENSSL +noinst_PROGRAMS += le-proxy +le_proxy_sources = le-proxy.c +le_proxy_LDADD = $(LDADD) ../libevent_openssl.la -lcrypto -lssl +endif + verify: DISTCLEANFILES = *~ diff --git a/sample/le-proxy.c b/sample/le-proxy.c new file mode 100644 index 00000000..d8355799 --- /dev/null +++ b/sample/le-proxy.c @@ -0,0 +1,210 @@ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct event_base *base; +static struct sockaddr_storage listen_on_addr; +static struct sockaddr_storage connect_to_addr; +static int connect_to_addrlen; +static int use_wrapper = 1; + +static SSL_CTX *ssl_ctx = NULL; + +static void +readcb(struct bufferevent *bev, void *ctx) +{ + struct bufferevent *partner = ctx; + struct evbuffer *src, *dst; + src = bufferevent_get_input(bev); + if (!partner) { + evbuffer_drain(src, evbuffer_get_length(src)); + return; + } + dst = bufferevent_get_output(partner); + + evbuffer_add_buffer(dst, src); +} + +static void +close_on_finished_writecb(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *b = bufferevent_get_output(bev); + if (evbuffer_get_length(b) == 0) { + bufferevent_free(bev); + } +} + +static void +eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct bufferevent *partner = ctx; + + if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { + if (what & BEV_EVENT_ERROR) + perror("maybe an error"); + + if (partner) { + /* Flush all pending data */ + readcb(bev, ctx); + + /* Disconnect this one from the partner. */ + bufferevent_setcb(partner, + NULL, close_on_finished_writecb, + eventcb, NULL); + } + bufferevent_free(bev); + } +} + +static void +syntax(void) +{ + fputs("Syntax:\n", stderr); + fputs(" le-proxy [-s] [-W] \n", stderr); + fputs("Example:\n", stderr); + fputs(" le-proxy 127.0.0.1:8888 1.2.3.4:80\n", stderr); + + exit(1); +} + +static void +accept_cb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *a, int slen, void *p) +{ + struct bufferevent *b_out, *b_in; + /* Create two linked bufferevent objects: one to connect, one for the + * new connection */ + b_in = bufferevent_socket_new(base, fd, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + + if (!ssl_ctx || use_wrapper) + b_out = bufferevent_socket_new(base, -1, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + else { + SSL *ssl = SSL_new(ssl_ctx); + b_out = bufferevent_openssl_socket_new(base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + } + + assert(b_in && b_out); + + if (bufferevent_socket_connect(b_out, + (struct sockaddr*)&connect_to_addr, connect_to_addrlen)<0) { + perror("bufferevent_socket_connect"); + bufferevent_free(b_out); + bufferevent_free(b_in); + return; + } + + if (ssl_ctx && use_wrapper) { + struct bufferevent *b_ssl; + SSL *ssl = SSL_new(ssl_ctx); + b_ssl = bufferevent_openssl_filter_new(base, + b_out, ssl, BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + if (!b_ssl) { + perror("Bufferevent_openssl_new"); + bufferevent_free(b_out); + bufferevent_free(b_in); + } + b_out = b_ssl; + } + + bufferevent_setcb(b_in, readcb, NULL, eventcb, b_out); + bufferevent_setcb(b_out, readcb, NULL, eventcb, b_in); + + bufferevent_enable(b_in, EV_READ|EV_WRITE); + bufferevent_enable(b_out, EV_READ|EV_WRITE); +} + +int +main(int argc, char **argv) +{ + int i; + int socklen; + + int use_ssl = 0; + struct evconnlistener *listener; + + if (argc < 3) + syntax(); + + for (i=1; i < argc; ++i) { + if (!strcmp(argv[i], "-s")) { + use_ssl = 1; + } else if (!strcmp(argv[i], "-W")) { + use_wrapper = 0; + } else if (argv[i][0] == '-') { + syntax(); + } else + break; + } + + if (i+2 != argc) + syntax(); + + memset(&listen_on_addr, 0, sizeof(listen_on_addr)); + socklen = sizeof(listen_on_addr); + if (evutil_parse_sockaddr_port(argv[i], + (struct sockaddr*)&listen_on_addr, &socklen)<0) { + int p = atoi(argv[i]); + struct sockaddr_in *sin = (struct sockaddr_in*)&listen_on_addr; + if (p < 1 || p > 65535) + syntax(); + sin->sin_port = htons(p); + sin->sin_addr.s_addr = htonl(0x7f000001); + sin->sin_family = AF_INET; + socklen = sizeof(struct sockaddr_in); + } + + memset(&connect_to_addr, 0, sizeof(connect_to_addr)); + connect_to_addrlen = sizeof(connect_to_addr); + if (evutil_parse_sockaddr_port(argv[i+1], + (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) + syntax(); + + base = event_base_new(); + if (!base) { + perror("event_base_new()"); + return 1; + } + + if (use_ssl) { + int r; + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + r = RAND_poll(); + if (r == 0) { + fprintf(stderr, "RAND_poll() failed.\n"); + return 1; + } + ssl_ctx = SSL_CTX_new(SSLv23_method()); + } + + listener = evconnlistener_new_bind(base, accept_cb, NULL, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_REUSEABLE, + -1, (struct sockaddr*)&listen_on_addr, socklen); + + event_base_dispatch(base); + + return 0; +} diff --git a/test/Makefile.am b/test/Makefile.am index b6c1b572..ad83bcc0 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -33,11 +33,17 @@ endif if BUILD_WIN32 regress_SOURCES += regress_iocp.c endif + regress_LDADD = ../libevent.la $(PTHREAD_LIBS) $(ZLIB_LIBS) regress_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat \ -I$(top_srcdir)/include $(PTHREAD_CFLAGS) $(ZLIB_CFLAGS) regress_LDFLAGS = $(PTHREAD_CFLAGS) +if OPENSSL +regress_SOURCES += regress_ssl.c +regress_LDADD += ../libevent_openssl.la -lcrypto -lssl +endif + bench_SOURCES = bench.c bench_LDADD = ../libevent.la bench_cascade_SOURCES = bench_cascade.c diff --git a/test/regress.h b/test/regress.h index b8724c01..c751f071 100644 --- a/test/regress.h +++ b/test/regress.h @@ -46,6 +46,7 @@ extern struct testcase_t rpc_testcases[]; extern struct testcase_t edgetriggered_testcases[]; extern struct testcase_t minheap_testcases[]; extern struct testcase_t iocp_testcases[]; +extern struct testcase_t ssl_testcases[]; void regress_threads(void *); void test_bufferevent_zlib(void *); diff --git a/test/regress_main.c b/test/regress_main.c index e16c7d6a..68dcdc4d 100644 --- a/test/regress_main.c +++ b/test/regress_main.c @@ -288,6 +288,9 @@ struct testgroup_t testgroups[] = { { "thread/", thread_testcases }, #ifdef WIN32 { "iocp/", iocp_testcases }, +#endif +#ifdef _EVENT_HAVE_OPENSSL + { "ssl/", ssl_testcases }, #endif END_OF_GROUPS }; diff --git a/test/regress_ssl.c b/test/regress_ssl.c new file mode 100644 index 00000000..83e93d72 --- /dev/null +++ b/test/regress_ssl.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2009 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#include +#include +#include +#include + +#include "regress.h" +#include "tinytest.h" +#include "tinytest_macros.h" + +#include +#include +#include +#include + +#include + +/* A short pre-generated key, to save the cost of doing an RSA key generation + * step during the unit tests. It's only 512 bits long, and it is published + * in this file, so you would have to be very foolish to consider using it in + * your own code. */ +static const char KEY[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIBOgIBAAJBAKibTEzXjj+sqpipePX1lEk5BNFuL/dDBbw8QCXgaJWikOiKHeJq\n" + "3FQ0OmCnmpkdsPFE4x3ojYmmdgE2i0dJwq0CAwEAAQJAZ08gpUS+qE1IClps/2gG\n" + "AAer6Bc31K2AaiIQvCSQcH440cp062QtWMC3V5sEoWmdLsbAHFH26/9ZHn5zAflp\n" + "gQIhANWOx/UYeR8HD0WREU5kcuSzgzNLwUErHLzxP7U6aojpAiEAyh2H35CjN/P7\n" + "NhcZ4QYw3PeUWpqgJnaE/4i80BSYkSUCIQDLHFhLYLJZ80HwHTADif/ISn9/Ow6b\n" + "p6BWh3DbMar/eQIgBPS6azH5vpp983KXkNv9AL4VZi9ac/b+BeINdzC6GP0CIDmB\n" + "U6GFEQTZ3IfuiVabG5pummdC4DNbcdI+WKrSFNmQ\n" + "-----END RSA PRIVATE KEY-----\n"; + +static EVP_PKEY * +getkey(void) +{ + EVP_PKEY *key; + BIO *bio; + + /* new read-only BIO backed by KEY. */ + bio = BIO_new_mem_buf((char*)KEY, -1); + tt_assert(bio); + + key = PEM_read_bio_PrivateKey(bio,NULL,NULL,NULL); + BIO_free(bio); + tt_assert(key); + + return key; +end: + return NULL; +} + +static X509 * +getcert(void) +{ + /* Dummy code to make a quick-and-dirty valid certificate with + OpenSSL. Don't copy this code into your own program! It does a + number of things in a stupid and insecure way. */ + X509 *x509 = NULL; + X509_NAME *name = NULL; + EVP_PKEY *key = getkey(); + int nid; + time_t now = time(NULL); + + tt_assert(key); + + x509 = X509_new(); + tt_assert(x509); + tt_assert(0 != X509_set_version(x509, 2)); + tt_assert(0 != ASN1_INTEGER_set(X509_get_serialNumber(x509), + (long)now)); + + name = X509_NAME_new(); + tt_assert(name); + tt_assert(NID_undef != (nid = OBJ_txt2nid("commonName"))); + tt_assert(0 != X509_NAME_add_entry_by_NID( + name, nid, MBSTRING_ASC, (unsigned char*)"example.com", + -1, -1, 0)); + + X509_set_subject_name(x509, name); + X509_set_issuer_name(x509, name); + + X509_time_adj(X509_get_notBefore(x509), 0, &now); + now += 3600; + X509_time_adj(X509_get_notAfter(x509), 0, &now); + X509_set_pubkey(x509, key); + tt_assert(0 != X509_sign(x509, key, EVP_sha1())); + + return x509; +end: + X509_free(x509); + return NULL; +} + +static SSL_CTX * +get_ssl_ctx(void) +{ + static SSL_CTX *the_ssl_ctx = NULL; + if (the_ssl_ctx) + return the_ssl_ctx; + return (the_ssl_ctx = SSL_CTX_new(SSLv23_method())); +} + +static void +init_ssl(void) +{ + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +} + +/* ==================== + Here's a simple test: we read a number from the input, increment it, and + reply, until we get to 1001. +*/ + +static int test_is_done = 0; +static int n_connected = 0; +static int got_close = 0; +static int got_error = 0; + +static void +respond_to_number(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *b = bufferevent_get_input(bev); + char *line; + int n; + line = evbuffer_readln(b, NULL, EVBUFFER_EOL_LF); + if (! line) + return; + n = atoi(line); + if (n <= 0) + TT_FAIL(("Bad number: %s", line)); + TT_BLATHER(("The number was %d", n)); + if (n == 1001) { + ++test_is_done; + bufferevent_free(bev); /* Should trigger close on other side. */ + return; + } + ++n; + evbuffer_add_printf(bufferevent_get_output(bev), + "%d\n", n); +} + +static void +eventcb(struct bufferevent *bev, short what, void *ctx) +{ + TT_BLATHER(("Got event %d", (int)what)); + if (what & BEV_EVENT_CONNECTED) + ++n_connected; + else if (what & BEV_EVENT_EOF) { + TT_BLATHER(("Got a good EOF")); + ++got_close; + bufferevent_free(bev); + } else if (what & BEV_EVENT_ERROR) { + TT_BLATHER(("Got an error.")); + ++got_error; + bufferevent_free(bev); + } +} + +static void +regress_bufferevent_openssl(void *arg) +{ + struct basic_test_data *data = arg; + + struct bufferevent *bev1, *bev2; + SSL *ssl1, *ssl2; + X509 *cert = getcert(); + EVP_PKEY *key = getkey(); + tt_assert(cert); + tt_assert(key); + + init_ssl(); + + ssl1 = SSL_new(get_ssl_ctx()); + ssl2 = SSL_new(get_ssl_ctx()); + + SSL_use_certificate(ssl2, cert); + SSL_use_PrivateKey(ssl2, key); + + if (strstr((char*)data->setup_data, "socketpair")) { + bev1 = bufferevent_openssl_socket_new( + data->base, + data->pair[0], + ssl1, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + bev2 = bufferevent_openssl_socket_new( + data->base, + data->pair[1], + ssl2, + BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + } else if (strstr((char*)data->setup_data, "filter")) { + struct bufferevent *bev_ll1, *bev_ll2; + bev_ll1 = bufferevent_socket_new(data->base, data->pair[0], + BEV_OPT_CLOSE_ON_FREE); + bev_ll2 = bufferevent_socket_new(data->base, data->pair[1], + BEV_OPT_CLOSE_ON_FREE); + tt_assert(bev_ll1); + tt_assert(bev_ll2); + bev1 = bufferevent_openssl_filter_new( + data->base, + bev_ll1, + ssl1, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + bev2 = bufferevent_openssl_filter_new( + data->base, + bev_ll2, + ssl2, + BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + } else { + TT_DIE(("Bad setup data %s", (char*)data->setup_data)); + } + + bufferevent_enable(bev1, EV_READ|EV_WRITE); + bufferevent_enable(bev2, EV_READ|EV_WRITE); + + bufferevent_setcb(bev1, respond_to_number, NULL, eventcb, NULL); + bufferevent_setcb(bev2, respond_to_number, NULL, eventcb, NULL); + + evbuffer_add_printf(bufferevent_get_output(bev1), "1\n"); + + event_base_dispatch(data->base); + + tt_assert(test_is_done == 1); + tt_assert(n_connected == 2); + /* We don't handle shutdown properly yet. + tt_int_op(got_close, ==, 1); + tt_int_op(got_error, ==, 0); + */ +end: + return; +} + +struct testcase_t ssl_testcases[] = { + + { "bufferevent_socketpair", regress_bufferevent_openssl, TT_ISOLATED, + &basic_setup, (void*)"socketpair" }, + { "bufferevent_filter", regress_bufferevent_openssl, + TT_ISOLATED, + &basic_setup, (void*)"filter" }, + + END_OF_TESTCASES, +}; diff --git a/util-internal.h b/util-internal.h index b2a371e0..5423a112 100644 --- a/util-internal.h +++ b/util-internal.h @@ -129,6 +129,9 @@ int evutil_strncasecmp(const char *, const char *, size_t); #define EVUTIL_UPCAST(ptr, type, field) \ ((type *)((char*)ptr) - evutil_offsetof(type, field)) + +int evutil_socket_connect(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen); + #ifdef __cplusplus } #endif -- 2.40.0