From bef05e0b2ea11b0afd8137613b394396edf93cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Fri, 11 Nov 2011 10:20:05 +0100 Subject: [PATCH] new module: mod_gnutls --- configure.ac | 17 ++ src/CMakeLists.txt | 18 +- src/modules/Makefile.am | 19 +- src/modules/mod_gnutls.c | 591 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 636 insertions(+), 9 deletions(-) create mode 100644 src/modules/mod_gnutls.c diff --git a/configure.ac b/configure.ac index 7d36375..f998b44 100644 --- a/configure.ac +++ b/configure.ac @@ -113,6 +113,23 @@ if test x$lfs = xtrue; then CFLAGS="${CFLAGS} -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES" fi +dnl Check for gnutls +AC_ARG_WITH([gnutls], [AS_HELP_STRING([--with-gnutls],[gnutls library for ssl/tls])], +[WITH_GNUTLS=$withval],[WITH_GNUTLS=no]) + +if test "$WITH_GNUTLS" != "no"; then + PKG_CHECK_MODULES([GNUTLS], [gnutls],[],[ + AC_MSG_ERROR([gnutls not found]) + ]) + + AC_SUBST([GNUTLS_CFLAGS]) + AC_SUBST([GNUTLS_LIBS]) + USE_GNUTLS=true +fi + +AM_CONDITIONAL([USE_GNUTLS], [test "$USE_GNUTLS" = "true"]) + + dnl Check for lua AC_MSG_CHECKING([for lua]) AC_ARG_WITH([lua], [AS_HELP_STRING([--with-lua],[lua engine (recommended)])], diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b2120d..3765421 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,14 +17,14 @@ cmake_policy(VERSION 2.6.4) ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES) -# OPTION(WITH_OPENSSL "with openssl-support [default: on]" ON) OPTION(WITH_LUA "with lua 5.1 for lua-configfile [default: on]" ON) OPTION(WITHOUT_CONFIG_PARSER "without standard config parser [default: off]" OFF) -OPTION(WITH_OPENSSL "with openssl-support [default: off]") +OPTION(WITH_OPENSSL "with openssl support [default: off]") +OPTION(WITH_GNUTLS "with gnutls support [default: off]") OPTION(BUILD_STATIC "build a static lighttpd with all modules added") OPTION(BUILD_EXTRA_WARNINGS "extra warnings") -OPTION(WITH_BZIP "with bzip2-support for mod_deflate") -OPTION(WITH_ZLIB "with deflate-support for mod_deflate") +OPTION(WITH_BZIP "with bzip2 support for mod_deflate") +OPTION(WITH_ZLIB "with deflate support for mod_deflate") OPTION(WITH_PROFILER "with memory profiler") OPTION(BUILD_UNIT_TESTS "build unit tests for testing") @@ -104,6 +104,10 @@ IF(WITH_LUA) SET(HAVE_LUA_H 1 "Have liblua header") ENDIF(WITH_LUA) +IF(WITH_GNUTLS) + pkg_search_module(GNUTLS REQUIRED gnutls) +ENDIF(WITH_GNUTLS) + IF(WITH_OPENSSL) CHECK_INCLUDE_FILES(openssl/ssl.h HAVE_OPENSSL_SSL_H) IF(HAVE_OPENSSL_SSL_H) @@ -357,6 +361,12 @@ IF(WITH_LUA) ADD_AND_INSTALL_LIBRARY(mod_lua "modules/mod_lua.c") 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}) +ENDIF(WITH_GNUTLS) + IF(HAVE_LIBSSL AND HAVE_LIBCRYPTO) ADD_AND_INSTALL_LIBRARY(mod_openssl "modules/mod_openssl.c") TARGET_LINK_LIBRARIES(mod_openssl ssl) diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index 3160bef..2c06b87 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -1,7 +1,7 @@ install_libs = common_cflags = -I$(top_srcdir)/include -I$(top_builddir)/include -common_cflags += $(GTHREAD_CFLAGS) $(LIBEV_CFLAGS) $(LUA_CFLAGS) $(OPENSSL_CFLAGS) +common_cflags += $(GTHREAD_CFLAGS) $(LIBEV_CFLAGS) $(LUA_CFLAGS) common_libs = $(GTHREAD_LIBS) $(LIBEV_LIBS) $(LUA_LIBS) common_ldflags = -module -export-dynamic -avoid-version -no-undefined $(common_libs) common_libadd = ../common/liblighttpd2-common.la ../main/liblighttpd2-shared.la @@ -43,8 +43,8 @@ libmod_debug_la_LIBADD = $(common_libadd) if USE_MOD_DEFLATE install_libs += libmod_deflate.la libmod_deflate_la_SOURCES = mod_deflate.c -libmod_deflate_la_LDFLAGS = $(common_ldflags) $(Z_LIB) $(BZ_LIB) -libmod_deflate_la_LIBADD = $(common_libadd) +libmod_deflate_la_LDFLAGS = $(common_ldflags) +libmod_deflate_la_LIBADD = $(common_libadd) $(Z_LIB) $(BZ_LIB) endif install_libs += libmod_dirlist.la @@ -72,6 +72,14 @@ libmod_fortune_la_SOURCES = mod_fortune.c libmod_fortune_la_LDFLAGS = $(common_ldflags) 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_LDFLAGS = $(common_ldflags) +libmod_gnutls_la_LIBADD = $(common_libadd) $(GNUTLS__LIB) +endif + install_libs += libmod_limit.la libmod_limit_la_SOURCES = mod_limit.c libmod_limit_la_LDFLAGS = $(common_ldflags) @@ -92,9 +100,10 @@ libmod_memcached_la_LIBADD = $(common_libadd) if USE_OPENSSL install_libs += libmod_openssl.la +libmod_openssl_la_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_CFLAGS) libmod_openssl_la_SOURCES = mod_openssl.c -libmod_openssl_la_LDFLAGS = $(common_ldflags) $(OPENSSL_LIBS) -libmod_openssl_la_LIBADD = $(common_libadd) +libmod_openssl_la_LDFLAGS = $(common_ldflags) +libmod_openssl_la_LIBADD = $(common_libadd) $(OPENSSL_LIBS) endif install_libs += libmod_progress.la diff --git a/src/modules/mod_gnutls.c b/src/modules/mod_gnutls.c new file mode 100644 index 0000000..593a46a --- /dev/null +++ b/src/modules/mod_gnutls.c @@ -0,0 +1,591 @@ + +#include + +#include +#include + + +LI_API gboolean mod_gnutls_init(liModules *mods, liModule *mod); +LI_API gboolean mod_gnutls_free(liModules *mods, liModule *mod); + + +typedef struct mod_connection_ctx mod_connection_ctx; +typedef struct mod_context mod_context; + +struct mod_connection_ctx { + gnutls_session_t session; + liConnection *con; + mod_context *ctx; + + int con_events; + liJob con_handle_events_job; + + unsigned int gnutls_again_in_progress:1; + + unsigned int initial_handshaked_finished:1; + unsigned int client_initiated_renegotiation:1; +}; + +struct mod_context { + gint refcount; + + gnutls_certificate_credentials_t server_cert; + gnutls_priority_t server_priority; + gnutls_priority_t server_priority_beast; + + unsigned int protect_against_beast:1; +}; + +static void mod_gnutls_context_release(mod_context *ctx) { + if (!ctx) return; + assert(g_atomic_int_get(&ctx->refcount) > 0); + if (g_atomic_int_dec_and_test(&ctx->refcount)) { + gnutls_priority_deinit(ctx->server_priority_beast); + gnutls_priority_deinit(ctx->server_priority); + gnutls_certificate_free_credentials(ctx->server_cert); + + g_slice_free(mod_context, ctx); + } +} + +static void mod_gnutls_context_acquire(mod_context *ctx) { + assert(g_atomic_int_get(&ctx->refcount) > 0); + g_atomic_int_inc(&ctx->refcount); +} + + +static mod_context *mod_gnutls_context_new(liServer *srv) { + mod_context *ctx = g_slice_new0(mod_context); + int r; + + if (GNUTLS_E_SUCCESS != (r = gnutls_certificate_allocate_credentials(&ctx->server_cert))) { + ERROR(srv, "gnutls_certificate_allocate_credentials failed(%s): %s\n", + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error0; + } + + if (GNUTLS_E_SUCCESS != (r = gnutls_priority_init(&ctx->server_priority, "NORMAL", NULL))) { + ERROR(srv, "gnutls_priority_init failed(%s): %s\n", + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error1; + } + + if (GNUTLS_E_SUCCESS != (r = gnutls_priority_init(&ctx->server_priority_beast, "NORMAL:-CIPHER-ALL:+ARCFOUR-128", NULL))) { + ERROR(srv, "gnutls_priority_init failed(%s): %s\n", + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error2; + } + + ctx->refcount = 1; + ctx->protect_against_beast = 1; + + return ctx; + +error2: + gnutls_priority_deinit(ctx->server_priority); + +error1: + gnutls_certificate_free_credentials(ctx->server_cert); + +error0: + g_slice_free(mod_context, ctx); + return NULL; +} + + + + +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; + + conctx->gnutls_again_in_progress = 0; + connection_handle_io(con); +} + +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; + + 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; + } + + con->can_read = TRUE; + con->can_write = TRUE; + + /* disable all events; they will get reactivated later */ + li_ev_io_set_events(loop, w, 0); + + li_job_now(&con->wrk->jobqueue, &conctx->con_handle_events_job); +} + +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); + + if (conctx->ctx->protect_against_beast) { + if (GNUTLS_SSL3 == p || GNUTLS_TLS1_0 == p) { + gnutls_priority_set(session, conctx->ctx->server_priority_beast); + } + } + + return GNUTLS_E_SUCCESS; +} + +static gboolean mod_gnutls_con_new(liConnection *con) { + liServer *srv = con->srv; + mod_context *ctx = con->srv_sock->data; + mod_connection_ctx *conctx = g_slice_new0(mod_connection_ctx); + int r; + + con->srv_sock_data = NULL; + + if (GNUTLS_E_SUCCESS != (r = gnutls_init(&conctx->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))) { + 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))) { + 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->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->info.is_ssl = TRUE; + + ev_set_cb(&con->sock_watcher, mod_gnutls_io_cb); + + return TRUE; + +fail: + gnutls_deinit(conctx->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; + + 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(con->mainvr, ci, 0, blocksize, &block_data, &block_len)) { + case LI_HANDLER_GO_ON: + break; + case LI_HANDLER_ERROR: + 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; + + if (!ctx) return; + + mod_gnutls_context_release(ctx); +} + + +static void gnutls_setup_listen_cb(liServer *srv, int fd, gpointer data) { + mod_context *ctx = data; + liServerSocket *srv_sock; + UNUSED(data); + + if (-1 == fd) { + mod_gnutls_context_release(ctx); + return; + } + + srv_sock = li_server_listen(srv, fd); + + 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) { + mod_context *ctx; + GHashTableIter hti; + gpointer hkey, hvalue; + GString *htkey; + liValue *htval; + int r; + + /* setup defaults */ + GString *ipstr = NULL; + const char + *priority = NULL, + *pemfile = NULL, *ca_file = NULL; + gboolean + protect_against_beast = TRUE; + + UNUSED(p); UNUSED(userdata); + + if (val->type != LI_VALUE_HASH) { + ERROR(srv, "%s", "gnutls expects a hash as parameter"); + return FALSE; + } + + g_hash_table_iter_init(&hti, val->data.hash); + while (g_hash_table_iter_next(&hti, &hkey, &hvalue)) { + htkey = hkey; htval = hvalue; + + if (g_str_equal(htkey->str, "listen")) { + if (htval->type != LI_VALUE_STRING) { + ERROR(srv, "%s", "gnutls listen expects a string as parameter"); + return FALSE; + } + ipstr = htval->data.string; + } else if (g_str_equal(htkey->str, "pemfile")) { + if (htval->type != LI_VALUE_STRING) { + ERROR(srv, "%s", "gnutls pemfile expects a string as parameter"); + return FALSE; + } + pemfile = htval->data.string->str; + } else if (g_str_equal(htkey->str, "ca-file")) { + if (htval->type != LI_VALUE_STRING) { + ERROR(srv, "%s", "gnutls ca-file expects a string as parameter"); + return FALSE; + } + ca_file = htval->data.string->str; + } else if (g_str_equal(htkey->str, "priority")) { + if (htval->type != LI_VALUE_STRING) { + ERROR(srv, "%s", "gnutls priority expects a string as parameter"); + return FALSE; + } + priority = htval->data.string->str; + } else if (g_str_equal(htkey->str, "protect-against-beast")) { + if (htval->type != LI_VALUE_BOOLEAN) { + ERROR(srv, "%s", "gnutls protect-against-beast expects a boolean as parameter"); + return FALSE; + } + protect_against_beast = htval->data.boolean; + } + } + + if (!ipstr) { + ERROR(srv, "%s", "gnutls needs a listen parameter"); + return FALSE; + } + + if (!pemfile) { + ERROR(srv, "%s", "gnutls needs a pemfile"); + return FALSE; + } + + if (!(ctx = mod_gnutls_context_new(srv))) return FALSE; + + ctx->protect_against_beast = protect_against_beast; + + if (GNUTLS_E_SUCCESS != (r = gnutls_certificate_set_x509_key_file(ctx->server_cert, pemfile, pemfile, GNUTLS_X509_FMT_PEM))) { + ERROR(srv, "gnutls_certificate_set_x509_key_file failed(certfile '%s', keyfile '%s', PEM) (%s): %s\n", + pemfile, pemfile, + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error_free_ctx; + } + + if ((NULL != ca_file) && GNUTLS_E_SUCCESS != (r = gnutls_certificate_set_x509_trust_file(ctx->server_cert, ca_file, GNUTLS_X509_FMT_PEM))) { + ERROR(srv, "gnutls_certificate_set_x509_trust_file failed(cafile '%s', PEM) (%s): %s\n", + ca_file, + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error_free_ctx; + } + + if (priority) { + const char *errpos = NULL; + gnutls_priority_t prio; + GString *s = srv->main_worker->tmp_str; + + if (GNUTLS_E_SUCCESS != (r = gnutls_priority_init(&prio, priority, &errpos))) { + ERROR(srv, "gnutls_priority_init failed(priority '%s', error at '%s') (%s): %s\n", + priority, errpos, + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error_free_ctx; + } + + gnutls_priority_deinit(ctx->server_priority); + ctx->server_priority = prio; + + if (protect_against_beast) { + g_string_assign(s, priority); + g_string_append_len(s, CONST_STR_LEN(":-CIPHER-ALL:+ARCFOUR-128")); + if (GNUTLS_E_SUCCESS != (r = gnutls_priority_init(&prio, priority, &errpos))) { + ERROR(srv, "gnutls_priority_init failed(priority '%s', error at '%s') (%s): %s\n", + priority, errpos, + gnutls_strerror_name(r), gnutls_strerror(r)); + goto error_free_ctx; + } + + gnutls_priority_deinit(ctx->server_priority_beast); + ctx->server_priority_beast = prio; + } + } + + li_angel_listen(srv, ipstr, gnutls_setup_listen_cb, ctx); + + return TRUE; + +error_free_ctx: + if (ctx) { + mod_gnutls_context_release(ctx); + } + + return FALSE; +} + +static const liPluginOption options[] = { + { NULL, 0, 0, NULL } +}; + +static const liPluginAction actions[] = { + { NULL, NULL, NULL } +}; + +static const liPluginSetup setups[] = { + { "gnutls", gnutls_setup, NULL }, + + { NULL, NULL, NULL } +}; + + +static void plugin_init(liServer *srv, liPlugin *p, gpointer userdata) { + UNUSED(srv); UNUSED(userdata); + + p->options = options; + p->actions = actions; + p->setups = setups; +} + +gboolean mod_gnutls_init(liModules *mods, liModule *mod) { + MODULE_VERSION_CHECK(mods); + + gnutls_global_init(); + + mod->config = li_plugin_register(mods->main, "mod_gnutls", plugin_init, NULL); + + return mod->config != NULL; +} + +gboolean mod_gnutls_free(liModules *mods, liModule *mod) { + if (mod->config) + li_plugin_free(mods->main, mod->config); + + gnutls_global_deinit(); + + return TRUE; +}