Browse Source

[mod_gnutls] stream

personal/stbuehler/wip
Stefan Bühler 9 years ago
parent
commit
b028ad841f
  1. 6
      src/CMakeLists.txt
  2. 3
      src/modules/Makefile.am
  3. 616
      src/modules/gnutls_filter.c
  4. 29
      src/modules/gnutls_filter.h
  5. 375
      src/modules/mod_gnutls.c

6
src/CMakeLists.txt

@ -368,9 +368,9 @@ IF(WITH_LUA)
ENDIF(WITH_LUA)
IF(WITH_GNUTLS)
# ADD_AND_INSTALL_LIBRARY(mod_gnutls "modules/mod_gnutls.c")
# TARGET_LINK_LIBRARIES(mod_gnutls ${GNUTLS_LDFLAGS})
# ADD_TARGET_PROPERTIES(mod_gnutls COMPILE_FLAGS ${GNUTLS_CFLAGS})
ADD_AND_INSTALL_LIBRARY(mod_gnutls "modules/mod_gnutls.c;modules/gnutls_filter.c")
TARGET_LINK_LIBRARIES(mod_gnutls ${GNUTLS_LDFLAGS})
ADD_TARGET_PROPERTIES(mod_gnutls COMPILE_FLAGS ${GNUTLS_CFLAGS})
ENDIF(WITH_GNUTLS)
IF(WITH_OPENSSL)

3
src/modules/Makefile.am

@ -77,10 +77,11 @@ libmod_fortune_la_LIBADD = $(common_libadd)
if USE_GNUTLS
install_libs += libmod_gnutls.la
libmod_gnutls_la_CPPFLAGS = $(AM_CPPFLAGS) $(GNUTLS_CFLAGS)
libmod_gnutls_la_SOURCES = mod_gnutls.c
libmod_gnutls_la_SOURCES = mod_gnutls.c gnutls_filter.c
libmod_gnutls_la_LDFLAGS = $(common_ldflags)
libmod_gnutls_la_LIBADD = $(common_libadd) $(GNUTLS_LIBS)
endif
EXTRA_DIST += gnutls_filter.h
install_libs += libmod_limit.la
libmod_limit_la_SOURCES = mod_limit.c

616
src/modules/gnutls_filter.c

