Browse Source

Add mod_openssl

personal/stbuehler/wip
Stefan Bühler 12 years ago
parent
commit
e7b7b1d82c
  1. 54
      configure.ac
  2. 35
      src/CMakeLists.txt
  3. 7
      src/modules/Makefile.am
  4. 493
      src/modules/mod_openssl.c

54
configure.ac

@ -149,6 +149,60 @@ if test x$ipv6 = xtrue; then
fi
fi
dnl Check for openssl
AC_MSG_CHECKING(for OpenSSL)
AC_ARG_WITH(openssl,
AC_HELP_STRING([--with-openssl@<:@=DIR@:>@],[Include openssl support (default no)]),
[WITH_OPENSSL=$withval],[WITH_OPENSSL=no])
OPENSSL_CFLAGS=""
OPENSSL_LIBS=""
if test "$WITH_OPENSSL" != "no"; then
use_openssl=yes
if test "$WITH_OPENSSL" != "yes"; then
OPENSSL_CFLAGS="-I$WITH_OPENSSL/include"
OPENSSL_LIBS="-L$WITH_OPENSSL/lib"
fi
else
use_openssl=no
fi
AC_MSG_RESULT([$use_openssl])
AC_ARG_WITH(openssl-includes,
AC_HELP_STRING([--with-openssl-includes=DIR],[OpenSSL includes]),
[ use_openssl=yes OPENSSL_CFLAGS="-I$withval" ]
)
AC_ARG_WITH(openssl-libs,
AC_HELP_STRING([--with-openssl-libs=DIR],[OpenSSL libraries]),
[ use_openssl=yes OPENSSL_LIBS="-L$withval" ]
)
AC_ARG_WITH(kerberos5,
AC_HELP_STRING([--with-kerberos5],[use Kerberos5 support with OpenSSL]),
[ use_kerberos=yes ], [use_kerberos=no]
)
if test "x$use_openssl" = "xyes"; then
if test "x$use_kerberos" != "xyes"; then
OPENSSL_CFLAGS="$OPENSSL_CFLAGS -DOPENSSL_NO_KRB5"
fi
AC_CHECK_HEADERS([openssl/ssl.h])
OLDLIBS="$LIBS"
AC_CHECK_LIB(crypto, BIO_f_base64, [
AC_CHECK_LIB(ssl, SSL_new, [ OPENSSL_LIBS="$OPENSSL_LIBS -lssl -lcrypto"
AC_DEFINE(HAVE_LIBSSL, [], [Have libssl]) ], [], [ -lcrypto "$DL_LIB" ])
], [], [])
LIBS="$OLDLIBS"
AC_SUBST(OPENSSL_CFLAGS)
AC_SUBST(OPENSSL_LIBS)
fi
# check for extra compiler options (warning options)
if test "${GCC}" = "yes"; then
CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic -std=gnu99"

35
src/CMakeLists.txt

