diff --git a/src/modules/mod_openssl.c b/src/modules/mod_openssl.c index db543e0..cccd734 100644 --- a/src/modules/mod_openssl.c +++ b/src/modules/mod_openssl.c @@ -34,7 +34,7 @@ * openssl.setenv "client"; * * Author: - * Copyright (c) 2009 Stefan Bühler, Joe Presbrey + * Copyright (c) 2009-2011 Stefan Bühler, Joe Presbrey */ #include @@ -57,6 +57,9 @@ struct openssl_connection_ctx { int con_events; liJob con_handle_events_job; + + unsigned int initial_handshaked_finished; + unsigned int client_initiated_renegotiation; }; struct openssl_context { @@ -107,9 +110,12 @@ static gboolean openssl_con_new(liConnection *con) { con->srv_sock_data = NULL; conctx->ssl = SSL_new(ctx->ssl_ctx); + SSL_set_app_data(conctx->ssl, conctx); conctx->con = con; li_job_init(&conctx->con_handle_events_job, openssl_con_handle_events_cb); conctx->con_events = 0; + conctx->initial_handshaked_finished = 0; + conctx->client_initiated_renegotiation = 0; if (NULL == conctx->ssl) { ERROR(srv, "SSL_new: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -169,6 +175,109 @@ static void openssl_update_events(liConnection *con, int events) { conctx->con_events = events; } +static void openssl_info_callback(const SSL *ssl, int where, int ret) { + UNUSED(ret); + + if (0 != (where & SSL_CB_HANDSHAKE_START)) { + openssl_connection_ctx *conctx = SSL_get_app_data(ssl); + if (conctx->initial_handshaked_finished) { + conctx->client_initiated_renegotiation = TRUE; + } + } +} + +static liNetworkStatus openssl_handle_error(liConnection *con, openssl_connection_ctx *conctx, const char *sslfunc, off_t len, int r) { + int oerrno = errno, err; + gboolean was_fatal; + + err = SSL_get_error(conctx->ssl, r); + + switch (err) { + case SSL_ERROR_WANT_READ: + li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ); + /* ignore requirement that we should pass the same buffer again */ + return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT; + 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; + 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, "%s(%i): %s", sslfunc, + con->sock_watcher.fd, + ERR_error_string(err, NULL)); + } + + switch (oerrno) { + case EPIPE: + case ECONNRESET: + return LI_NETWORK_STATUS_CONNECTION_CLOSE; + } + + if (0 != r || oerrno != 0) { + VR_ERROR(con->mainvr, "%s(%i) returned %i: %s", sslfunc, + con->sock_watcher.fd, + r, + g_strerror(oerrno)); + return LI_NETWORK_STATUS_FATAL_ERROR; + } else { + return LI_NETWORK_STATUS_CONNECTION_CLOSE; + } + + 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: + case SSL_R_NO_SHARED_CIPHER: + case SSL_R_UNKNOWN_PROTOCOL: + /* 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, "%s(%i): %s", sslfunc, + con->sock_watcher.fd, + ERR_error_string(err, NULL)); + } + if (!was_fatal) return LI_NETWORK_STATUS_CONNECTION_CLOSE; + return LI_NETWORK_STATUS_FATAL_ERROR; + } +} + +static liNetworkStatus openssl_do_handshake(liConnection *con, openssl_connection_ctx *conctx) { + int r = SSL_do_handshake(conctx->ssl); + if (1 == r) { + conctx->initial_handshaked_finished = 1; + conctx->ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; + return LI_NETWORK_STATUS_SUCCESS; + } else { + return openssl_handle_error(con, conctx, "SSL_do_handshake", 0, r); + } +} + static liNetworkStatus openssl_con_write(liConnection *con, goffset write_max) { const ssize_t blocksize = 16*1024; /* 16k */ char *block_data; @@ -178,6 +287,11 @@ static liNetworkStatus openssl_con_write(liConnection *con, goffset write_max) { liChunkQueue *cq = con->raw_out; openssl_connection_ctx *conctx = con->srv_sock_data; + if (!conctx->initial_handshaked_finished) { + liNetworkStatus res = openssl_do_handshake(con, conctx); + if (res != LI_NETWORK_STATUS_SUCCESS) return res; + } + do { if (0 == cq->length) return LI_NETWORK_STATUS_SUCCESS; @@ -202,57 +316,13 @@ static liNetworkStatus openssl_con_write(liConnection *con, goffset write_max) { */ ERR_clear_error(); - if ((r = SSL_write(conctx->ssl, block_data, block_len)) <= 0) { - unsigned long err; - - switch (SSL_get_error(conctx->ssl, r)) { - case SSL_ERROR_WANT_READ: - li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ); - return LI_NETWORK_STATUS_WAIT_FOR_EVENT; - case SSL_ERROR_WANT_WRITE: - li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_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(%i): %s", - con->sock_watcher.fd, - 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(%i): %s", - con->sock_watcher.fd, - g_strerror(errno)); - break; - } - } else { - /* neither error-queue nor errno ? */ - VR_ERROR(con->mainvr, "SSL_write(%i): %s", - con->sock_watcher.fd, - "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(%i): %s", - con->sock_watcher.fd, - ERR_error_string(err, NULL)); - } - - return LI_NETWORK_STATUS_FATAL_ERROR; - } + r = SSL_write(conctx->ssl, block_data, block_len); + if (conctx->client_initiated_renegotiation) { + VR_ERROR(con->mainvr, "%s", "SSL: client initiated renegotitation, closing connection"); + return LI_NETWORK_STATUS_FATAL_ERROR; + } + if (r <= 0) { + return openssl_handle_error(con, conctx, "SSL_write", 0, r); } li_chunkqueue_skip(cq, r); @@ -274,6 +344,11 @@ static liNetworkStatus openssl_con_read(liConnection *con) { ssize_t r; off_t len = 0; + if (!conctx->initial_handshaked_finished) { + liNetworkStatus res = openssl_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; @@ -315,84 +390,12 @@ static liNetworkStatus openssl_con_read(liConnection *con) { assert(con->raw_in_buffer == buf); r = SSL_read(conctx->ssl, buf->addr + buf->used, buf->alloc_size - buf->used); - if (r < 0) { - int oerrno = errno, err; - gboolean was_fatal; - - err = SSL_get_error(conctx->ssl, r); - - switch (err) { - case SSL_ERROR_WANT_READ: - li_ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ); - /* ignore requirement that we should pass the same buffer again */ - return (len > 0) ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_WAIT_FOR_EVENT; - 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; - } - - 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(%i): %s", - con->sock_watcher.fd, - ERR_error_string(err, NULL)); - } - - switch (oerrno) { - case EPIPE: - case ECONNRESET: - return LI_NETWORK_STATUS_CONNECTION_CLOSE; - } - - VR_ERROR(con->mainvr, "SSL_read(%i): %s", - con->sock_watcher.fd, - 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(%i): %s", - con->sock_watcher.fd, - ERR_error_string(err, NULL)); - } - if (!was_fatal) return LI_NETWORK_STATUS_CONNECTION_CLOSE; - break; - } - + if (conctx->client_initiated_renegotiation) { + VR_ERROR(con->mainvr, "%s", "SSL: client initiated renegotitation, closing connection"); return LI_NETWORK_STATUS_FATAL_ERROR; + } + if (r < 0) { + return openssl_handle_error(con, conctx, "SSL_read", len, r); } else if (r == 0) { return LI_NETWORK_STATUS_CONNECTION_CLOSE; } @@ -773,6 +776,8 @@ static gboolean openssl_setup(liServer *srv, liPlugin* p, liValue *val, gpointer goto error_free_socket; } + SSL_CTX_set_info_callback(ctx->ssl_ctx, openssl_info_callback); + if (!SSL_CTX_set_options(ctx->ssl_ctx, options)) { ERROR(srv, "SSL_CTX_set_options(%lx): %s", options, ERR_error_string(ERR_get_error(), NULL)); goto error_free_socket;