@ -0,0 +1,616 @@
#include "gnutls_filter.h"
struct liGnuTLSFilter {
int refcount;
const liGnuTLSFilterCallbacks *callbacks;
gpointer callback_data;
liServer *srv;
liWorker *wrk;
liLogContext *log_context; /* TODO: support setting this to VR context */
gnutls_session_t session;
liStream crypt_source;
liStream crypt_drain;
liStream plain_source;
liStream plain_drain;
liBuffer *raw_in_buffer; /* for gnutls_record_recv */
liBuffer *raw_out_buffer; /* stream_pushv */
unsigned int initial_handshaked_finished:1;
unsigned int closing:1, aborted:1;
unsigned int write_wants_read:1;
};
static ssize_t stream_push(gnutls_transport_ptr_t, const void*, size_t);
static ssize_t stream_pushv(gnutls_transport_ptr_t, const giovec_t * iov, int iovcnt);
static ssize_t stream_pull(gnutls_transport_ptr_t, void*, size_t);
static ssize_t stream_push(gnutls_transport_ptr_t trans, const void *buf, size_t len) {
giovec_t vec;
vec.iov_base = (void *) buf;
vec.iov_len = len;
return stream_pushv(trans, &vec, 1);
}
static ssize_t stream_pushv(gnutls_transport_ptr_t trans, const giovec_t * iov, int iovcnt) {
const ssize_t blocksize = 16*1024; /* 16k */
liGnuTLSFilter *f = (liGnuTLSFilter*) trans;
liChunkQueue *cq;
int i;
liBuffer *buf;
gboolean cq_buf_append;
ssize_t written = 0;
errno = ECONNRESET;
if (NULL == f || NULL == f->crypt_source.out) return -1;
cq = f->crypt_source.out;
if (cq->is_closed) return -1;
buf = f->raw_out_buffer;
cq_buf_append = (buf != NULL && buf == li_chunkqueue_get_last_buffer(cq, 1024));
for (i = 0; i < iovcnt; ++i) {
const char *data = iov[i].iov_base;
size_t len = iov[i].iov_len;
while (len > 0) {
size_t bufsize, do_write;
if (NULL == buf) buf = li_buffer_new(blocksize);
bufsize = buf->alloc_size - buf->used;
do_write = (bufsize > len) ? len : bufsize;
memcpy(buf->addr + buf->used, data, do_write);
len -= do_write;
data += do_write;
if (cq_buf_append) {
/* also updates buf->used */
li_chunkqueue_update_last_buffer_size(cq, do_write);
} else {
gsize offset = buf->used;
buf->used += do_write;
li_buffer_acquire(buf);
li_chunkqueue_append_buffer2(cq, buf, offset, do_write);
cq_buf_append = TRUE;
}
if (buf->used == buf->alloc_size) {
li_buffer_release(buf);
buf = NULL;
cq_buf_append = FALSE;
}
written += do_write;
}
}
if (NULL != buf && buf->alloc_size - buf->used < 1024) {
li_buffer_release(buf);
f->raw_out_buffer = buf = NULL;
} else {
f->raw_out_buffer = buf;
}
li_stream_notify_later(&f->crypt_source);
errno = 0;
return written;
}
static ssize_t stream_pull(gnutls_transport_ptr_t trans, void *buf, size_t len) {
liGnuTLSFilter *f = (liGnuTLSFilter*) trans;
liChunkQueue *cq;
errno = ECONNRESET;
if (NULL == f || NULL == f->crypt_drain.out) return -1;
cq = f->crypt_drain.out;
if (0 == cq->length) {
if (cq->is_closed) {
errno = 0;
return 0;
} else {
errno = EAGAIN;
return -1;
}
}
if (len > (size_t) cq->length) len = cq->length;
if (!li_chunkqueue_extract_to_memory(cq, len, buf, NULL)) return -1;
li_chunkqueue_skip(cq, len);
errno = 0;
return len;
}
static void f_close_gnutls(liGnuTLSFilter *f) {
if (NULL != f->session && !f->closing) {
liCQLimit *limit;
f->closing = TRUE;
f->session = NULL;
assert(NULL != f->crypt_source.out);
assert(NULL != f->crypt_source.out->limit);
limit = f->crypt_source.out->limit;
limit->notify = NULL;
limit->context = NULL;
li_stream_disconnect(&f->plain_source);
li_stream_disconnect(&f->plain_drain);
li_stream_disconnect_dest(&f->plain_source);
f->log_context = NULL;
if (NULL != f->callbacks && NULL != f->callbacks->closed_cb) {
f->callbacks->closed_cb(f, f->callback_data);
}
}
}
static void f_acquire(liGnuTLSFilter *f) {
assert(f->refcount > 0);
++f->refcount;
}
static void f_release(liGnuTLSFilter *f) {
assert(f->refcount > 0);
if (0 == --f->refcount) {
f->refcount = 1;
f_close_gnutls(f);
g_slice_free(liGnuTLSFilter, f);
}
}
static void f_abort_gnutls(liGnuTLSFilter *f) {
if (f->aborted) return;
f->aborted = TRUE;
f_acquire(f);
f_close_gnutls(f);
li_stream_disconnect(&f->crypt_drain);
li_stream_disconnect_dest(&f->crypt_source);
f_release(f);
}
static void do_handle_error(liGnuTLSFilter *f, const char *gnutlsfunc, int r, gboolean writing) {
switch (r) {
case GNUTLS_E_AGAIN:
if (writing) f->write_wants_read = TRUE;
break;
case GNUTLS_E_REHANDSHAKE:
if (f->initial_handshaked_finished) {
_ERROR(f->srv, f->wrk, f->log_context, "%s: gnutls: client initiated renegotitation, closing connection", gnutlsfunc);
f_abort_gnutls(f);
}
break;
case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
f_abort_gnutls(f);
break;
case GNUTLS_E_UNKNOWN_CIPHER_SUITE:
case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
_DEBUG(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
f_abort_gnutls(f);
break;
default:
if (gnutls_error_is_fatal(r)) {
_ERROR(f->srv, f->wrk, f->log_context, "%s (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
f_abort_gnutls(f);
} else {
_ERROR(f->srv, f->wrk, f->log_context, "%s non fatal (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
}
}
}
#define TIMEDIFF_MS(ts1, ts2) (((ts2).tv_sec - (ts1).tv_sec)*1.0e3 + ((ts2).tv_nsec - (ts1).tv_nsec)*1.0e-6)
static gboolean do_gnutls_handshake(liGnuTLSFilter *f, gboolean writing) {
int r;
assert(!f->initial_handshaked_finished);
r = gnutls_handshake(f->session);
if (GNUTLS_E_SUCCESS == r) {
f->initial_handshaked_finished = 1;
li_stream_acquire(&f->plain_source);
li_stream_acquire(&f->plain_drain);
f->callbacks->handshake_cb(f, f->callback_data, &f->plain_source, &f->plain_drain);
li_stream_release(&f->plain_source);
li_stream_release(&f->plain_drain);
return TRUE;
} else {
do_handle_error(f, "gnutls_handshake", r, writing);
return FALSE;
}
}
static void do_gnutls_read(liGnuTLSFilter *f) {
const ssize_t blocksize = 16*1024; /* 16k */
off_t max_read = 4 * blocksize; /* 64k */
ssize_t r;
off_t len = 0;
liChunkQueue *cq = f->plain_source.out;
f_acquire(f);
if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, FALSE)) goto out;
if (NULL == f->session) {
f_abort_gnutls(f);
goto out;
}
do {
liBuffer *buf;
gboolean cq_buf_append;
buf = li_chunkqueue_get_last_buffer(cq, 1024);
cq_buf_append = (buf != NULL);
if (buf != NULL) {
/* use last buffer as raw_in_buffer; they should be the same anyway */
if (G_UNLIKELY(buf != f->raw_in_buffer)) {
li_buffer_acquire(buf);
li_buffer_release(f->raw_in_buffer);
f->raw_in_buffer = buf;
}
} else {
buf = f->raw_in_buffer;
if (buf != NULL && buf->alloc_size - buf->used < 1024) {
/* release *buffer */
li_buffer_release(buf);
f->raw_in_buffer = buf = NULL;
}
if (buf == NULL) {
f->raw_in_buffer = buf = li_buffer_new(blocksize);
}
}
assert(f->raw_in_buffer == buf);
r = gnutls_record_recv(f->session, buf->addr + buf->used, buf->alloc_size - buf->used);
if (r < 0) {
do_handle_error(f, "gnutls_record_recv", r, FALSE);
goto out;
} else if (r == 0) {
/* clean shutdown? */
f->plain_source.out->is_closed = TRUE;
f->plain_drain.out->is_closed = TRUE;
f->crypt_source.out->is_closed = TRUE;
f->crypt_drain.out->is_closed = TRUE;
li_stream_disconnect(&f->crypt_drain);
li_stream_disconnect_dest(&f->crypt_source);
f_close_gnutls(f);
goto out;
}
if (cq_buf_append) {
li_chunkqueue_update_last_buffer_size(cq, r);
} else {
gsize offset;
li_buffer_acquire(buf);
offset = buf->used;
buf->used += r;
li_chunkqueue_append_buffer2(cq, buf, offset, r);
}
if (buf->alloc_size - buf->used < 1024) {
/* release *buffer */
li_buffer_release(buf);
f->raw_in_buffer = buf = NULL;
}
len += r;
} while (len < max_read);
out:
f_release(f);
}
#if GNUTLS_VERSION_NUMBER >= 0x030109
/* gnutls_record_cork / gnutls_record_uncork available since 3.1.9 */
#define USE_CORK
#endif
static void do_gnutls_write(liGnuTLSFilter *f) {
const ssize_t blocksize = 16*1024; /* 16k */
char *block_data;
off_t block_len;
ssize_t r;
off_t write_max;
#ifdef USE_CORK
gboolean corked = FALSE;
#endif
liChunkQueue *cq = f->plain_drain.out;
f_acquire(f);
f->write_wants_read = FALSE;
/* use space in (encrypted) outgoing buffer as amounts of bytes we try to write from (plain) output
* don't care if we write a little bit more than the limit allowed */
write_max = li_chunkqueue_limit_available(f->crypt_source.out);
assert(write_max >= 0); /* we set a limit! */
if (0 == write_max) goto out;
/* if we start writing, try to write at least blocksize bytes */
if (write_max < blocksize) write_max = blocksize;
if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, TRUE)) goto out;
if (NULL == f->session) {
f_abort_gnutls(f);
goto out;
}
#ifdef USE_CORK
if (0 != cq->length && cq->queue.length > 1) {
corked = TRUE;
gnutls_record_cork(f->session);
}
#endif
do {
GError *err = NULL;
liChunkIter ci;
if (0 == cq->length) break;
ci = li_chunkqueue_iter(cq);
switch (li_chunkiter_read(ci, 0, blocksize, &block_data, &block_len, &err)) {
case LI_HANDLER_GO_ON:
break;
case LI_HANDLER_ERROR:
if (NULL != err) {
_ERROR(f->srv, f->wrk, f->log_context, "Couldn't read data from chunkqueue: %s", err->message);
g_error_free(err);
}
/* fall through */
default:
f_abort_gnutls(f);
goto out;
}
r = gnutls_record_send(f->session, block_data, block_len);
if (r <= 0) {
do_handle_error(f, "gnutls_record_send", r, TRUE);
goto out;
}
li_chunkqueue_skip(cq, r);
write_max -= r;
} while (r == block_len && write_max > 0);
if (cq->is_closed && 0 == cq->length) {
r = gnutls_bye(f->session, GNUTLS_SHUT_RDWR);
switch (r) {
case GNUTLS_E_SUCCESS:
case GNUTLS_E_AGAIN:
case GNUTLS_E_INTERRUPTED:
f->plain_source.out->is_closed = TRUE;
f->crypt_source.out->is_closed = TRUE;
f->crypt_drain.out->is_closed = TRUE;
f_close_gnutls(f);
break;
default:
do_handle_error(f, "gnutls_bye", r, TRUE);
f_abort_gnutls(f);
break;
}
} else if (0 < cq->length && 0 != li_chunkqueue_limit_available(f->crypt_source.out)) {
li_stream_again_later(&f->plain_drain);
}
out:
#ifdef USE_CORK
if (NULL != f->session && corked) {
corked = TRUE;
gnutls_record_uncork(f->session);
}
#endif
f_release(f);
}
/* ssl crypted out -> io */
static void stream_crypt_source_cb(liStream *stream, liStreamEvent event) {
liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_source);
switch (event) {
case LI_STREAM_NEW_DATA:
/* data comes through SSL */
break;
case LI_STREAM_NEW_CQLIMIT:
break;
case LI_STREAM_CONNECTED_DEST: /* io out */
break;
case LI_STREAM_CONNECTED_SOURCE: /* plain_drain */
break;
case LI_STREAM_DISCONNECTED_DEST: /* io out disconnect */
if (!stream->out->is_closed || 0 != stream->out->length) {
f_abort_gnutls(f); /* didn't read everything */
}
break;
case LI_STREAM_DISCONNECTED_SOURCE: /* plain_drain */
if (!stream->out->is_closed) { /* f_close_ssl before we were ready */
f_abort_gnutls(f);
}
break;
case LI_STREAM_DESTROY:
f_release(f);
break;
}
}
/* io -> ssl crypted in */
static void stream_crypt_drain_cb(liStream *stream, liStreamEvent event) {
liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_drain);
switch (event) {
case LI_STREAM_NEW_DATA:
if (!stream->out->is_closed && NULL != stream->source) {
li_chunkqueue_steal_all(stream->out, stream->source->out);
stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed;
li_stream_notify(stream); /* tell plain_source to do SSL_read */
}
if (stream->out->is_closed) {
li_stream_disconnect(stream);
}
break;
case LI_STREAM_NEW_CQLIMIT:
break;
case LI_STREAM_CONNECTED_DEST: /* plain_source */
break;
case LI_STREAM_CONNECTED_SOURCE: /* io in */
break;
case LI_STREAM_DISCONNECTED_DEST: /* plain_source */
if (!stream->out->is_closed || 0 != stream->out->length) {
f_abort_gnutls(f); /* didn't read everything */
}
break;
case LI_STREAM_DISCONNECTED_SOURCE: /* io in disconnect */
if (!stream->out->is_closed) {
f_abort_gnutls(f); /* conn aborted */
}
break;
case LI_STREAM_DESTROY:
f_release(f);
break;
}
}
/* ssl (plain) -> app */
static void stream_plain_source_cb(liStream *stream, liStreamEvent event) {
liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_source);
switch (event) {
case LI_STREAM_NEW_DATA:
do_gnutls_read(f);
if (f->write_wants_read) do_gnutls_write(f);
li_stream_notify(stream);
break;
case LI_STREAM_NEW_CQLIMIT:
break;
case LI_STREAM_CONNECTED_DEST: /* app */
break;
case LI_STREAM_CONNECTED_SOURCE: /* crypt_drain */
break;
case LI_STREAM_DISCONNECTED_DEST: /* app */
if (!stream->out->is_closed || 0 != stream->out->length) {
f_abort_gnutls(f); /* didn't read everything */
}
break;
case LI_STREAM_DISCONNECTED_SOURCE: /* crypt_drain */
if (!stream->out->is_closed) {
f_abort_gnutls(f); /* didn't get everything */
}
break;
case LI_STREAM_DESTROY:
f_release(f);
break;
}
}
/* app -> ssl (plain) */
static void stream_plain_drain_cb(liStream *stream, liStreamEvent event) {
liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_drain);
switch (event) {
case LI_STREAM_NEW_DATA:
if (!stream->out->is_closed && NULL != stream->source) {
li_chunkqueue_steal_all(stream->out, stream->source->out);
stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed;
}
do_gnutls_write(f);
if (stream->out->is_closed) {
li_stream_disconnect(stream);
stream->out->is_closed = FALSE;
}
break;
case LI_STREAM_NEW_CQLIMIT:
break;
case LI_STREAM_CONNECTED_DEST: /* crypt_source */
break;
case LI_STREAM_CONNECTED_SOURCE: /* app */
break;
case LI_STREAM_DISCONNECTED_DEST:
if (!stream->out->is_closed || 0 != stream->out->length) {
f_abort_gnutls(f); /* didn't read everything */
}
break;
case LI_STREAM_DISCONNECTED_SOURCE:
if (!stream->out->is_closed) {
f_abort_gnutls(f); /* didn't get everything */
}
break;
case LI_STREAM_DESTROY:
f_release(f);
break;
}
}
static void stream_crypt_source_limit_notify_cb(gpointer context, gboolean locked) {
liGnuTLSFilter *f = context;
if (!locked) li_stream_again_later(&f->plain_drain);
}
static int post_client_hello_cb(gnutls_session_t session) {
liGnuTLSFilter *f = gnutls_session_get_ptr(session);
return f->callbacks->post_client_hello_cb(f, f->callback_data);
}
liGnuTLSFilter* li_gnutls_filter_new(
liServer *srv, liWorker *wrk,
const liGnuTLSFilterCallbacks *callbacks, gpointer data,
gnutls_session_t session, liStream *crypt_source, liStream *crypt_drain
) {
liEventLoop *loop = crypt_source->loop;
liGnuTLSFilter *f;
liCQLimit *out_limit;
f = g_slice_new0(liGnuTLSFilter);
f->refcount = 5; /* 1 + 4 streams */
f->callbacks = callbacks;
f->callback_data = data;
f->srv = srv;
f->wrk = wrk;
f->session = session;
gnutls_transport_set_ptr(f->session, (gnutls_transport_ptr_t) f);
gnutls_transport_set_push_function(f->session, stream_push);
gnutls_transport_set_vec_push_function(f->session, stream_pushv);
gnutls_transport_set_pull_function(f->session, stream_pull);
gnutls_session_set_ptr(f->session, f);
gnutls_handshake_set_post_client_hello_function(f->session, post_client_hello_cb);
f->initial_handshaked_finished = 0;
f->closing = f->aborted = 0;
f->write_wants_read = 0;
li_stream_init(&f->crypt_source, loop, stream_crypt_source_cb);
li_stream_init(&f->crypt_drain, loop, stream_crypt_drain_cb);
li_stream_init(&f->plain_source, loop, stream_plain_source_cb);
li_stream_init(&f->plain_drain, loop, stream_plain_drain_cb);
/* "virtual" connections - the content goes through SSL */
li_stream_connect(&f->plain_drain, &f->crypt_source);
li_stream_connect(&f->crypt_drain, &f->plain_source);
li_stream_connect(crypt_source, &f->crypt_drain);
li_stream_connect(&f->crypt_source, crypt_drain);
/* separate limit for buffer of encrypted data
*
* f->plain_drain is already connected to f->crypt_source,
* so they won't share the same limit */
out_limit = li_cqlimit_new();
out_limit->notify = stream_crypt_source_limit_notify_cb;
out_limit->context = f;
li_cqlimit_set_limit(out_limit, 32*1024);
li_chunkqueue_set_limit(crypt_drain->out, out_limit);
li_chunkqueue_set_limit(f->crypt_source.out, out_limit);
li_cqlimit_release(out_limit);
return f;
}
void li_gnutls_filter_free(liGnuTLSFilter *f) {
assert(NULL != f->callbacks);
f->callbacks = NULL;
f->callback_data = NULL;
f_close_gnutls(f);
li_stream_release(&f->crypt_source);
li_stream_release(&f->crypt_drain);
li_stream_release(&f->plain_source);
li_stream_release(&f->plain_drain);
f_release(f);
}

