diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h new file mode 100644 index 000000000..c37bcdcc1 --- /dev/null +++ b/include/proto/ssl_sock.h @@ -0,0 +1,37 @@ +/* + * include/proto/ssl_sock.h + * This file contains definition for ssl stream socket operations + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_SSL_SOCK_H +#define _PROTO_SSL_SOCK_H + +#include + +extern struct data_ops ssl_sock; +int ssl_sock_handshake(struct connection *conn, unsigned int flag); + +#endif /* _PROTO_SSL_SOCK_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/connection.c b/src/connection.c index 748e14e9c..90283fc5e 100644 --- a/src/connection.c +++ b/src/connection.c @@ -19,6 +19,10 @@ #include #include +#ifdef USE_OPENSSL +#include +#endif + /* I/O callback for fd-based connections. It calls the read/write handlers * provided by the connection's sock_ops, which must be valid. It returns 0. */ @@ -52,6 +56,11 @@ int conn_fd_handler(int fd) if (conn->flags & CO_FL_SI_SEND_PROXY) if (!conn_si_send_proxy(conn, CO_FL_SI_SEND_PROXY)) goto leave; +#ifdef USE_OPENSSL + if (conn->flags & CO_FL_SSL_WAIT_HS) + if (!ssl_sock_handshake(conn, CO_FL_SSL_WAIT_HS)) + goto leave; +#endif } /* Once we're purely in the data phase, we disable handshake polling */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c new file mode 100644 index 000000000..2aa11b8bc --- /dev/null +++ b/src/ssl_sock.c @@ -0,0 +1,365 @@ +/* + * SSL data transfer functions between buffers and SOCK_STREAM sockets + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* + * This function is called if SSL * context is not yet allocated. The function + * is designed to be called before any other data-layer operation and sets the + * handshake flag on the connection. It is safe to call it multiple times. + * It returns 0 on success and -1 in error case. + */ +static int ssl_sock_init(struct connection *conn) +{ + /* already initialized */ + if (conn->data_ctx) + return 0; + + /* If it is in client mode initiate SSL session + in connect state otherwise accept state */ + if (target_srv(&conn->target)) { + + /* Alloc a new SSL session ctx */ + conn->data_ctx = SSL_new(target_srv(&conn->target)->ssl_ctx.ctx); + if (!conn->data_ctx) + return -1; + + SSL_set_connect_state(conn->data_ctx); + if (target_srv(&conn->target)->ssl_ctx.reused_sess) + SSL_set_session(conn->data_ctx, target_srv(&conn->target)->ssl_ctx.reused_sess); + + /* set fd on SSL session context */ + SSL_set_fd(conn->data_ctx, conn->t.sock.fd); + + /* leave init state and start handshake */ + conn->flags |= CO_FL_SSL_WAIT_HS; + return 0; + } + else if (target_client(&conn->target)) { + + /* Alloc a new SSL session ctx */ + conn->data_ctx = SSL_new(target_client(&conn->target)->ssl_ctx.ctx); + if (!conn->data_ctx) + return -1; + + SSL_set_accept_state(conn->data_ctx); + + /* set fd on SSL session context */ + SSL_set_fd(conn->data_ctx, conn->t.sock.fd); + + /* leave init state and start handshake */ + conn->flags |= CO_FL_SSL_WAIT_HS; + return 0; + } + /* don't know how to handle such a target */ + return -1; +} + + +/* This is the callback which is used when an SSL handshake is pending. It + * updates the FD status if it wants some polling before being called again. + * It returns 0 if it fails in a fatal way or needs to poll to go further, + * otherwise it returns non-zero and removes itself from the connection's + * flags (the bit is provided in by the caller). + */ +int ssl_sock_handshake(struct connection *conn, unsigned int flag) +{ + int ret; + + if (!conn->data_ctx) + goto out_error; + + ret = SSL_do_handshake(conn->data_ctx); + if (ret != 1) { + /* handshake did not complete, let's find why */ + ret = SSL_get_error(conn->data_ctx, ret); + + if (ret == SSL_ERROR_WANT_WRITE) { + /* SSL handshake needs to write, L4 connection may not be ready */ + __conn_sock_stop_recv(conn); + __conn_sock_poll_send(conn); + return 0; + } + else if (ret == SSL_ERROR_WANT_READ) { + /* SSL handshake needs to read, L4 connection is ready */ + if (conn->flags & CO_FL_WAIT_L4_CONN) + conn->flags &= ~CO_FL_WAIT_L4_CONN; + __conn_sock_stop_send(conn); + __conn_sock_poll_recv(conn); + return 0; + } + else { + /* Fail on all other handshake errors */ + goto out_error; + } + } + + /* Handshake succeeded */ + if (target_srv(&conn->target)) { + if (!SSL_session_reused(conn->data_ctx)) { + /* check if session was reused, if not store current session on server for reuse */ + if (target_srv(&conn->target)->ssl_ctx.reused_sess) + SSL_SESSION_free(target_srv(&conn->target)->ssl_ctx.reused_sess); + + target_srv(&conn->target)->ssl_ctx.reused_sess = SSL_get1_session(conn->data_ctx); + } + } + + /* The connection is now established at both layers, it's time to leave */ + conn->flags &= ~(flag | CO_FL_WAIT_L4_CONN | CO_FL_WAIT_L6_CONN); + return 1; + + out_error: + /* Fail on all other handshake errors */ + conn->flags |= CO_FL_ERROR; + conn->flags &= ~flag; + return 0; +} + +/* Receive up to bytes from connection 's socket and store them + * into buffer . The caller must ensure that is always smaller + * than the buffer's size. Only one call to recv() is performed, unless the + * buffer wraps, in which case a second call may be performed. The connection's + * flags are updated with whatever special event is detected (error, read0, + * empty). The caller is responsible for taking care of those events and + * avoiding the call if inappropriate. The function does not call the + * connection's polling update function, so the caller is responsible for this. + */ +static int ssl_sock_to_buf(struct connection *conn, struct buffer *buf, int count) +{ + int ret, done = 0; + int try = count; + + if (!conn->data_ctx) + goto out_error; + + if (conn->flags & CO_FL_HANDSHAKE) + /* a handshake was requested */ + return 0; + + /* compute the maximum block size we can read at once. */ + if (buffer_empty(buf)) { + /* let's realign the buffer to optimize I/O */ + buf->p = buf->data; + } + else if (buf->data + buf->o < buf->p && + buf->p + buf->i < buf->data + buf->size) { + /* remaining space wraps at the end, with a moving limit */ + if (try > buf->data + buf->size - (buf->p + buf->i)) + try = buf->data + buf->size - (buf->p + buf->i); + } + + /* read the largest possible block. For this, we perform only one call + * to recv() unless the buffer wraps and we exactly fill the first hunk, + * in which case we accept to do it once again. A new attempt is made on + * EINTR too. + */ + while (try) { + ret = SSL_read(conn->data_ctx, bi_end(buf), try); + + if (ret > 0) { + buf->i += ret; + done += ret; + if (ret < try) + break; + count -= ret; + try = count; + } + else if (ret == 0) { + goto read0; + } + else { + ret = SSL_get_error(conn->data_ctx, ret); + if (ret == SSL_ERROR_WANT_WRITE) { + /* handshake is running, and it needs to poll for a write event */ + conn->flags |= CO_FL_SSL_WAIT_HS; + __conn_sock_poll_send(conn); + break; + } + else if (ret == SSL_ERROR_WANT_READ) { + /* we need to poll for retry a read later */ + __conn_data_poll_recv(conn); + break; + } + /* otherwise it's a real error */ + goto out_error; + } + } + return done; + + read0: + conn_sock_read0(conn); + return done; + out_error: + conn->flags |= CO_FL_ERROR; + return done; +} + + +/* Send all pending bytes from buffer to connection 's socket. + * may contain MSG_MORE to make the system hold on without sending + * data too fast, but this flag is ignored at the moment. + * Only one call to send() is performed, unless the buffer wraps, in which case + * a second call may be performed. The connection's flags are updated with + * whatever special event is detected (error, empty). The caller is responsible + * for taking care of those events and avoiding the call if inappropriate. The + * function does not call the connection's polling update function, so the caller + * is responsible for this. + */ +static int ssl_sock_from_buf(struct connection *conn, struct buffer *buf, int flags) +{ + int ret, try, done; + + done = 0; + + if (!conn->data_ctx) + goto out_error; + + if (conn->flags & CO_FL_HANDSHAKE) + /* a handshake was requested */ + return 0; + + /* send the largest possible block. For this we perform only one call + * to send() unless the buffer wraps and we exactly fill the first hunk, + * in which case we accept to do it once again. + */ + while (buf->o) { + try = buf->o; + /* outgoing data may wrap at the end */ + if (buf->data + try > buf->p) + try = buf->data + try - buf->p; + + ret = SSL_write(conn->data_ctx, bo_ptr(buf), try); + if (ret > 0) { + buf->o -= ret; + done += ret; + + if (likely(!buffer_len(buf))) + /* optimize data alignment in the buffer */ + buf->p = buf->data; + + /* if the system buffer is full, don't insist */ + if (ret < try) + break; + } + else { + ret = SSL_get_error(conn->data_ctx, ret); + if (ret == SSL_ERROR_WANT_WRITE) { + /* we need to poll to retry a write later */ + __conn_data_poll_send(conn); + break; + } + else if (ret == SSL_ERROR_WANT_READ) { + /* handshake is running, and + it needs to poll for a read event, + write polling must be disabled cause + we are sure we can't write anything more + before handshake re-performed */ + conn->flags |= CO_FL_SSL_WAIT_HS; + __conn_sock_poll_recv(conn); + break; + } + goto out_error; + } + } + return done; + + out_error: + conn->flags |= CO_FL_ERROR; + return done; +} + + +static void ssl_sock_close(struct connection *conn) { + + if (conn->data_ctx) { + SSL_free(conn->data_ctx); + conn->data_ctx = NULL; + } + +} + +/* This function tries to perform a clean shutdown on an SSL connection, and in + * any case, flags the connection as reusable if no handshake was in progress. + */ +static void ssl_sock_shutw(struct connection *conn, int clean) +{ + if (conn->flags & CO_FL_HANDSHAKE) + return; + /* no handshake was in progress, try a clean ssl shutdown */ + if (clean) + SSL_shutdown(conn->data_ctx); + + /* force flag on ssl to keep session in cache regardless shutdown result */ + SSL_set_shutdown(conn->data_ctx, SSL_SENT_SHUTDOWN); +} + + +/* data-layer operations for SSL sockets */ +struct data_ops ssl_sock = { + .snd_buf = ssl_sock_from_buf, + .rcv_buf = ssl_sock_to_buf, + .rcv_pipe = NULL, + .snd_pipe = NULL, + .shutr = NULL, + .shutw = ssl_sock_shutw, + .close = ssl_sock_close, + .init = ssl_sock_init, +}; + +__attribute__((constructor)) +static void __ssl_sock_init(void) { + STACK_OF(SSL_COMP)* cm; + + SSL_library_init(); + cm = SSL_COMP_get_compression_methods(); + sk_SSL_COMP_zero(cm); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */