2009-09-08 17:10:09 +00:00
|
|
|
/*
|
|
|
|
* mod_openssl - ssl support
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* mod_openssl listens on separate sockets for ssl connections (https://...)
|
|
|
|
*
|
|
|
|
* Setups:
|
2011-11-30 00:08:37 +00:00
|
|
|
* 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 colon separated list of allowed ciphers
|
|
|
|
* allow-ssl2 - (boolean) allow ssl2 (default: disabled)
|
|
|
|
* verify - (boolean) enable client certificate verification (default: false)
|
|
|
|
* verify-any - (boolean) allow all CAs and self-signed certificates (for manual checking, default: false)
|
|
|
|
* verify-depth - (number) sets client verification depth (default: 1)
|
|
|
|
* verify-require - (boolean) abort clients failing verification (default: false)
|
|
|
|
* client-ca-file - (string) path to file containing client CA certificates
|
2009-09-08 17:10:09 +00:00
|
|
|
*
|
|
|
|
* Example config:
|
|
|
|
* setup openssl [ "listen": "0.0.0.0:8443", "pemfile": "server.pem" ];
|
|
|
|
* setup openssl [ "listen": "[::]:8443", "pemfile": "server.pem" ];
|
|
|
|
*
|
|
|
|
* Author:
|
2011-11-30 00:08:37 +00:00
|
|
|
* Copyright (c) 2009 Stefan Bühler, Joe Presbrey
|
2009-09-08 17:10:09 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#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;
|
2010-09-23 11:36:07 +00:00
|
|
|
liConnection *con;
|
|
|
|
|
|
|
|
int con_events;
|
|
|
|
liJob con_handle_events_job;
|
2009-09-08 17:10:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct openssl_context {
|
|
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
};
|
|
|
|
|
2010-09-23 11:36:07 +00:00
|
|
|
static void openssl_con_handle_events_cb(liJob *job) {
|
|
|
|
openssl_connection_ctx *conctx = LI_CONTAINER_OF(job, openssl_connection_ctx, con_handle_events_job);
|
|
|
|
liConnection *con = conctx->con;
|
|
|
|
|
|
|
|
connection_handle_io(con);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void openssl_io_cb(struct ev_loop *loop, ev_io *w, int revents) {
|
|
|
|
liConnection *con = (liConnection*) w->data;
|
|
|
|
openssl_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);
|
|
|
|
}
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
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);
|
|
|
|
|
2009-10-02 10:09:37 +00:00
|
|
|
con->srv_sock_data = NULL;
|
|
|
|
|
2009-10-02 12:04:28 +00:00
|
|
|
conctx->ssl = SSL_new(ctx->ssl_ctx);
|
2010-09-23 11:36:07 +00:00
|
|
|
conctx->con = con;
|
|
|
|
li_job_init(&conctx->con_handle_events_job, openssl_con_handle_events_cb);
|
|
|
|
conctx->con_events = 0;
|
2009-10-02 12:04:28 +00:00
|
|
|
|
|
|
|
if (NULL == conctx->ssl) {
|
2009-09-08 17:10:09 +00:00
|
|
|
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;
|
2010-07-31 12:44:45 +00:00
|
|
|
con->info.is_ssl = TRUE;
|
2009-09-08 17:10:09 +00:00
|
|
|
|
2010-09-23 11:36:07 +00:00
|
|
|
ev_set_cb(&con->sock_watcher, openssl_io_cb);
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
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;
|
|
|
|
|
2009-10-02 08:44:08 +00:00
|
|
|
if (!conctx) return;
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
if (conctx->ssl) {
|
|
|
|
SSL_shutdown(conctx->ssl); /* TODO: wait for something??? */
|
|
|
|
SSL_free(conctx->ssl);
|
|
|
|
conctx->ssl = FALSE;
|
|
|
|
}
|
2009-10-02 08:44:08 +00:00
|
|
|
|
|
|
|
con->srv_sock_data = NULL;
|
2010-07-31 12:44:45 +00:00
|
|
|
con->info.is_ssl = FALSE;
|
2010-09-23 11:36:07 +00:00
|
|
|
li_job_clear(&conctx->con_handle_events_job);
|
2009-10-02 10:09:37 +00:00
|
|
|
|
2009-10-02 08:44:08 +00:00
|
|
|
g_slice_free(openssl_connection_ctx, conctx);
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
|
|
|
|
2010-09-23 11:36:07 +00:00
|
|
|
static void openssl_update_events(liConnection *con, int events) {
|
|
|
|
openssl_connection_ctx *conctx = con->srv_sock_data;
|
|
|
|
|
|
|
|
/* new events -> add them to socket watcher too */
|
|
|
|
if (0 != (events & ~conctx->con_events)) {
|
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, events);
|
|
|
|
}
|
|
|
|
|
|
|
|
conctx->con_events = events;
|
|
|
|
}
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
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;
|
|
|
|
|
2009-09-26 18:31:52 +00:00
|
|
|
ci = li_chunkqueue_iter(cq);
|
2009-09-08 17:10:09 +00:00
|
|
|
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) {
|
|
|
|
unsigned long err;
|
|
|
|
|
2010-10-07 16:31:21 +00:00
|
|
|
switch (SSL_get_error(conctx->ssl, r)) {
|
2009-09-08 17:10:09 +00:00
|
|
|
case SSL_ERROR_WANT_READ:
|
2010-09-23 11:36:07 +00:00
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ);
|
|
|
|
return LI_NETWORK_STATUS_WAIT_FOR_EVENT;
|
2009-09-08 17:10:09 +00:00
|
|
|
case SSL_ERROR_WANT_WRITE:
|
2010-09-23 11:36:07 +00:00
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
|
2009-09-08 17:10:09 +00:00
|
|
|
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 {
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_write(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
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:
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_write(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
g_strerror(errno));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* neither error-queue nor errno ? */
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_write(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
"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())) {
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_write(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
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);
|
|
|
|
|
2010-11-26 13:04:28 +00:00
|
|
|
if (0 != cq->length) {
|
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
|
|
|
|
}
|
2009-09-08 17:10:09 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
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 */
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "li_network_read: fd %i should be disabled as chunkqueue is already full",
|
|
|
|
con->sock_watcher.fd);
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
2010-01-23 19:24:21 +00:00
|
|
|
liBuffer *buf;
|
|
|
|
gboolean cq_buf_append;
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
ERR_clear_error();
|
|
|
|
|
2010-01-23 19:24:21 +00:00
|
|
|
buf = li_chunkqueue_get_last_buffer(cq, 1024);
|
2010-02-10 21:25:48 +00:00
|
|
|
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);
|
|
|
|
}
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
2010-02-10 21:25:48 +00:00
|
|
|
assert(con->raw_in_buffer == buf);
|
2009-09-08 17:10:09 +00:00
|
|
|
|
2010-01-23 19:24:21 +00:00
|
|
|
r = SSL_read(conctx->ssl, buf->addr + buf->used, buf->alloc_size - buf->used);
|
2009-09-08 17:10:09 +00:00
|
|
|
if (r < 0) {
|
|
|
|
int oerrno = errno, err;
|
|
|
|
gboolean was_fatal;
|
|
|
|
|
|
|
|
err = SSL_get_error(conctx->ssl, r);
|
|
|
|
|
2010-09-23 11:36:07 +00:00
|
|
|
switch (err) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ);
|
2010-01-23 19:24:21 +00:00
|
|
|
/* ignore requirement that we should pass the same buffer again */
|
|
|
|
return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT;
|
2010-09-23 11:36:07 +00:00
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
|
|
|
|
/* ignore requirement that we should pass the same buffer again */
|
|
|
|
return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT;
|
|
|
|
default:
|
|
|
|
break;
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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())) {
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_read(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
ERR_error_string(err, NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (oerrno) {
|
|
|
|
case EPIPE:
|
|
|
|
case ECONNRESET:
|
|
|
|
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
|
|
|
|
}
|
|
|
|
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_read(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
|
|
|
g_strerror(oerrno));
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
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 */
|
2009-10-02 12:04:09 +00:00
|
|
|
VR_ERROR(con->mainvr, "SSL_read(%i): %s",
|
|
|
|
con->sock_watcher.fd,
|
2009-09-08 17:10:09 +00:00
|
|
|
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) {
|
|
|
|
return LI_NETWORK_STATUS_CONNECTION_CLOSE;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:24:21 +00:00
|
|
|
if (cq_buf_append) {
|
|
|
|
li_chunkqueue_update_last_buffer_size(cq, r);
|
|
|
|
} else {
|
2010-02-10 21:25:48 +00:00
|
|
|
gsize offset;
|
|
|
|
|
2010-02-13 21:29:27 +00:00
|
|
|
li_buffer_acquire(buf);
|
|
|
|
|
2010-02-10 21:25:48 +00:00
|
|
|
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;
|
2010-01-23 19:24:21 +00:00
|
|
|
}
|
2009-09-08 17:10:09 +00:00
|
|
|
len += r;
|
2010-01-23 19:24:21 +00:00
|
|
|
} while (len < max_read);
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
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;
|
2010-09-23 11:36:07 +00:00
|
|
|
srv_sock->update_events_cb = openssl_update_events;
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
|
|
|
|
2011-11-30 00:08:37 +00:00
|
|
|
static int openssl_verify_any_cb(int ok, X509_STORE_CTX *ctx) { UNUSED(ok); UNUSED(ctx); return 1; }
|
|
|
|
|
2009-12-21 11:29:14 +00:00
|
|
|
static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer userdata) {
|
2009-09-08 17:10:09 +00:00
|
|
|
openssl_context *ctx;
|
|
|
|
GHashTableIter hti;
|
|
|
|
gpointer hkey, hvalue;
|
|
|
|
GString *htkey;
|
|
|
|
liValue *htval;
|
2011-11-30 00:08:37 +00:00
|
|
|
STACK_OF(X509_NAME) *client_ca_list;
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
/* options */
|
2011-11-30 00:08:37 +00:00
|
|
|
const char *ciphers = NULL, *pemfile = NULL, *ca_file = NULL, *client_ca_file = NULL;
|
2009-09-08 17:10:09 +00:00
|
|
|
GString *ipstr = NULL;
|
2011-11-30 00:08:37 +00:00
|
|
|
gboolean allow_ssl2 = FALSE, verify_any = FALSE;
|
|
|
|
gint verify_mode = 0, verify_depth = 1;
|
2009-09-08 17:10:09 +00:00
|
|
|
|
2009-12-21 11:29:14 +00:00
|
|
|
UNUSED(p); UNUSED(userdata);
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
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;
|
2011-11-30 00:08:37 +00:00
|
|
|
} else if (g_str_equal(htkey->str, "verify")) {
|
|
|
|
if (htval->type != LI_VALUE_BOOLEAN) {
|
|
|
|
ERROR(srv, "%s", "openssl verify expects a boolean as parameter");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (htval->data.boolean)
|
|
|
|
verify_mode |= SSL_VERIFY_PEER;
|
|
|
|
} else if (g_str_equal(htkey->str, "verify-any")) {
|
|
|
|
if (htval->type != LI_VALUE_BOOLEAN) {
|
|
|
|
ERROR(srv, "%s", "openssl verify-any expects a boolean as parameter");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
verify_any = htval->data.boolean;
|
|
|
|
} else if (g_str_equal(htkey->str, "verify-depth")) {
|
|
|
|
if (htval->type != LI_VALUE_NUMBER) {
|
|
|
|
ERROR(srv, "%s", "openssl verify-depth expects a number as parameter");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
verify_depth = htval->data.number;
|
|
|
|
} else if (g_str_equal(htkey->str, "verify-require")) {
|
|
|
|
if (htval->type != LI_VALUE_BOOLEAN) {
|
|
|
|
ERROR(srv, "%s", "openssl verify-require expects a boolean as parameter");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (htval->data.boolean)
|
|
|
|
verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
|
|
} else if (g_str_equal(htkey->str, "client-ca-file")) {
|
|
|
|
if (htval->type != LI_VALUE_STRING) {
|
|
|
|
ERROR(srv, "%s", "openssl client-ca-file expects a string as parameter");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
client_ca_file = htval->data.string->str;
|
2009-09-08 17:10:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 */
|
2010-03-07 12:25:51 +00:00
|
|
|
if (0 == (SSL_OP_NO_SSLv2 & SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2))) {
|
2009-09-08 17:10:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-11-30 00:08:37 +00:00
|
|
|
if (verify_mode) {
|
|
|
|
if (SSL_CTX_set_session_id_context(ctx->ssl_ctx, (void*) &srv, sizeof(srv)) != 1) {
|
|
|
|
ERROR(srv, "SSL_CTX_set_session_id_context(): %s", ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
goto error_free_socket;
|
|
|
|
}
|
|
|
|
SSL_CTX_set_verify(ctx->ssl_ctx, verify_mode, verify_any ? openssl_verify_any_cb : NULL);
|
|
|
|
SSL_CTX_set_verify_depth(ctx->ssl_ctx, verify_depth);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (client_ca_file) {
|
|
|
|
if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, client_ca_file, NULL) != 1) {
|
|
|
|
ERROR(srv, "SSL_CTX_load_verify_locations('%s'): %s", client_ca_file, ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
goto error_free_socket;
|
|
|
|
}
|
|
|
|
if ((client_ca_list = SSL_load_client_CA_file(client_ca_file)) == NULL) {
|
|
|
|
ERROR(srv, "SSL_load_client_CA_file('%s'): %s", client_ca_file, ERR_error_string(ERR_get_error(), NULL));
|
|
|
|
goto error_free_socket;
|
|
|
|
}
|
|
|
|
SSL_CTX_set_client_CA_list(ctx->ssl_ctx, client_ca_list);
|
|
|
|
}
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
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[] = {
|
2010-01-24 20:30:41 +00:00
|
|
|
{ NULL, 0, 0, NULL }
|
2009-09-08 17:10:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const liPluginAction actions[] = {
|
2009-12-21 11:29:14 +00:00
|
|
|
{ NULL, NULL, NULL }
|
2009-09-08 17:10:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const liPluginSetup setups[] = {
|
2009-12-21 11:29:14 +00:00
|
|
|
{ "openssl", openssl_setup, NULL },
|
2009-09-08 17:10:09 +00:00
|
|
|
|
2009-12-21 11:29:14 +00:00
|
|
|
{ NULL, NULL, NULL }
|
2009-09-08 17:10:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2009-12-21 11:29:14 +00:00
|
|
|
static void plugin_init(liServer *srv, liPlugin *p, gpointer userdata) {
|
|
|
|
UNUSED(srv); UNUSED(userdata);
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
p->options = options;
|
|
|
|
p->actions = actions;
|
|
|
|
p->setups = setups;
|
|
|
|
}
|
|
|
|
|
2009-10-02 12:04:28 +00:00
|
|
|
static GMutex** ssl_locks;
|
|
|
|
|
|
|
|
static void ssl_lock_cb(int mode, int n, const char *file, int line) {
|
|
|
|
UNUSED(file);
|
|
|
|
UNUSED(line);
|
|
|
|
|
|
|
|
if (mode & CRYPTO_LOCK) {
|
|
|
|
g_mutex_lock(ssl_locks[n]);
|
|
|
|
} else if (mode & CRYPTO_UNLOCK) {
|
|
|
|
g_mutex_unlock(ssl_locks[n]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long ssl_id_cb(void) {
|
|
|
|
return (intptr_t) g_thread_self();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sslthread_init() {
|
|
|
|
int n = CRYPTO_num_locks(), i;
|
|
|
|
|
|
|
|
ssl_locks = g_slice_alloc0(sizeof(GMutex*) * n);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
ssl_locks[i] = g_mutex_new();
|
|
|
|
}
|
|
|
|
|
|
|
|
CRYPTO_set_locking_callback(ssl_lock_cb);
|
|
|
|
CRYPTO_set_id_callback(ssl_id_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sslthread_free() {
|
|
|
|
int n = CRYPTO_num_locks(), i;
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
g_mutex_free(ssl_locks[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_slice_free1(sizeof(GMutex*) * n, ssl_locks);
|
|
|
|
}
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
gboolean mod_openssl_init(liModules *mods, liModule *mod) {
|
|
|
|
MODULE_VERSION_CHECK(mods);
|
|
|
|
|
2009-10-02 12:04:28 +00:00
|
|
|
sslthread_init();
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
SSL_load_error_strings();
|
|
|
|
SSL_library_init();
|
|
|
|
|
|
|
|
if (0 == RAND_status()) {
|
|
|
|
ERROR(mods->main, "SSL: %s", "not enough entropy in the pool");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2009-12-21 11:29:14 +00:00
|
|
|
mod->config = li_plugin_register(mods->main, "mod_openssl", plugin_init, NULL);
|
2009-09-08 17:10:09 +00:00
|
|
|
|
|
|
|
return mod->config != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean mod_openssl_free(liModules *mods, liModule *mod) {
|
|
|
|
if (mod->config)
|
|
|
|
li_plugin_free(mods->main, mod->config);
|
|
|
|
|
2009-10-02 10:09:37 +00:00
|
|
|
ERR_free_strings();
|
|
|
|
|
2009-10-02 12:04:28 +00:00
|
|
|
sslthread_free();
|
|
|
|
|
2009-09-08 17:10:09 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|