29
src/modules/gnutls_filter.h

@ -0,0 +1,29 @@
#ifndef _LIGHTTPD_GNUTLS_FILTER_H_
#define _LIGHTTPD_GNUTLS_FILTER_H_
#include <lighttpd/base.h>
#include <gnutls/gnutls.h>
typedef struct liGnuTLSFilter liGnuTLSFilter;
typedef void (*liGnuTLSFilterHandshakeCB)(liGnuTLSFilter *f, gpointer data, liStream *plain_source, liStream *plain_drain);
typedef void (*liGnuTLSFilterClosedCB)(liGnuTLSFilter *f, gpointer data);
typedef int (*liGnuTLSFilterPostClientHelloCB)(liGnuTLSFilter *f, gpointer data);
typedef struct liGnuTLSFilterCallbacks liGnuTLSFilterCallbacks;
struct liGnuTLSFilterCallbacks {
liGnuTLSFilterHandshakeCB handshake_cb; /* called after initial handshake is done */
liGnuTLSFilterClosedCB closed_cb;
liGnuTLSFilterPostClientHelloCB post_client_hello_cb;
};
LI_API liGnuTLSFilter* li_gnutls_filter_new(
liServer *srv, liWorker *wrk,
const liGnuTLSFilterCallbacks *callbacks, gpointer data,
gnutls_session_t session, liStream *crypt_source, liStream *crypt_drain);
/* doesn't call closed_cb; but you can call this from closed_cb */
LI_API void li_gnutls_filter_free(liGnuTLSFilter *f);
#endif

