diff options
Diffstat (limited to 'bufferevent_mbedtls.c')
-rw-r--r-- | bufferevent_mbedtls.c | 1386 |
1 files changed, 1386 insertions, 0 deletions
diff --git a/bufferevent_mbedtls.c b/bufferevent_mbedtls.c new file mode 100644 index 00000000..22f0b7b1 --- /dev/null +++ b/bufferevent_mbedtls.c @@ -0,0 +1,1386 @@ +/* + * Copyright (c) 2009-2012 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. + */ + +// Get rid of OSX 10.7 and greater deprecation warnings. +#if defined(__APPLE__) && defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include "event2/event-config.h" +#include "evconfig-private.h" + +#include <sys/types.h> + +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef EVENT__HAVE_STDARG_H +#include <stdarg.h> +#endif +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#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 <mbedtls/ssl.h> +#include <mbedtls/net_sockets.h> +#include <mbedtls/error.h> +#define SSL_ERROR_WANT_READ MBEDTLS_ERR_SSL_WANT_READ +#define SSL_ERROR_WANT_WRITE MBEDTLS_ERR_SSL_WANT_WRITE +#define SSL mbedtls_ssl_context + +/* + * 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 mechanisms 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 extract data from the BIO. */ +static int +bio_bufferevent_read(void *ctx, unsigned char *out, size_t outlen) +{ + struct bufferevent *bufev = (struct bufferevent*)ctx; + int r = 0; + struct evbuffer *input; + + //BIO_clear_retry_flags(b); + fprintf(stdout, "bio prepare write:\n"); + fwrite(out, 1, outlen, stdout); + + if (!out) + return 0; + if (!bufev) + return MBEDTLS_ERR_NET_INVALID_CONTEXT; + + input = bufferevent_get_input(bufev); + if (evbuffer_get_length(input) == 0) { + /* If there's no data to read, say so. */ + //BIO_set_retry_read(b); + return MBEDTLS_ERR_SSL_WANT_READ; + } else { + r = evbuffer_remove(input, out, outlen); + } + fprintf(stderr, "bio read %d bytes\n", r); + + return r; +} + +/* Called to write data info the BIO */ +static int +bio_bufferevent_write(void *ctx, const unsigned char *in, size_t inlen) +{ + struct bufferevent *bufev = (struct bufferevent*)ctx; + struct evbuffer *output; + size_t outlen; + + //BIO_clear_retry_flags(b); + + if (!bufev) + return MBEDTLS_ERR_NET_INVALID_CONTEXT; + + 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 MBEDTLS_ERR_SSL_WANT_WRITE; + } + inlen = bufev->wm_write.high - outlen; + } + + EVUTIL_ASSERT(inlen > 0); + evbuffer_add(output, in, inlen); + fprintf(stderr, "bio write %d bytes\n", inlen); + return inlen; +} + + +/* -------------------- + 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 bio_data_counts { + unsigned long n_written; + unsigned long n_read; +}; + +struct bufferevent_mbedtls { + /* Shared fields with common bufferevent 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; + /* net fd */ + mbedtls_net_context net_ctx; + /* 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; + + /* A count of how much data the bios have read/written total. Used + for rate-limiting. */ + struct bio_data_counts counts; + + /* If this value is greater than 0, then the last SSL_write blocked, + * and we need to try it again with this many bytes. */ + ev_ssize_t last_write; + +#define NUM_ERRORS 3 + ev_uint32_t errors[NUM_ERRORS]; + + /* 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; + /* Treat TCP close before SSL close on SSL >= v3 as clean EOF. */ + unsigned allow_dirty_shutdown : 1; + /* XXX */ + unsigned n_errors : 2; + + /* Are we currently connecting, accepting, or doing IO? */ + unsigned state : 2; + /* If we reset fd, we sould reset state too */ + unsigned old_state : 2; +}; + +static int be_mbedtls_enable(struct bufferevent *, short); +static int be_mbedtls_disable(struct bufferevent *, short); +static void be_mbedtls_unlink(struct bufferevent *); +static void be_mbedtls_destruct(struct bufferevent *); +static int be_mbedtls_adj_timeouts(struct bufferevent *); +static int be_mbedtls_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode); +static int be_mbedtls_ctrl(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *); + +const struct bufferevent_ops bufferevent_ops_mbedtls = { + "ssl", + evutil_offsetof(struct bufferevent_mbedtls, bev.bev), + be_mbedtls_enable, + be_mbedtls_disable, + be_mbedtls_unlink, + be_mbedtls_destruct, + be_mbedtls_adj_timeouts, + be_mbedtls_flush, + be_mbedtls_ctrl, +}; + +/* Given a bufferevent, return a pointer to the bufferevent_mbedtls that + * contains it, if any. */ +static inline struct bufferevent_mbedtls * +upcast(struct bufferevent *bev) +{ + struct bufferevent_mbedtls *bev_o; + if (!BEV_IS_MBEDTLS(bev)) + return NULL; + bev_o = (void*)( ((char*)bev) - + evutil_offsetof(struct bufferevent_mbedtls, bev.bev)); + EVUTIL_ASSERT(BEV_IS_MBEDTLS(&bev_o->bev.bev)); + return bev_o; +} + +static inline void +put_error(struct bufferevent_mbedtls *bev_ssl, unsigned long err) +{ + if (bev_ssl->n_errors == NUM_ERRORS) + return; + /* The error type according to openssl is "unsigned long", but + openssl never uses more than 32 bits of it. It _can't_ use more + than 32 bits of it, since it needs to report errors on systems + where long is only 32 bits. + */ + bev_ssl->errors[bev_ssl->n_errors++] = (ev_uint32_t) err; +} + +/* 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 int +start_reading(struct bufferevent_mbedtls *bev_ssl) +{ + if (bev_ssl->underlying) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + return 0; + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + int r; + r = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); + if (r == 0 && bev_ssl->read_blocked_on_write) + r = bufferevent_add_event_(&bev->ev_write, + &bev->timeout_write); + return r; + } +} + +/* 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 int +start_writing(struct bufferevent_mbedtls *bev_ssl) +{ + int r = 0; + if (bev_ssl->underlying) { + if (bev_ssl->write_blocked_on_read) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + r = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); + if (!r && bev_ssl->write_blocked_on_read) + r = bufferevent_add_event_(&bev->ev_read, + &bev->timeout_read); + } + return r; +} + +static void +stop_reading(struct bufferevent_mbedtls *bev_ssl) +{ + if (bev_ssl->write_blocked_on_read) + return; + if (bev_ssl->underlying) { + bufferevent_suspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_read); + } +} + +static void +stop_writing(struct bufferevent_mbedtls *bev_ssl) +{ + if (bev_ssl->read_blocked_on_write) + return; + if (bev_ssl->underlying) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_write); + } +} + +static int +set_rbow(struct bufferevent_mbedtls *bev_ssl) +{ + if (!bev_ssl->underlying) + stop_reading(bev_ssl); + bev_ssl->read_blocked_on_write = 1; + return start_writing(bev_ssl); +} + +static int +set_wbor(struct bufferevent_mbedtls *bev_ssl) +{ + if (!bev_ssl->underlying) + stop_writing(bev_ssl); + bev_ssl->write_blocked_on_read = 1; + return start_reading(bev_ssl); +} + +static int +clear_rbow(struct bufferevent_mbedtls *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + int r = 0; + bev_ssl->read_blocked_on_write = 0; + if (!(bev->enabled & EV_WRITE)) + stop_writing(bev_ssl); + if (bev->enabled & EV_READ) + r = start_reading(bev_ssl); + return r; +} + + +static int +clear_wbor(struct bufferevent_mbedtls *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + int r = 0; + bev_ssl->write_blocked_on_read = 0; + if (!(bev->enabled & EV_READ)) + stop_reading(bev_ssl); + if (bev->enabled & EV_WRITE) + r = start_writing(bev_ssl); + return r; +} + +static void +conn_closed(struct bufferevent_mbedtls *bev_ssl, int when, int errcode, int ret) +{ + int event = BEV_EVENT_ERROR; + //int dirty_shutdown = 0; + unsigned long err; + char buf[100] = {}; + + fprintf(stderr, "when %d error code %d", when, errcode); + + if (when & BEV_EVENT_READING && ret == 0) + { + if (bev_ssl->allow_dirty_shutdown) + event = BEV_EVENT_EOF; + } else { + mbedtls_strerror(errcode, buf, sizeof(buf)); + switch (errcode) { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + event = BEV_EVENT_EOF; + break; + //case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: + //case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: + case MBEDTLS_ERR_SSL_CLIENT_RECONNECT: + event_warnx("BUG: Unsupported feature %d: %s", errcode, buf); + break; + default: + /* should be impossible; treat as normal error. */ + event_warnx("BUG: Unexpected mbedtls error code %d: %s", errcode, buf); + break; + } + + put_error(bev_ssl, errcode); + } + + + stop_reading(bev_ssl); + stop_writing(bev_ssl); + + bufferevent_run_eventcb_(&bev_ssl->bev.bev, when | event, 0); +} + +/*static void +init_bio_counts(struct bufferevent_mbedtls *bev_ssl) +{ + BIO *rbio, *wbio; + + wbio = SSL_get_wbio(bev_ssl->ssl); + bev_ssl->counts.n_written = wbio ? BIO_number_written(wbio) : 0; + rbio = SSL_get_rbio(bev_ssl->ssl); + bev_ssl->counts.n_read = rbio ? BIO_number_read(rbio) : 0; +} + +static inline void +decrement_buckets(struct bufferevent_mbedtls *bev_ssl) +{ + unsigned long num_w = BIO_number_written(SSL_get_wbio(bev_ssl->ssl)); + unsigned long num_r = BIO_number_read(SSL_get_rbio(bev_ssl->ssl)); + /* These next two subtractions can wrap around. That's okay. * / + unsigned long w = num_w - bev_ssl->counts.n_written; + unsigned long r = num_r - bev_ssl->counts.n_read; + if (w) + bufferevent_decrement_write_buckets_(&bev_ssl->bev, w); + if (r) + bufferevent_decrement_read_buckets_(&bev_ssl->bev, r); + bev_ssl->counts.n_written = num_w; + bev_ssl->counts.n_read = num_r; +}*/ + +#define OP_MADE_PROGRESS 1 +#define OP_BLOCKED 2 +#define OP_ERR 4 + +/* Return a bitmask of OP_MADE_PROGRESS (if we read anything); OP_BLOCKED (if + we're now blocked); and OP_ERR (if an error occurred). */ +static int +do_read(struct bufferevent_mbedtls *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, atmost; + struct evbuffer_iovec space[2]; + int result = 0; + + if (bev_ssl->bev.read_suspended) + return 0; + + atmost = bufferevent_get_read_max_(&bev_ssl->bev); + if (n_to_read > atmost) + n_to_read = atmost; + + n = evbuffer_reserve_space(input, n_to_read, space, 2); + if (n < 0) + return OP_ERR; + + for (i=0; i<n; ++i) { + if (bev_ssl->bev.read_suspended) + break; + r = mbedtls_ssl_read(bev_ssl->ssl, space[i].iov_base, space[i].iov_len); + if (r>0) { + result |= OP_MADE_PROGRESS; + if (bev_ssl->read_blocked_on_write) + if (clear_rbow(bev_ssl) < 0) + return OP_ERR | result; + ++n_used; + space[i].iov_len = r; + //decrement_buckets(bev_ssl); + } else { + int err = 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) + if (clear_rbow(bev_ssl) < 0) + return OP_ERR | result; + break; + case SSL_ERROR_WANT_WRITE: + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->read_blocked_on_write) + if (set_rbow(bev_ssl) < 0) + return OP_ERR | result; + break; + default: + conn_closed(bev_ssl, BEV_EVENT_READING, err, r); + break; + } + result |= OP_BLOCKED; + 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); + } + + return result; +} + +/* Return a bitmask of OP_MADE_PROGRESS (if we wrote anything); OP_BLOCKED (if + we're now blocked); and OP_ERR (if an error occurred). */ +static int +do_write(struct bufferevent_mbedtls *bev_ssl, int atmost) +{ + int i, r, n, n_written = 0; + struct bufferevent *bev = &bev_ssl->bev.bev; + struct evbuffer *output = bev->output; + struct evbuffer_iovec space[8]; + int result = 0; + + if (bev_ssl->last_write > 0) + atmost = bev_ssl->last_write; + else + atmost = bufferevent_get_write_max_(&bev_ssl->bev); + + n = evbuffer_peek(output, atmost, NULL, space, 8); + if (n < 0) + return OP_ERR | result; + + if (n > 8) + n = 8; + for (i=0; i < n; ++i) { + if (bev_ssl->bev.write_suspended) + break; + + /* SSL_write will (reasonably) return 0 if we tell it to + send 0 data. Skip this case so we don't interpret the + result as an error */ + if (space[i].iov_len == 0) + continue; + + r = mbedtls_ssl_write(bev_ssl->ssl, space[i].iov_base, + space[i].iov_len); + if (r > 0) { + result |= OP_MADE_PROGRESS; + if (bev_ssl->write_blocked_on_read) + if (clear_wbor(bev_ssl) < 0) + return OP_ERR | result; + n_written += r; + bev_ssl->last_write = -1; + //decrement_buckets(bev_ssl); + } else { + int err = 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) + if (clear_wbor(bev_ssl) < 0) + return OP_ERR | result; + bev_ssl->last_write = space[i].iov_len; + break; + case SSL_ERROR_WANT_READ: + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->write_blocked_on_read) + if (set_wbor(bev_ssl) < 0) + return OP_ERR | result; + bev_ssl->last_write = space[i].iov_len; + break; + default: + conn_closed(bev_ssl, BEV_EVENT_WRITING, err, r); + bev_ssl->last_write = -1; + break; + } + result |= OP_BLOCKED; + break; + } + } + if (n_written) { + evbuffer_drain(output, n_written); + if (bev_ssl->underlying) + BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); + + bufferevent_trigger_nolock_(bev, EV_WRITE, BEV_OPT_DEFER_CALLBACKS); + } + return result; +} + +#define WRITE_FRAME 15000 + +#define READ_DEFAULT 4096 + +/* Try to figure out how many bytes to read; return 0 if we shouldn't be + * reading. */ +static int +bytes_to_read(struct bufferevent_mbedtls *bev) +{ + struct evbuffer *input = bev->bev.bev.input; + struct event_watermark *wm = &bev->bev.bev.wm_read; + int result = READ_DEFAULT; + ev_ssize_t limit; + /* XXX 99% of this is generic code that nearly all bufferevents will + * want. */ + + if (bev->write_blocked_on_read) { + return 0; + } + + if (! (bev->bev.bev.enabled & EV_READ)) { + return 0; + } + + if (bev->bev.read_suspended) { + return 0; + } + + if (wm->high) { + if (evbuffer_get_length(input) >= wm->high) { + return 0; + } + + result = wm->high - evbuffer_get_length(input); + } else { + result = READ_DEFAULT; + } + + /* Respect the rate limit */ + limit = bufferevent_get_read_max_(&bev->bev); + if (result > limit) { + result = limit; + } + + return result; +} + + +/* 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_mbedtls *bev_ssl) +{ + int r; + int n_to_read; + int all_result_flags = 0; + + while (bev_ssl->write_blocked_on_read) { + r = do_write(bev_ssl, WRITE_FRAME); + if (r & (OP_BLOCKED|OP_ERR)) + break; + } + if (bev_ssl->write_blocked_on_read) + return; + + n_to_read = bytes_to_read(bev_ssl); + + while (n_to_read) { + r = do_read(bev_ssl, n_to_read); + all_result_flags |= r; + + if (r & (OP_BLOCKED|OP_ERR)) + break; + + if (bev_ssl->bev.read_suspended) + break; + + /* Read all pending data. This won't hit the network + * again, and will (most importantly) put us in a state + * where we don't need to read anything else until the + * socket is readable again. It'll potentially make us + * overrun our read high-watermark (somewhat + * regrettable). The damage to the rate-limit has + * already been done, since OpenSSL went and read a + * whole SSL record anyway. */ + n_to_read = mbedtls_ssl_get_bytes_avail(bev_ssl->ssl); + + /* XXX This if statement is actually a bad bug, added to avoid + * XXX a worse bug. + * + * The bad bug: It can potentially cause resource unfairness + * by reading too much data from the underlying bufferevent; + * it can potentially cause read looping if the underlying + * bufferevent is a bufferevent_pair and deferred callbacks + * aren't used. + * + * The worse bug: If we didn't do this, then we would + * potentially not read any more from bev_ssl->underlying + * until more data arrived there, which could lead to us + * waiting forever. + */ + if (!n_to_read && bev_ssl->underlying) + n_to_read = bytes_to_read(bev_ssl); + } + + if (all_result_flags & OP_MADE_PROGRESS) { + struct bufferevent *bev = &bev_ssl->bev.bev; + + bufferevent_trigger_nolock_(bev, EV_READ, 0); + } + + if (!bev_ssl->underlying) { + /* Should be redundant, but let's avoid busy-looping */ + if (bev_ssl->bev.read_suspended || + !(bev_ssl->bev.bev.enabled & EV_READ)) { + event_del(&bev_ssl->bev.bev.ev_read); + } + } +} + +static void +consider_writing(struct bufferevent_mbedtls *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 & OP_MADE_PROGRESS) { + struct bufferevent *bev = &bev_ssl->bev.bev; + + bufferevent_trigger_nolock_(bev, EV_READ, 0); + } + if (r & (OP_ERR|OP_BLOCKED)) + 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) && + (! bev_ssl->bev.write_suspended) && + 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 & (OP_BLOCKED|OP_ERR)) + break; + } + + if (!bev_ssl->underlying) { + if (evbuffer_get_length(output) == 0) { + event_del(&bev_ssl->bev.bev.ev_write); + } else if (bev_ssl->bev.write_suspended || + !(bev_ssl->bev.bev.enabled & EV_WRITE)) { + /* Should be redundant, but let's avoid busy-looping */ + event_del(&bev_ssl->bev.bev.ev_write); + } + } +} + +static void +be_mbedtls_readcb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_mbedtls *bev_ssl = ctx; + consider_reading(bev_ssl); +} + +static void +be_mbedtls_writecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_mbedtls *bev_ssl = ctx; + consider_writing(bev_ssl); +} + +static void +be_mbedtls_eventcb(struct bufferevent *bev_base, short what, void *ctx) +{ + struct bufferevent_mbedtls *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_ERROR) { + /* An error occurred on the connection. 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, 0); +} + +static void +be_mbedtls_readeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_mbedtls *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, 0); + } else { + consider_reading(bev_ssl); + } + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static void +be_mbedtls_writeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_mbedtls *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, 0); + } else { + consider_writing(bev_ssl); + } + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static evutil_socket_t +be_mbedtls_auto_fd(struct bufferevent_mbedtls *bev_ssl, evutil_socket_t fd) +{ + if (!bev_ssl->underlying) { + struct bufferevent *bev = &bev_ssl->bev.bev; + if (event_initialized(&bev->ev_read) && fd < 0) { + fd = event_get_fd(&bev->ev_read); + } + } + return fd; +} + +static int +set_open_callbacks(struct bufferevent_mbedtls *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_mbedtls_readcb, be_mbedtls_writecb, be_mbedtls_eventcb, + bev_ssl); + return 0; + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + int rpending=0, wpending=0, r1=0, r2=0; + + if (event_initialized(&bev->ev_read)) { + 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|EV_FINALIZE, + be_mbedtls_readeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST|EV_FINALIZE, + be_mbedtls_writeeventcb, bev_ssl); + + if (rpending) + r1 = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); + if (wpending) + r2 = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); + + return (r1<0 || r2<0) ? -1 : 0; + } +} + +static int +do_handshake(struct bufferevent_mbedtls *bev_ssl) +{ + int r; + + switch (bev_ssl->state) { + default: + case BUFFEREVENT_SSL_OPEN: + EVUTIL_ASSERT(0); + return -1; + case BUFFEREVENT_SSL_CONNECTING: + case BUFFEREVENT_SSL_ACCEPTING: + r = mbedtls_ssl_handshake(bev_ssl->ssl); + break; + } + //decrement_buckets(bev_ssl); + + if (r==0) { + evutil_socket_t fd = event_get_fd(&bev_ssl->bev.bev.ev_read); + /* We're done! */ + bev_ssl->state = BUFFEREVENT_SSL_OPEN; + set_open_callbacks(bev_ssl, fd); /* XXXX handle failure */ + /* Call do_read and do_write as needed */ + bufferevent_enable(&bev_ssl->bev.bev, bev_ssl->bev.bev.enabled); + bufferevent_run_eventcb_(&bev_ssl->bev.bev, + BEV_EVENT_CONNECTED, 0); + return 1; + } else { + int err = r; + print_err(err); + switch (err) { + case SSL_ERROR_WANT_WRITE: + stop_reading(bev_ssl); + return start_writing(bev_ssl); + case SSL_ERROR_WANT_READ: + stop_writing(bev_ssl); + return start_reading(bev_ssl); + default: + conn_closed(bev_ssl, BEV_EVENT_READING, err, r); + return -1; + } + } +} + +static void +be_mbedtls_handshakecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_mbedtls *bev_ssl = ctx; + do_handshake(bev_ssl);/* XXX handle failure */ +} + +static void +be_mbedtls_handshakeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_mbedtls *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, 0); + } else + do_handshake(bev_ssl);/* XXX handle failure */ + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static int +set_handshake_callbacks(struct bufferevent_mbedtls *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_mbedtls_handshakecb, be_mbedtls_handshakecb, + be_mbedtls_eventcb, + bev_ssl); + + if (fd < 0) + return 0; + + if (bufferevent_setfd(bev_ssl->underlying, fd)) + return 1; + + return do_handshake(bev_ssl); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + + if (event_initialized(&bev->ev_read)) { + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + + event_assign(&bev->ev_read, bev->ev_base, fd, + EV_READ|EV_PERSIST|EV_FINALIZE, + be_mbedtls_handshakeeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST|EV_FINALIZE, + be_mbedtls_handshakeeventcb, bev_ssl); + if (fd >= 0) + bufferevent_enable(bev, bev->enabled); + return 0; + } +} + +int +bufferevent_ssl_renegotiate(struct bufferevent *bev) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + if (!bev_ssl) + return -1; + if (mbedtls_ssl_renegotiate(bev_ssl->ssl) < 0) + return -1; + bev_ssl->state = BUFFEREVENT_SSL_CONNECTING; + if (set_handshake_callbacks(bev_ssl, be_mbedtls_auto_fd(bev_ssl, -1)) < 0) + return -1; + if (!bev_ssl->underlying) + return do_handshake(bev_ssl); + return 0; +} + +static void +be_mbedtls_outbuf_cb(struct evbuffer *buf, + const struct evbuffer_cb_info *cbinfo, void *arg) +{ + struct bufferevent_mbedtls *bev_ssl = arg; + int r = 0; + /* XXX need to hold a reference here. */ + + if (cbinfo->n_added && bev_ssl->state == BUFFEREVENT_SSL_OPEN) { + if (cbinfo->orig_size == 0) + r = bufferevent_add_event_(&bev_ssl->bev.bev.ev_write, + &bev_ssl->bev.bev.timeout_write); + + if (bev_ssl->underlying) + consider_writing(bev_ssl); + } + /* XXX Handle r < 0 */ + (void)r; +} + + +static int +be_mbedtls_enable(struct bufferevent *bev, short events) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + int r1 = 0, r2 = 0; + + if (events & EV_READ) + r1 = start_reading(bev_ssl); + if (events & EV_WRITE) + r2 = start_writing(bev_ssl); + + if (bev_ssl->underlying) { + if (events & EV_READ) + BEV_RESET_GENERIC_READ_TIMEOUT(bev); + if (events & EV_WRITE) + BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); + + if (events & EV_READ) + consider_reading(bev_ssl); + if (events & EV_WRITE) + consider_writing(bev_ssl); + } + return (r1 < 0 || r2 < 0) ? -1 : 0; +} + +static int +be_mbedtls_disable(struct bufferevent *bev, short events) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + + if (events & EV_READ) + stop_reading(bev_ssl); + if (events & EV_WRITE) + stop_writing(bev_ssl); + + if (bev_ssl->underlying) { + if (events & EV_READ) + BEV_DEL_GENERIC_READ_TIMEOUT(bev); + if (events & EV_WRITE) + BEV_DEL_GENERIC_WRITE_TIMEOUT(bev); + } + return 0; +} + +static void +be_mbedtls_unlink(struct bufferevent *bev) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { + if (bev_ssl->underlying) { + if (BEV_UPCAST(bev_ssl->underlying)->refcnt < 2) { + event_warnx("BEV_OPT_CLOSE_ON_FREE set on an " + "bufferevent with too few references"); + } else { + mbedtls_ssl_set_bio(bev_ssl->ssl, NULL, NULL, NULL, NULL); + bufferevent_free(bev_ssl->underlying); + bev_ssl->underlying = NULL; + } + } + } else { + if (bev_ssl->underlying) { + if (bev_ssl->underlying->errorcb == be_mbedtls_eventcb) + bufferevent_setcb(bev_ssl->underlying, + NULL,NULL,NULL,NULL); + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } + } +} + +static void +be_mbedtls_destruct(struct bufferevent *bev) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { + if (! bev_ssl->underlying) { + evutil_socket_t fd = (evutil_socket_t)bev_ssl->net_ctx.fd; + if (fd >= 0) + evutil_closesocket(fd); + } + mbedtls_ssl_free(bev_ssl->ssl); + } +} + +static int +be_mbedtls_adj_timeouts(struct bufferevent *bev) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + + if (bev_ssl->underlying) { + return bufferevent_generic_adj_timeouts_(bev); + } else { + return bufferevent_generic_adj_existing_timeouts_(bev); + } +} + +static int +be_mbedtls_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode) +{ + /* XXXX Implement this. */ + return 0; +} + +static int +be_mbedtls_set_fd(struct bufferevent_mbedtls *bev_ssl, + enum bufferevent_ssl_state state, evutil_socket_t fd) +{ + if (!bev_ssl->underlying) { + bev_ssl->net_ctx.fd = fd; + mbedtls_ssl_set_bio(bev_ssl->ssl, &(bev_ssl->net_ctx), mbedtls_net_send, mbedtls_net_recv, NULL); + } else { + mbedtls_ssl_set_bio(bev_ssl->ssl, bev_ssl->underlying, bio_bufferevent_write, bio_bufferevent_read, NULL); + } + + bev_ssl->state = state; + + switch (state) { + case BUFFEREVENT_SSL_ACCEPTING: + if (bev_ssl->ssl->conf->endpoint != MBEDTLS_SSL_IS_SERVER) + return -1; + //SSL_set_accept_state(bev_ssl->ssl); + if (set_handshake_callbacks(bev_ssl, fd) < 0) + return -1; + break; + case BUFFEREVENT_SSL_CONNECTING: + if (bev_ssl->ssl->conf->endpoint != MBEDTLS_SSL_IS_CLIENT) + return -1; + //SSL_set_connect_state(bev_ssl->ssl); + if (set_handshake_callbacks(bev_ssl, fd) < 0) + return -1; + break; + case BUFFEREVENT_SSL_OPEN: + if (set_open_callbacks(bev_ssl, fd) < 0) + return -1; + break; + default: + return -1; + } + + return 0; +} + +static int +be_mbedtls_ctrl(struct bufferevent *bev, + enum bufferevent_ctrl_op op, union bufferevent_ctrl_data *data) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bev); + switch (op) { + case BEV_CTRL_SET_FD: + if (!bev_ssl->underlying) { + //bev_ssl->net_ctx.fd = data->fd; + //mbedtls_ssl_set_bio(bev_ssl->ssl, &(bev_ssl->net_ctx), mbedtls_net_send, mbedtls_net_recv, NULL); + } else { + //mbedtls_ssl_set_bio(bev_ssl->ssl, bev_ssl->underlying, bio_bufferevent_write, bio_bufferevent_read, NULL); + } + + return be_mbedtls_set_fd(bev_ssl, bev_ssl->old_state, data->fd); + case BEV_CTRL_GET_FD: + if (bev_ssl->underlying) { + data->fd = event_get_fd(&bev_ssl->underlying->ev_read); + } else { + data->fd = event_get_fd(&bev->ev_read); + } + return 0; + case BEV_CTRL_GET_UNDERLYING: + data->ptr = bev_ssl->underlying; + return 0; + case BEV_CTRL_CANCEL_ALL: + default: + return -1; + } +} + +SSL * +bufferevent_mbedtls_get_ssl(struct bufferevent *bufev) +{ + struct bufferevent_mbedtls *bev_ssl = upcast(bufev); + if (!bev_ssl) + return NULL; + return bev_ssl->ssl; +} + +static struct bufferevent * +bufferevent_mbedtls_new_impl(struct event_base *base, + struct bufferevent *underlying, + evutil_socket_t fd, + SSL *ssl, + enum bufferevent_ssl_state state, + int options) +{ + struct bufferevent_mbedtls *bev_ssl = NULL; + struct bufferevent_private *bev_p = NULL; + int tmp_options = options & ~BEV_OPT_THREADSAFE; + + /* Only one can be set. */ + if (underlying != NULL && fd >= 0) + goto err; + + if (!(bev_ssl = mm_calloc(1, sizeof(struct bufferevent_mbedtls)))) + goto err; + + bev_p = &bev_ssl->bev; + + if (bufferevent_init_common_(bev_p, base, + &bufferevent_ops_mbedtls, tmp_options) < 0) + goto err; + + /* Don't explode if we decide to realloc a chunk we're writing from in + * the output buffer. */ + //SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + bev_ssl->underlying = underlying; + bev_ssl->ssl = ssl; + + bev_ssl->outbuf_cb = evbuffer_add_cb(bev_p->bev.output, + be_mbedtls_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); + bufferevent_incref_(underlying); + } + + bev_ssl->old_state = state; + bev_ssl->last_write = -1; + + //init_bio_counts(bev_ssl); + + fd = be_mbedtls_auto_fd(bev_ssl, fd); + if (be_mbedtls_set_fd(bev_ssl, state, fd)) + goto err; + + if (underlying) { + bufferevent_setwatermark(underlying, EV_READ, 0, 0); + bufferevent_enable(underlying, EV_READ|EV_WRITE); + if (state == BUFFEREVENT_SSL_OPEN) + bufferevent_suspend_read_(underlying, + BEV_SUSPEND_FILT_READ); + } + + return &bev_ssl->bev.bev; +err: + if (options & BEV_OPT_CLOSE_ON_FREE) + mbedtls_ssl_free(ssl); + if (bev_ssl) { + bev_ssl->ssl = NULL; + bufferevent_free(&bev_ssl->bev.bev); + } + return NULL; +} + +struct bufferevent * +bufferevent_mbedtls_filter_new(struct event_base *base, + struct bufferevent *underlying, + SSL *ssl, + enum bufferevent_ssl_state state, + int options) +{ + struct bufferevent *bev; + + if (!underlying) + goto err; + + bev = bufferevent_mbedtls_new_impl( + base, underlying, -1, ssl, state, options); + return bev; + +err: + if (options & BEV_OPT_CLOSE_ON_FREE) + mbedtls_ssl_free(ssl); + return NULL; +} + +struct bufferevent * +bufferevent_mbedtls_socket_new(struct event_base *base, + evutil_socket_t fd, + SSL *ssl, + enum bufferevent_ssl_state state, + int options) +{ + if (fd >= 0) { + /* ... and we have an fd we want to use. */ + } else { + /* Leave the fd unset. */ + } + + return bufferevent_mbedtls_new_impl( + base, NULL, fd, ssl, state, options); + +err: + if (options & BEV_OPT_CLOSE_ON_FREE) + mbedtls_ssl_free(ssl); + return NULL; +} + +int +bufferevent_mbedtls_get_allow_dirty_shutdown(struct bufferevent *bev) +{ + int allow_dirty_shutdown = -1; + struct bufferevent_mbedtls *bev_ssl; + BEV_LOCK(bev); + bev_ssl = upcast(bev); + if (bev_ssl) + allow_dirty_shutdown = bev_ssl->allow_dirty_shutdown; + BEV_UNLOCK(bev); + return allow_dirty_shutdown; +} + +void +bufferevent_mbedtls_set_allow_dirty_shutdown(struct bufferevent *bev, + int allow_dirty_shutdown) +{ + struct bufferevent_mbedtls *bev_ssl; + BEV_LOCK(bev); + bev_ssl = upcast(bev); + if (bev_ssl) + bev_ssl->allow_dirty_shutdown = !!allow_dirty_shutdown; + BEV_UNLOCK(bev); +} + +unsigned long +bufferevent_get_mbedtls_error(struct bufferevent *bev) +{ + unsigned long err = 0; + struct bufferevent_mbedtls *bev_ssl; + BEV_LOCK(bev); + bev_ssl = upcast(bev); + if (bev_ssl && bev_ssl->n_errors) { + err = bev_ssl->errors[--bev_ssl->n_errors]; + } + BEV_UNLOCK(bev); + return err; +} |