--- /dev/null
+/*
+ * SSL data transfer functions between buffers and SOCK_STREAM sockets
+ *
+ * Copyright (C) 2012 EXCELIANCE, Emeric Brun <ebrun@exceliance.fr>
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <netinet/tcp.h>
+
+#include <openssl/ssl.h>
+
+#include <common/buffer.h>
+#include <common/compat.h>
+#include <common/config.h>
+#include <common/debug.h>
+#include <common/standard.h>
+#include <common/ticks.h>
+#include <common/time.h>
+
+#include <proto/connection.h>
+#include <proto/fd.h>
+#include <proto/freq_ctr.h>
+#include <proto/frontend.h>
+#include <proto/log.h>
+#include <proto/protocols.h>
+#include <proto/ssl_sock.h>
+#include <proto/task.h>
+
+#include <types/global.h>
+
+
+/*
+ * 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 <flag> 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 <count> bytes from connection <conn>'s socket and store them
+ * into buffer <buf>. The caller must ensure that <count> 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 <buf> to connection <conn>'s socket.
+ * <flags> 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:
+ */