@ -19,6 +19,7 @@ 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(WITH_OPENSSL "with openssl-support [default: off]")
OPTION(BUILD_STATIC "build a static lighttpd with all modules added")
OPTION(BUILD_EXTRA_WARNINGS "extra warnings")
@ -71,17 +72,6 @@ CHECK_C_SOURCE_COMPILES("
return 0;
}" HAVE_IPV6)
# IF(WITH_OPENSSL)
# CHECK_INCLUDE_FILES(openssl/ssl.h HAVE_OPENSSL_SSL_H)
# IF(HAVE_OPENSSL_SSL_H)
# CHECK_LIBRARY_EXISTS(crypto BIO_f_base64 "" HAVE_LIBCRYPTO)
# IF(HAVE_LIBCRYPTO)
# SET(OPENSSL_NO_KRB5 1)
# CHECK_LIBRARY_EXISTS(ssl SSL_new "" HAVE_LIBSSL)
# ENDIF(HAVE_LIBCRYPTO)
# ENDIF(HAVE_OPENSSL_SSL_H)
# ENDIF(WITH_OPENSSL)
# glib/gthread
pkg_check_modules(GTHREAD REQUIRED gthread-2.0)
pkg_check_modules(GMODULE REQUIRED gmodule-2.0)
@ -92,6 +82,17 @@ IF(WITH_LUA)
SET(HAVE_LUA_H 1 "Have liblua header")
ENDIF(WITH_LUA)
IF(WITH_OPENSSL)
CHECK_INCLUDE_FILES(openssl/ssl.h HAVE_OPENSSL_SSL_H)
IF(HAVE_OPENSSL_SSL_H)
CHECK_LIBRARY_EXISTS(crypto BIO_f_base64 "" HAVE_LIBCRYPTO)
IF(HAVE_LIBCRYPTO)
SET(OPENSSL_NO_KRB5 1)
CHECK_LIBRARY_EXISTS(ssl SSL_new "" HAVE_LIBSSL)
ENDIF(HAVE_LIBCRYPTO)
ENDIF(HAVE_OPENSSL_SSL_H)
ENDIF(WITH_OPENSSL)
IF(NOT BUILD_STATIC)
CHECK_INCLUDE_FILES(dlfcn.h HAVE_DLFCN_H)
ENDIF(NOT BUILD_STATIC)
@ -252,6 +253,13 @@ ADD_AND_INSTALL_LIBRARY(mod_rewrite "modules/mod_rewrite.c")
ADD_AND_INSTALL_LIBRARY(mod_status "modules/mod_status.c")
ADD_AND_INSTALL_LIBRARY(mod_vhost "modules/mod_vhost.c")
IF(HAVE_LIBSSL AND HAVE_LIBCRYPTO)
ADD_AND_INSTALL_LIBRARY(mod_openssl "modules/mod_openssl.c")
TARGET_LINK_LIBRARIES(mod_openssl ssl)
TARGET_LINK_LIBRARIES(mod_openssl crypto)
ENDIF(HAVE_LIBSSL AND HAVE_LIBCRYPTO)
ADD_TARGET_PROPERTIES(common LINK_FLAGS ${COMMON_LDFLAGS})
ADD_TARGET_PROPERTIES(common COMPILE_FLAGS ${COMMON_CFLAGS})
@ -282,11 +290,6 @@ IF(WIN32)
ENDIF(MINGW)
ENDIF(WIN32)
IF(HAVE_LIBSSL AND HAVE_LIBCRYPTO)
TARGET_LINK_LIBRARIES(lighttpd ssl)
TARGET_LINK_LIBRARIES(lighttpd crypto)
ENDIF(HAVE_LIBSSL AND HAVE_LIBCRYPTO)
IF(NOT WIN32)
INSTALL(TARGETS ${L_INSTALL_TARGETS}
RUNTIME DESTINATION ${SBINDIR}

7
src/modules/Makefile.am

@ -1,7 +1,7 @@
install_libs =
common_cflags = -I$(top_srcdir)/include -I$(top_builddir)/include
common_cflags += $(GTHREAD_CFLAGS) $(GMODULE_CFLAGS) $(LIBEV_CFLAGS) $(LUA_CFLAGS)
common_cflags += $(GTHREAD_CFLAGS) $(GMODULE_CFLAGS) $(LIBEV_CFLAGS) $(LUA_CFLAGS) $(OPENSSL_CFLAGS)
common_libadd = $(GTHREAD_LIBS) $(GMODULE_LIBS) $(LIBEV_LIBS) $(LUA_LIBS)
common_ldflags = -module -export-dynamic -avoid-version -no-undefined $(common_libadd)
@ -57,6 +57,11 @@ libmod_fortune_la_SOURCES = mod_fortune.c
libmod_fortune_la_LDFLAGS = $(common_ldflags)
# libmod_fortune_la_LIBADD = $(common_libadd)
install_libs += libmod_openssl.la
libmod_openssl_la_SOURCES = mod_openssl.c
libmod_openssl_la_LDFLAGS = $(common_ldflags) $(OPENSSL_LIBS)
# libmod_openssl_la_LIBADD = $(common_libadd)
install_libs += libmod_redirect.la
libmod_redirect_la_SOURCES = mod_redirect.c
libmod_redirect_la_LDFLAGS = $(common_ldflags)

493
src/modules/mod_openssl.c

@ -0,0 +1,493 @@
/*
* mod_openssl - ssl support
*
* Description:
* mod_openssl listens on separate sockets for ssl connections (https://...)
*
* Setups:
* openssl - setup a ssl socket; takes a hash of following parameters:
* listen - (mandatory) the socket address (same as standard listen)
* pemfile - (mandatory) contains key and direct certificate for the key (PEM format)
* ca-file - contains certificate chain
* ciphers - contains comma separated list of allowed ciphers
* allow-ssl2 - boolean option to allow ssl2 (disabled by default)
*
* Example config:
* setup openssl [ "listen": "0.0.0.0:8443", "pemfile": "server.pem" ];
* setup openssl [ "listen": "[::]:8443", "pemfile": "server.pem" ];
*
* Author:
* Copyright (c) 2009 Stefan Bühler
*/
#include <lighttpd/base.h>
#include <lighttpd/plugin_core.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
LI_API gboolean mod_openssl_init(liModules *mods, liModule *mod);
LI_API gboolean mod_openssl_free(liModules *mods, liModule *mod);
typedef struct openssl_connection_ctx openssl_connection_ctx;
typedef struct openssl_context openssl_context;
struct openssl_connection_ctx {
SSL *ssl;
GByteArray *reuse_read_buffer;
};
struct openssl_context {
SSL_CTX *ssl_ctx;
};
static gboolean openssl_con_new(liConnection *con) {
liServer *srv = con->srv;
openssl_context *ctx = con->srv_sock->data;
openssl_connection_ctx *conctx = g_slice_new0(openssl_connection_ctx);
if (NULL == (conctx->ssl = SSL_new(ctx->ssl_ctx))) {
ERROR(srv, "SSL_new: %s", ERR_error_string(ERR_get_error(), NULL));
goto fail;
}
SSL_set_accept_state(conctx->ssl);
if (1 != (SSL_set_fd(conctx->ssl, con->sock_watcher.fd))) {
ERROR(srv, "SSL_set_fd: %s", ERR_error_string(ERR_get_error(), NULL));
goto fail;
}
con->srv_sock_data = conctx;
return TRUE;
fail:
if (conctx->ssl) {
SSL_free(conctx->ssl);
}
g_slice_free(openssl_connection_ctx, conctx);
return FALSE;
}
static void openssl_con_close(liConnection *con) {
openssl_connection_ctx *conctx = con->srv_sock_data;
if (conctx->ssl) {
SSL_shutdown(conctx->ssl); /* TODO: wait for something??? */
SSL_free(conctx->ssl);
conctx->ssl = FALSE;
}
}
static liNetworkStatus openssl_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;
openssl_connection_ctx *conctx = con->srv_sock_data;
do {
if (0 == cq->length)
return LI_NETWORK_STATUS_SUCCESS;
ci = 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;
}
/**
* SSL_write man-page
*
* WARNING
* When an SSL_write() operation has to be repeated because of
* SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be
* repeated with the same arguments.
*
*/
ERR_clear_error();
if ((r = SSL_write(conctx->ssl, block_data, block_len)) <= 0) {
int ssl_r;
unsigned long err;
switch ((ssl_r = SSL_get_error(conctx->ssl, r))) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return LI_NETWORK_STATUS_WAIT_FOR_EVENT;
case SSL_ERROR_SYSCALL:
/* perhaps we have error waiting in our error-queue */
if (0 != (err = ERR_get_error())) {
do {
VR_ERROR(con->mainvr, "SSL_write: %s",
ERR_error_string(err, NULL));
} while (0 != (err = ERR_get_error()));
} else if (r == -1) {
/* no, but we have errno */
switch(errno) {
case EPIPE:
case ECONNRESET:
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
default:
VR_ERROR(con->mainvr, "SSL_write: %s",
g_strerror(errno));
break;
}
} else {
/* neither error-queue nor errno ? */
VR_ERROR(con->mainvr, "SSL_write: %s",
"Unexpected eof");
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
}
return LI_NETWORK_STATUS_FATAL_ERROR;
case SSL_ERROR_ZERO_RETURN:
/* clean shutdown on the remote side */
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
default:
while (0 != (err = ERR_get_error())) {
VR_ERROR(con->mainvr, "SSL_write: %s",
ERR_error_string(err, NULL));
}
return LI_NETWORK_STATUS_FATAL_ERROR;
}
}
li_chunkqueue_skip(cq, r);
write_max -= r;
} while (r == block_len && write_max > 0);
return LI_NETWORK_STATUS_SUCCESS;
}
static liNetworkStatus openssl_con_read(liConnection *con) {
liChunkQueue *cq = con->raw_in;
openssl_connection_ctx *conctx = con->srv_sock_data;
GByteArray *buf;
const ssize_t blocksize = 16*1024; /* 16k */
off_t max_read = 16 * blocksize; /* 256k */
ssize_t r;
off_t len = 0;
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, "%s", "li_network_read: fd should be disabled as chunkqueue is already full");
}
}
}
buf = conctx->reuse_read_buffer;
conctx->reuse_read_buffer = NULL;
do {
ERR_clear_error();
if (!buf) {
buf = g_byte_array_new();
g_byte_array_set_size(buf, blocksize);
}
r = SSL_read(conctx->ssl, buf->data, buf->len);
if (r < 0) {
int oerrno = errno, err;
gboolean was_fatal;
err = SSL_get_error(conctx->ssl, r);
if (SSL_ERROR_WANT_READ == err || SSL_ERROR_WANT_WRITE == err) {
conctx->reuse_read_buffer = buf;
return LI_NETWORK_STATUS_WAIT_FOR_EVENT;
}
g_byte_array_free(buf, TRUE);
buf = NULL;
switch (err) {
case SSL_ERROR_SYSCALL:
/**
* man SSL_get_error()
*
* SSL_ERROR_SYSCALL
* Some I/O error occurred. The OpenSSL error queue may contain more
* information on the error. If the error queue is empty (i.e.
* ERR_get_error() returns 0), ret can be used to find out more about
* the error: If ret == 0, an EOF was observed that violates the
* protocol. If ret == -1, the underlying BIO reported an I/O error
* (for socket I/O on Unix systems, consult errno for details).
*
*/
while (0 != (err = ERR_get_error())) {
VR_ERROR(con->mainvr, "SSL_read: %s",
ERR_error_string(err, NULL));
}
switch (oerrno) {
case EPIPE:
case ECONNRESET:
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
}
VR_ERROR(con->mainvr, "SSL_read: %s", g_strerror(oerrno));
break;
case SSL_ERROR_ZERO_RETURN:
/* clean shutdown on the remote side */
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
default:
was_fatal = FALSE;
while((err = ERR_get_error())) {
switch (ERR_GET_REASON(err)) {
case SSL_R_SSL_HANDSHAKE_FAILURE:
case SSL_R_TLSV1_ALERT_UNKNOWN_CA:
case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:
case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:
/* TODO: if (!con->conf.log_ssl_noise) */ continue;
break;
default:
was_fatal = TRUE;
break;
}
/* get all errors from the error-queue */
VR_ERROR(con->mainvr, "SSL_read: %s",
ERR_error_string(err, NULL));
}
if (!was_fatal) return LI_NETWORK_STATUS_CONNECTION_CLOSE;
break;
}
return LI_NETWORK_STATUS_FATAL_ERROR;
} else if (r == 0) {
g_byte_array_free(buf, TRUE);
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
}
g_byte_array_set_size(buf, r);
li_chunkqueue_append_bytearr(cq, buf);
buf = NULL;
len += r;
} while (r == blocksize && len < max_read);
return LI_NETWORK_STATUS_SUCCESS;
}
static void openssl_sock_release(liServerSocket *srv_sock) {
openssl_context *ctx = srv_sock->data;
if (!ctx) return;
SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
}
static void openssl_setup_listen_cb(liServer *srv, int fd, gpointer data) {
openssl_context *ctx = data;
liServerSocket *srv_sock;
UNUSED(data);
if (-1 == fd) {
SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
return;
}
srv_sock = li_server_listen(srv, fd);
srv_sock->data = ctx;
srv_sock->write_cb = openssl_con_write;
srv_sock->read_cb = openssl_con_read;
srv_sock->new_cb = openssl_con_new;
srv_sock->close_cb = openssl_con_close;
srv_sock->release_cb = openssl_sock_release;
}
static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val) {
openssl_context *ctx;
GHashTableIter hti;
gpointer hkey, hvalue;
GString *htkey;
liValue *htval;
/* options */
const char *pemfile = NULL, *ca_file = NULL, *ciphers = NULL;
GString *ipstr = NULL;
gboolean allow_ssl2 = FALSE;
UNUSED(p);
if (val->type != LI_VALUE_HASH) {
ERROR(srv, "%s", "openssl 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", "openssl pemfile 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", "openssl 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", "openssl ca-file expects a string as parameter");
return FALSE;
}
ca_file = htval->data.string->str;
} else if (g_str_equal(htkey->str, "ciphers")) {
if (htval->type != LI_VALUE_STRING) {
ERROR(srv, "%s", "openssl ciphers expects a string as parameter");
return FALSE;
}
ciphers = htval->data.string->str;
} else if (g_str_equal(htkey->str, "allow-ssl2")) {
if (htval->type != LI_VALUE_BOOLEAN) {
ERROR(srv, "%s", "openssl allow-ssl2 expects a boolean as parameter");
return FALSE;
}
allow_ssl2 = htval->data.boolean;
}
}
if (!ipstr) {
ERROR(srv, "%s", "openssl needs a listen parameter");
return FALSE;
}
if (!pemfile) {
ERROR(srv, "%s", "openssl needs a pemfile");
return FALSE;
}
ctx = g_slice_new0(openssl_context);
if (NULL == (ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) {
ERROR(srv, "SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
if (!allow_ssl2) {
/* disable SSLv2 */
if (SSL_OP_NO_SSLv2 != SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2)) {
ERROR(srv, "SSL_CTX_set_options(SSL_OP_NO_SSLv2): %s", ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
}
if (ciphers) {
/* Disable support for low encryption ciphers */
if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, ciphers) != 1) {
ERROR(srv, "SSL_CTX_set_cipher_list('%s'): %s", ciphers, ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
}
if (ca_file) {
if (1 != SSL_CTX_load_verify_locations(ctx->ssl_ctx, ca_file, NULL)) {
ERROR(srv, "SSL_CTX_load_verify_locations('%s'): %s", ca_file, ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
}
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pemfile, SSL_FILETYPE_PEM) < 0) {
ERROR(srv, "SSL_CTX_use_certificate_file('%s'): %s", pemfile,
ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
if (SSL_CTX_use_PrivateKey_file (ctx->ssl_ctx, pemfile, SSL_FILETYPE_PEM) < 0) {
ERROR(srv, "SSL_CTX_use_PrivateKey_file('%s'): %s", pemfile,
ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
if (SSL_CTX_check_private_key(ctx->ssl_ctx) != 1) {
ERROR(srv, "SSL: Private key '%s' does not match the certificate public key, reason: %s", pemfile,
ERR_error_string(ERR_get_error(), NULL));
goto error_free_socket;
}
SSL_CTX_set_default_read_ahead(ctx->ssl_ctx, 1);
SSL_CTX_set_mode(ctx->ssl_ctx, SSL_CTX_get_mode(ctx->ssl_ctx) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
li_angel_listen(srv, ipstr, openssl_setup_listen_cb, ctx);
return TRUE;
error_free_socket:
if (ctx) {
if (ctx->ssl_ctx) SSL_CTX_free(ctx->ssl_ctx);
g_slice_free(openssl_context, ctx);
}
return FALSE;
}
static const liPluginOption options[] = {
{ NULL, 0, NULL, NULL, NULL }
};
static const liPluginAction actions[] = {
{ NULL, NULL }
};
static const liPluginSetup setups[] = {
{ "openssl", openssl_setup },
{ NULL, NULL }
};
static void plugin_init(liServer *srv, liPlugin *p) {
UNUSED(srv);
p->options = options;
p->actions = actions;
p->setups = setups;
}
gboolean mod_openssl_init(liModules *mods, liModule *mod) {
MODULE_VERSION_CHECK(mods);
SSL_load_error_strings();
SSL_library_init();
if (0 == RAND_status()) {
ERROR(mods->main, "SSL: %s", "not enough entropy in the pool");
return FALSE;
}
mod->config = li_plugin_register(mods->main, "mod_openssl", plugin_init);
return mod->config != NULL;
}
gboolean mod_openssl_free(liModules *mods, liModule *mod) {
if (mod->config)
li_plugin_free(mods->main, mod->config);
return TRUE;
}
Loading…
Cancel
Save