375
src/modules/mod_gnutls.c

@ -1,6 +1,8 @@
#include <lighttpd/base.h>
#include "gnutls_filter.h"
#include <gnutls/gnutls.h>
#include <glib-2.0/glib/galloca.h>
@ -17,13 +19,10 @@ struct mod_connection_ctx {
liConnection *con;
mod_context *ctx;
int con_events;
liJob con_handle_events_job;
unsigned int gnutls_again_in_progress:1;
liGnuTLSFilter *tls_filter;
unsigned int initial_handshaked_finished:1;
unsigned int client_initiated_renegotiation:1;
liIOStream *sock_stream;
gpointer simple_socket_data;
};
struct mod_context {
@ -92,306 +91,175 @@ error0:
return NULL;
}
static void tcp_io_cb(liIOStream *stream, liIOStreamEvent event) {
mod_connection_ctx *conctx = stream->data;
assert(NULL == conctx->sock_stream || conctx->sock_stream == stream);
if (LI_IOSTREAM_DESTROY == event) {
li_stream_simple_socket_close(stream, TRUE); /* kill it, ssl sent an close alert message */
}
li_connection_simple_tcp(&conctx->con, stream, &conctx->simple_socket_data, event);
static void mod_gnutls_con_handle_events_cb(liJob *job) {
mod_connection_ctx *conctx = LI_CONTAINER_OF(job, mod_connection_ctx, con_handle_events_job);
liConnection *con = conctx->con;
if (NULL != conctx->con && conctx->con->out_has_all_data
&& (NULL == stream->stream_out.out || 0 == stream->stream_out.out->length)
&& li_streams_empty(conctx->con->con_sock.raw_out, NULL)) {
li_connection_request_done(conctx->con);
}
conctx->gnutls_again_in_progress = 0;
connection_handle_io(con);
switch (event) {
case LI_IOSTREAM_DESTROY:
assert(NULL == conctx->sock_stream);
assert(NULL == conctx->tls_filter);
assert(NULL == conctx->con);
stream->data = NULL;
g_slice_free(mod_connection_ctx, conctx);
return;
default:
break;
}
}
static void mod_gnutls_io_cb(struct ev_loop *loop, ev_io *w, int revents) {
liConnection *con = (liConnection*) w->data;
mod_connection_ctx *conctx = con->srv_sock_data;
static void handshake_cb(liGnuTLSFilter *f, gpointer data, liStream *plain_source, liStream *plain_drain) {
mod_connection_ctx *conctx = data;
liConnection *con = conctx->con;
UNUSED(f);
if (revents & EV_ERROR) {
/* if this happens, we have a serious bug in the event handling */
VR_ERROR(con->mainvr, "%s", "EV_ERROR encountered, dropping connection!");
li_connection_error(con);
return;
if (NULL != con) {
li_stream_connect(plain_source, con->con_sock.raw_in);
li_stream_connect(con->con_sock.raw_out, plain_drain);
} else {
li_stream_reset(plain_source);
li_stream_reset(plain_drain);
}
}
con->can_read = TRUE;
con->can_write = TRUE;
static void close_cb(liGnuTLSFilter *f, gpointer data) {
mod_connection_ctx *conctx = data;
liConnection *con = conctx->con;
assert(conctx->tls_filter == f);
conctx->tls_filter = NULL;
li_gnutls_filter_free(f);
gnutls_deinit(conctx->session);
/* disable all events; they will get reactivated later */
li_ev_io_set_events(loop, w, 0);
if (NULL != conctx->ctx) {
mod_gnutls_context_release(conctx->ctx);
conctx->ctx = NULL;
}
li_job_now(&con->wrk->loop.jobqueue, &conctx->con_handle_events_job);
if (NULL != conctx->con) {
liStream *raw_out = con->con_sock.raw_out, *raw_in = con->con_sock.raw_in;
assert(con->con_sock.data == conctx);
conctx->con = NULL;
con->con_sock.data = NULL;
li_stream_acquire(raw_in);
li_stream_reset(raw_out);
li_stream_reset(raw_in);
li_stream_release(raw_in);
}
if (NULL != conctx->sock_stream) {
liIOStream *stream = conctx->sock_stream;
conctx->sock_stream = NULL;
li_iostream_release(stream);
}
}
static int mod_gnutls_post_client_hello_cb(gnutls_session_t session) {
gnutls_protocol_t p = gnutls_protocol_get_version(session);
mod_connection_ctx *conctx = gnutls_session_get_ptr(session);
static int post_client_hello_cb(liGnuTLSFilter *f, gpointer data) {
mod_connection_ctx *conctx = data;
gnutls_protocol_t p = gnutls_protocol_get_version(conctx->session);
UNUSED(f);
if (conctx->ctx->protect_against_beast) {
if (GNUTLS_SSL3 == p || GNUTLS_TLS1_0 == p) {
gnutls_priority_set(session, conctx->ctx->server_priority_beast);
gnutls_priority_set(conctx->session, conctx->ctx->server_priority_beast);
}
}
return GNUTLS_E_SUCCESS;
}
static gboolean mod_gnutls_con_new(liConnection *con) {
static const liGnuTLSFilterCallbacks filter_callbacks = {
handshake_cb,
close_cb,
post_client_hello_cb
};
static void gnutlc_tcp_finished(liConnection *con, gboolean aborted) {
mod_connection_ctx *conctx = con->con_sock.data;
UNUSED(aborted);
con->info.is_ssl = FALSE;
con->con_sock.callbacks = NULL;
if (NULL != conctx) {
assert(con == conctx->con);
close_cb(conctx->tls_filter, conctx);
}
{
liStream *raw_out = con->con_sock.raw_out, *raw_in = con->con_sock.raw_in;
con->con_sock.raw_out = con->con_sock.raw_in = NULL;
if (NULL != raw_out) { li_stream_reset(raw_out); li_stream_release(raw_out); }
if (NULL != raw_in) { li_stream_reset(raw_in); li_stream_release(raw_in); }
}
}
static const liConnectionSocketCallbacks gnutls_tcp_cbs = {
gnutlc_tcp_finished
};
static gboolean mod_gnutls_con_new(liConnection *con, int fd) {
liEventLoop *loop = &con->wrk->loop;
liServer *srv = con->srv;
mod_context *ctx = con->srv_sock->data;
mod_connection_ctx *conctx = g_slice_new0(mod_connection_ctx);
mod_connection_ctx *conctx;
gnutls_session_t session;
int r;
con->srv_sock_data = NULL;
if (GNUTLS_E_SUCCESS != (r = gnutls_init(&conctx->session, GNUTLS_SERVER))) {
if (GNUTLS_E_SUCCESS != (r = gnutls_init(&session, GNUTLS_SERVER))) {
ERROR(srv, "gnutls_init (%s): %s",
gnutls_strerror_name(r), gnutls_strerror(r));
g_slice_free(mod_connection_ctx, conctx);
return FALSE;
}
mod_gnutls_context_acquire(ctx);
if (GNUTLS_E_SUCCESS != (r = gnutls_priority_set(conctx->session, ctx->server_priority))) {
if (GNUTLS_E_SUCCESS != (r = gnutls_priority_set(session, ctx->server_priority))) {
ERROR(srv, "gnutls_priority_set (%s): %s",
gnutls_strerror_name(r), gnutls_strerror(r));
goto fail;
}
if (GNUTLS_E_SUCCESS != (r = gnutls_credentials_set(conctx->session, GNUTLS_CRD_CERTIFICATE, ctx->server_cert))) {
if (GNUTLS_E_SUCCESS != (r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, ctx->server_cert))) {
ERROR(srv, "gnutls_credentials_set (%s): %s",
gnutls_strerror_name(r), gnutls_strerror(r));
goto fail;
}
gnutls_transport_set_ptr(conctx->session, (gnutls_transport_ptr_t)(intptr_t) con->sock_watcher.fd);
gnutls_session_set_ptr(conctx->session, conctx);
gnutls_handshake_set_post_client_hello_function(conctx->session, mod_gnutls_post_client_hello_cb);
conctx = g_slice_new0(mod_connection_ctx);
conctx->session = session;
conctx->sock_stream = li_iostream_new(con->wrk, fd, tcp_io_cb, conctx);
conctx->tls_filter = li_gnutls_filter_new(srv, con->wrk, &filter_callbacks, conctx, conctx->session,
&conctx->sock_stream->stream_in, &conctx->sock_stream->stream_out);
conctx->con = con;
conctx->ctx = ctx;
li_job_init(&conctx->con_handle_events_job, mod_gnutls_con_handle_events_cb);
conctx->con_events = 0;
conctx->gnutls_again_in_progress = 0;
conctx->initial_handshaked_finished = 0;
conctx->client_initiated_renegotiation = 0;
con->srv_sock_data = conctx;
con->con_sock.data = conctx;
con->con_sock.callbacks = &gnutls_tcp_cbs;
con->con_sock.raw_out = li_stream_plug_new(loop);
con->con_sock.raw_in = li_stream_plug_new(loop);
con->info.is_ssl = TRUE;
ev_set_cb(&con->sock_watcher, mod_gnutls_io_cb);
return TRUE;
fail:
gnutls_deinit(conctx->session);
gnutls_deinit(session);
mod_gnutls_context_release(ctx);
g_slice_free(mod_connection_ctx, conctx);
return FALSE;
}
static void mod_gnutls_con_close(liConnection *con) {
mod_connection_ctx *conctx = con->srv_sock_data;
if (!conctx) return;
gnutls_bye(conctx->session, GNUTLS_SHUT_RDWR);
gnutls_deinit(conctx->session);
mod_gnutls_context_release(conctx->ctx);
con->srv_sock_data = NULL;
con->info.is_ssl = FALSE;
li_job_clear(&conctx->con_handle_events_job);
g_slice_free(mod_connection_ctx, conctx);
}
static void mod_gnutls_update_events(liConnection *con, int events) {
mod_connection_ctx *conctx = con->srv_sock_data;
/* new events -> add them to socket watcher too */
if (!conctx->gnutls_again_in_progress && 0 != (events & ~conctx->con_events)) {
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, events);
}
conctx->con_events = events;
}
static liNetworkStatus mod_gnutls_handle_error(liConnection *con, mod_connection_ctx *conctx, const char *gnutlsfunc, off_t len, int r) {
switch (r) {
case GNUTLS_E_AGAIN:
conctx->gnutls_again_in_progress = TRUE;
li_ev_io_set_events(con->wrk->loop, &con->sock_watcher, gnutls_record_get_direction(conctx->session) ? EV_WRITE : EV_READ);
return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT;
case GNUTLS_E_REHANDSHAKE:
if (conctx->initial_handshaked_finished) {
VR_ERROR(con->mainvr, "%s", "gnutls: client initiated renegotitation, closing connection");
return LI_NETWORK_STATUS_FATAL_ERROR;
}
break;
case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
/* connection abort */
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
case GNUTLS_E_UNKNOWN_CIPHER_SUITE:
case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
VR_DEBUG(con->mainvr, "%s (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
default:
if (gnutls_error_is_fatal(r)) {
VR_ERROR(con->mainvr, "%s (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
return LI_NETWORK_STATUS_FATAL_ERROR;
} else {
VR_ERROR(con->mainvr, "%s non fatal (%s): %s", gnutlsfunc,
gnutls_strerror_name(r), gnutls_strerror(r));
}
}
return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT;
}
static liNetworkStatus mod_gnutls_do_handshake(liConnection *con, mod_connection_ctx *conctx) {
int r;
if (GNUTLS_E_SUCCESS != (r = gnutls_handshake(conctx->session))) {
return mod_gnutls_handle_error(con, conctx, "gnutls_handshake", 0, r);
} else {
conctx->initial_handshaked_finished = 1;
return LI_NETWORK_STATUS_SUCCESS;
}
}
static liNetworkStatus mod_gnutls_con_write(liConnection *con, goffset write_max) {
const ssize_t blocksize = 16*1024; /* 16k */
char *block_data;
off_t block_len;
ssize_t r;
liChunkIter ci;
liChunkQueue *cq = con->raw_out;
mod_connection_ctx *conctx = con->srv_sock_data;
GError *err = NULL;
if (!conctx->initial_handshaked_finished) {
liNetworkStatus res = mod_gnutls_do_handshake(con, conctx);
if (res != LI_NETWORK_STATUS_SUCCESS) return res;
}
do {
if (0 == cq->length) {
return LI_NETWORK_STATUS_SUCCESS;
}
ci = li_chunkqueue_iter(cq);
switch (li_chunkiter_read(ci, 0, blocksize, &block_data, &block_len, &err)) {
case LI_HANDLER_GO_ON:
break;
case LI_HANDLER_ERROR:
if (NULL != err) {
VR_ERROR(con->mainvr, "Couldn't read data from chunkqueue: %s", err->message);
g_error_free(err);
}
default:
return LI_NETWORK_STATUS_FATAL_ERROR;
}
if (0 >= (r = gnutls_record_send(conctx->session, block_data, block_len))) {
return mod_gnutls_handle_error(con, conctx, "gnutls_record_send", 0, r);
}
/* VR_BACKEND_LINES(con->mainvr, block_data, "written %i from %i bytes: ", (int) r, (int) block_len); */
li_chunkqueue_skip(cq, r);
write_max -= r;
} while (r == block_len && write_max > 0);
if (0 != cq->length) {
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
}
return LI_NETWORK_STATUS_SUCCESS;
}
static liNetworkStatus mod_gnutls_con_read(liConnection *con) {
liChunkQueue *cq = con->raw_in;
mod_connection_ctx *conctx = con->srv_sock_data;
const ssize_t blocksize = 16*1024; /* 16k */
off_t max_read = 16 * blocksize; /* 256k */
ssize_t r;
off_t len = 0;
if (!conctx->initial_handshaked_finished) {
liNetworkStatus res = mod_gnutls_do_handshake(con, conctx);
if (res != LI_NETWORK_STATUS_SUCCESS) return res;
}
if (cq->limit && cq->limit->limit > 0) {
if (max_read > cq->limit->limit - cq->limit->current) {
max_read = cq->limit->limit - cq->limit->current;
if (max_read <= 0) {
max_read = 0; /* we still have to read something */
VR_ERROR(con->mainvr, "li_network_read: fd %i should be disabled as chunkqueue is already full",
con->sock_watcher.fd);
}
}
}
do {
liBuffer *buf;
gboolean cq_buf_append;
buf = li_chunkqueue_get_last_buffer(cq, 1024);
cq_buf_append = (buf != NULL);
if (buf != NULL) {
/* use last buffer as raw_in_buffer; they should be the same anyway */
if (G_UNLIKELY(buf != con->raw_in_buffer)) {
li_buffer_acquire(buf);
li_buffer_release(con->raw_in_buffer);
con->raw_in_buffer = buf;
}
} else {
buf = con->raw_in_buffer;
if (buf != NULL && buf->alloc_size - buf->used < 1024) {
/* release *buffer */
li_buffer_release(buf);
con->raw_in_buffer = buf = NULL;
}
if (buf == NULL) {
con->raw_in_buffer = buf = li_buffer_new(blocksize);
}
}
assert(con->raw_in_buffer == buf);
if (0 > (r = gnutls_record_recv(conctx->session, buf->addr + buf->used, buf->alloc_size - buf->used))) {
return mod_gnutls_handle_error(con, conctx, "gnutls_record_recv", len, r);
} else if (r == 0) {
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
}
if (cq_buf_append) {
li_chunkqueue_update_last_buffer_size(cq, r);
} else {
gsize offset;
li_buffer_acquire(buf);
offset = buf->used;
buf->used += r;
li_chunkqueue_append_buffer2(cq, buf, offset, r);
}
if (buf->alloc_size - buf->used < 1024) {
/* release *buffer */
li_buffer_release(buf);
con->raw_in_buffer = buf = NULL;
}
len += r;
} while (len < max_read);
return LI_NETWORK_STATUS_SUCCESS;
}
static void mod_gnutls_sock_release(liServerSocket *srv_sock) {
mod_context *ctx = srv_sock->data;
@ -401,7 +269,6 @@ static void mod_gnutls_sock_release(liServerSocket *srv_sock) {
mod_gnutls_context_release(ctx);
}
static void gnutls_setup_listen_cb(liServer *srv, int fd, gpointer data) {
mod_context *ctx = data;
liServerSocket *srv_sock;
@ -416,12 +283,8 @@ static void gnutls_setup_listen_cb(liServer *srv, int fd, gpointer data) {
srv_sock->data = ctx; /* transfer ownership, no refcount change */
srv_sock->write_cb = mod_gnutls_con_write;
srv_sock->read_cb = mod_gnutls_con_read;
srv_sock->new_cb = mod_gnutls_con_new;
srv_sock->close_cb = mod_gnutls_con_close;
srv_sock->release_cb = mod_gnutls_sock_release;
srv_sock->update_events_cb = mod_gnutls_update_events;
}
static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer userdata) {

Loading…
Cancel
Save