Code

SSL utils: Added helper functions for managing OpenSSL servers and clients.
authorSebastian Harl <sh@tokkee.org>
Wed, 28 Jan 2015 15:07:50 +0000 (16:07 +0100)
committerSebastian Harl <sh@tokkee.org>
Wed, 28 Jan 2015 15:11:35 +0000 (16:11 +0100)
The module provides session management for OpenSSL servers and clients and
helper functions for I/O operations.

configure.ac
src/Makefile.am
src/include/utils/ssl.h [new file with mode: 0644]
src/utils/ssl.c [new file with mode: 0644]

index 325a733a2a5d526694bbdd5b58a7cf8d452d4414..a03b5ce55d5e8bd3325f2d2b71dbf42d07de47fa 100644 (file)
@@ -483,6 +483,12 @@ if test "x$have_fopencookie" = "xyes"; then
        AC_DEFINE([HAVE_FOPENCOOKIE], 1)
 fi
 
+dnl OpenSSL support
+PKG_CHECK_MODULES([OPENSSL], [openssl], [have_openssl="yes"], [have_openssl="no"])
+if test "x$have_openssl" != "xyes"; then
+       AC_MSG_ERROR([OpenSSL not found])
+fi
+
 dnl readline support
 AC_ARG_WITH([readline],
                [AS_HELP_STRING([--with-readline],
@@ -720,6 +726,7 @@ AC_MSG_RESULT()
 AC_MSG_RESULT([  Libraries:])
 AC_MSG_RESULT([    libdbi: . . . . . . . . . . $with_libdbi])
 AC_MSG_RESULT([    libedit:  . . . . . . . . . $have_libedit])
+AC_MSG_RESULT([    libopenssl: . . . . . . . . $have_openssl])
 AC_MSG_RESULT([    libreadline:  . . . . . . . $have_libreadline])
 AC_MSG_RESULT([    librrd: . . . . . . . . . . $have_librrd])
 AC_MSG_RESULT()
index 7cff3e8a5b5dafc05568cc41c6ab8f435de6a46d..6521e8463a5d6df15e1e5b75d8690ac79e0f8316 100644 (file)
@@ -39,6 +39,7 @@ pkgutilsinclude_HEADERS = \
                include/utils/llist.h \
                include/utils/os.h \
                include/utils/proto.h \
+               include/utils/ssl.h \
                include/utils/strbuf.h \
                include/utils/unixsock.h
 
@@ -95,14 +96,15 @@ libsysdb_la_SOURCES = \
                utils/llist.c include/utils/llist.h \
                utils/os.c include/utils/os.h \
                utils/proto.c include/utils/proto.h \
+               utils/ssl.c include/utils/ssl.h \
                utils/strbuf.c include/utils/strbuf.h \
                utils/unixsock.c include/utils/unixsock.h
-libsysdb_la_CFLAGS = $(AM_CFLAGS)
+libsysdb_la_CFLAGS = $(AM_CFLAGS) @OPENSSL_CFLAGS@
 libsysdb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LTDLINCL)
 libsysdb_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0 \
                -pthread -lm -lrt
 libsysdb_la_LIBADD = libsysdb_fe_parser.la \
-               $(LIBLTDL) liboconfig/liboconfig.la
+               $(LIBLTDL) liboconfig/liboconfig.la @OPENSSL_LIBS@
 libsysdb_la_DEPENDENCIES = libsysdb_fe_parser.la liboconfig/liboconfig.la
 
 if BUILD_WITH_LIBDBI
@@ -134,11 +136,12 @@ endif
 sysdbd_SOURCES = tools/sysdbd/main.c include/sysdb.h \
                tools/sysdbd/configfile.c tools/sysdbd/configfile.h \
                $(libsysdb_la_SOURCES)
-sysdbd_CFLAGS = $(AM_CFLAGS) -DBUILD_DATE="\"$$( date --utc '+%F %T' ) (UTC)\""
+sysdbd_CFLAGS = $(AM_CFLAGS) @OPENSSL_CFLAGS@ \
+               -DBUILD_DATE="\"$$( date --utc '+%F %T' ) (UTC)\""
 sysdbd_CPPFLAGS = $(AM_CPPFLAGS) $(LTDLINCL)
 sysdbd_LDFLAGS = $(AM_LDFLAGS) -export-dynamic -pthread -lm
 sysdbd_LDADD = libsysdb_fe_parser.la liboconfig/liboconfig.la \
-               $(LIBLTDL) -lrt
+               $(LIBLTDL) -lrt @OPENSSL_LIBS@
 sysdbd_DEPENDENCIES = libsysdb_fe_parser.la liboconfig/liboconfig.la
 
 if BUILD_WITH_LIBDBI
diff --git a/src/include/utils/ssl.h b/src/include/utils/ssl.h
new file mode 100644 (file)
index 0000000..6489f8b
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * SysDB - src/include/utils/ssl.h
+ * Copyright (C) 2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDB_UTILS_SSL_H
+#define SDB_UTILS_SSL_H 1
+
+#include <sys/types.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef SDB_SSL_KEYFILE
+#      define SDB_SSL_KEYFILE SYSCONFDIR "/sysdb/ssl/key.pem"
+#endif
+#ifndef SDB_SSL_CERTFILE
+#      define SDB_SSL_CERTFILE SYSCONFDIR "/sysdb/ssl/cert.pem"
+#endif
+#ifndef SDB_SSL_CRLFILE
+#      define SDB_SSL_CRLFILE SYSCONFDIR "/sysdb/ssl/crl.pem"
+#endif
+#ifndef SDB_SSL_CAFILE
+#      define SDB_SSL_CAFILE SYSCONFDIR "/ssl/certs/ca-certificates.crt"
+#endif
+
+typedef struct {
+       char *ca_file;
+       char *key_file;
+       char *cert_file;
+       char *crl_file;
+} sdb_ssl_options_t;
+#define SDB_SSL_DEFAULT_OPTIONS { \
+       SDB_SSL_CAFILE, SDB_SSL_KEYFILE, SDB_SSL_CERTFILE, SDB_SSL_CRLFILE, \
+}
+
+struct sdb_ssl_client;
+typedef struct sdb_ssl_client sdb_ssl_client_t;
+
+struct sdb_ssl_server;
+typedef struct sdb_ssl_server sdb_ssl_server_t;
+
+struct sdb_ssl_session;
+typedef struct sdb_ssl_session sdb_ssl_session_t;
+
+/*
+ * sdb_ssl_client_create:
+ * Allocate and initialize a TLS/SSL client using the specified options. If no
+ * options are specified, default values will be used instead.
+ */
+sdb_ssl_client_t *
+sdb_ssl_client_create(sdb_ssl_options_t *opts);
+
+/*
+ * sdb_ssl_client_destroy:
+ * Destroy a TLS/SSL client and free all of its memory.
+ */
+void
+sdb_ssl_client_destroy(sdb_ssl_client_t *client);
+
+/*
+ * sdb_ssl_client_connect:
+ * Initialize a TLS/SSL session on the specified socket.
+ */
+sdb_ssl_session_t *
+sdb_ssl_client_connect(sdb_ssl_client_t *client, int fd);
+
+/*
+ * sdb_ssl_server_create:
+ * Allocate and initialize a TLS/SSL server using the specified options. If no
+ * options are specified, default values will be used instead.
+ */
+sdb_ssl_server_t *
+sdb_ssl_server_create(sdb_ssl_options_t *opts);
+
+/*
+ * sdb_ssl_server_destroy:
+ * Destroy a TLS/SSL server and free all of its memory.
+ */
+void
+sdb_ssl_server_destroy(sdb_ssl_server_t *server);
+
+/*
+ * sdb_ssl_server_accept:
+ * Initialize a TLS/SSL session on the specified socket.
+ */
+sdb_ssl_session_t *
+sdb_ssl_server_accept(sdb_ssl_server_t *server, int fd);
+
+/*
+ * sdb_ssl_session_destroy:
+ * Shutdown and destroy a TLS/SSL session.
+ */
+void
+sdb_ssl_session_destroy(sdb_ssl_session_t *session);
+
+/*
+ * sdb_ssl_session_peer:
+ * Return the name of the peer of a TLS/SSL session.
+ *
+ * Returns:
+ *  - a dynamically allocated string on success
+ *  - NULL else
+ */
+char *
+sdb_ssl_session_peer(sdb_ssl_session_t *session);
+
+/*
+ * sdb_ssl_session_write:
+ * Write a message to an open TLS/SSL session.
+ */
+ssize_t
+sdb_ssl_session_write(sdb_ssl_session_t *session, const void *buf, size_t n);
+
+/*
+ * sdb_ssl_session_read:
+ * Read from an open TLS/SSL session.
+ */
+ssize_t
+sdb_ssl_session_read(sdb_ssl_session_t *session, void *buf, size_t n);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! SDB_UTILS_SSL_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/utils/ssl.c b/src/utils/ssl.c
new file mode 100644 (file)
index 0000000..477d16c
--- /dev/null
@@ -0,0 +1,516 @@
+/*
+ * SysDB - src/utils/ssl.c
+ * Copyright (C) 2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#      include "config.h"
+#endif
+
+#include "utils/ssl.h"
+#include "utils/error.h"
+
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+#include <openssl/err.h>
+
+/*
+ * data types
+ */
+
+struct sdb_ssl_client {
+       SSL_CTX *ctx;
+       sdb_ssl_options_t opts;
+};
+
+struct sdb_ssl_server {
+       SSL_CTX *ctx;
+       sdb_ssl_options_t opts;
+};
+
+struct sdb_ssl_session {
+       SSL *ssl;
+};
+
+/*
+ * private helper functions
+ */
+
+/* log all pending SSL errors */
+static void
+ssl_log(int prio, const char *prefix, ...)
+{
+       char msg[1024];
+       va_list ap;
+
+       va_start(ap, prefix);
+       vsnprintf(msg, sizeof(msg), prefix, ap);
+       msg[sizeof(msg) - 1] = '\0';
+       va_end(ap);
+
+       while (42) {
+               unsigned long e = ERR_get_error();
+               if (! e)
+                       break;
+               sdb_log(prio, "%s: %s", msg, ERR_reason_error_string(e));
+       }
+} /* ssl_log */
+
+static void
+ssl_log_err(int prio, SSL *ssl, int status, const char *prefix, ...)
+{
+       int err = SSL_get_error(ssl, status);
+       char msg[1024];
+       va_list ap;
+
+       va_start(ap, prefix);
+       vsnprintf(msg, sizeof(msg), prefix, ap);
+       msg[sizeof(msg) - 1] = '\0';
+       va_end(ap);
+
+       errno = 0;
+       switch (err) {
+               case SSL_ERROR_NONE:
+                       sdb_log(prio, "%s: success", msg);
+                       break;
+               case SSL_ERROR_ZERO_RETURN:
+                       errno = ECONNRESET;
+                       break;
+               case SSL_ERROR_WANT_READ:
+               case SSL_ERROR_WANT_WRITE:
+                       errno = EWOULDBLOCK;
+                       break;
+               case SSL_ERROR_WANT_CONNECT:
+               case SSL_ERROR_WANT_ACCEPT:
+                       sdb_log(prio, "%s: connection not set up", msg);
+                       break;
+               case SSL_ERROR_WANT_X509_LOOKUP:
+                       sdb_log(prio, "%s: application error", msg);
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       if (ERR_peek_error())
+                               return ssl_log(prio, msg);
+                       if (! status)
+                               sdb_log(prio, "%s: unexpected end-of-file", msg);
+                       else if (! errno)
+                               errno = EIO;
+               case SSL_ERROR_SSL:
+                       return ssl_log(prio, msg);
+               default:
+                       sdb_log(prio, "%s: unkown SSL error %d", msg, err);
+                       break;
+       }
+
+       if (errno) {
+               char errbuf[1024];
+               sdb_log(prio, "%s: %s", msg,
+                               sdb_strerror(errno, errbuf, sizeof(errbuf)));
+       }
+} /* ssl_log_err */
+
+static int
+copy_options(sdb_ssl_options_t *dst, sdb_ssl_options_t *src)
+{
+       sdb_ssl_options_t def = SDB_SSL_DEFAULT_OPTIONS;
+
+       if (! src)
+               src = &def;
+
+       if (! src->ca_file)
+               src->ca_file = def.ca_file;
+       if (! src->key_file)
+               src->key_file = def.key_file;
+       if (! src->cert_file)
+               src->cert_file = def.cert_file;
+
+       dst->ca_file = strdup(src->ca_file);
+       dst->key_file = strdup(src->key_file);
+       dst->cert_file = strdup(src->cert_file);
+       if ((! dst->ca_file) || (! dst->key_file) || (! dst->cert_file))
+               return -1;
+       if (src->crl_file) {
+               dst->crl_file = strdup(src->crl_file);
+               if (! dst->crl_file)
+                       return -1;
+       }
+       return 0;
+} /* copy_options */
+
+static void
+free_options(sdb_ssl_options_t *opts)
+{
+       if (opts->ca_file)
+               free(opts->ca_file);
+       if (opts->key_file)
+               free(opts->key_file);
+       if (opts->cert_file)
+               free(opts->cert_file);
+       if (opts->crl_file)
+               free(opts->crl_file);
+} /* free_options */
+
+/*
+ * public API
+ */
+
+sdb_ssl_client_t *
+sdb_ssl_client_create(sdb_ssl_options_t *opts)
+{
+       sdb_ssl_client_t *client;
+
+       client = calloc(1, sizeof(*client));
+       if (! client)
+               return NULL;
+
+       if (copy_options(&client->opts, opts)) {
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+
+       client->ctx = SSL_CTX_new(SSLv23_client_method());
+       if (! client->ctx) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL context");
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+
+       if (! SSL_CTX_load_verify_locations(client->ctx,
+                               client->opts.ca_file, NULL)) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to load CA file");
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+       if (! SSL_CTX_use_certificate_file(client->ctx,
+                               client->opts.cert_file, SSL_FILETYPE_PEM)) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to load cert file '%s'",
+                               client->opts.cert_file);
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+       if (! SSL_CTX_use_PrivateKey_file(client->ctx,
+                               client->opts.key_file, SSL_FILETYPE_PEM)) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to load key file '%s'",
+                               client->opts.key_file);
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+       if (! SSL_CTX_check_private_key(client->ctx)) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to verify key (%s)",
+                               client->opts.key_file);
+               sdb_ssl_client_destroy(client);
+               return NULL;
+       }
+
+       SSL_CTX_set_mode(client->ctx, SSL_MODE_AUTO_RETRY);
+       SSL_CTX_set_verify(client->ctx, SSL_VERIFY_PEER, NULL);
+       SSL_CTX_set_verify_depth(client->ctx, 1);
+       return client;
+} /* sdb_ssl_client_create */
+
+void
+sdb_ssl_client_destroy(sdb_ssl_client_t *client)
+{
+       if (! client)
+               return;
+
+       if (client->ctx)
+               SSL_CTX_free(client->ctx);
+       free_options(&client->opts);
+       free(client);
+} /* sdb_ssl_client_destroy */
+
+sdb_ssl_session_t *
+sdb_ssl_client_connect(sdb_ssl_client_t *client, int fd)
+{
+       sdb_ssl_session_t *session;
+       int status;
+       BIO *bio;
+
+       if ((! client) || (fd < 0))
+               return NULL;
+
+       session = calloc(1, sizeof(*session));
+       if (! session)
+               return NULL;
+
+       bio = BIO_new_socket(fd, BIO_NOCLOSE);
+       if (! bio) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL socket");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       session->ssl = SSL_new(client->ctx);
+       if (! session->ssl) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL object");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       SSL_set_bio(session->ssl, bio, bio);
+
+       if ((status = SSL_connect(session->ssl)) <= 0) {
+               ssl_log_err(SDB_LOG_ERR, session->ssl, status,
+                               "ssl: Failed to initialize SSL session");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       if (SSL_get_verify_result(session->ssl) != X509_V_OK) {
+               sdb_log(SDB_LOG_ERR, "Failed to verify SSL connection");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       return session;
+} /* sdb_ssl_client_connect */
+
+sdb_ssl_server_t *
+sdb_ssl_server_create(sdb_ssl_options_t *opts)
+{
+       sdb_ssl_server_t *server;
+
+       server = calloc(1, sizeof(*server));
+       if (! server)
+               return NULL;
+
+       if (copy_options(&server->opts, opts)) {
+               sdb_ssl_server_destroy(server);
+               return NULL;
+       }
+
+       server->ctx = SSL_CTX_new(SSLv23_server_method());
+       if (! server->ctx) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL context");
+               sdb_ssl_server_destroy(server);
+               return NULL;
+       }
+
+       /* Recommendation documented at
+        * https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ */
+       if (! SSL_CTX_set_cipher_list(server->ctx,
+                               "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:"
+                               "DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:"
+                               "!aNULL:!MD5:!DSS")) {
+               sdb_log(SDB_LOG_ERR, "ssl: Invalid cipher list");
+               sdb_ssl_server_destroy(server);
+               return NULL;
+       }
+
+       if (! SSL_CTX_load_verify_locations(server->ctx,
+                               server->opts.ca_file, NULL)) {
+               ssl_log(SDB_LOG_ERR, "Failed to load CA file %s",
+                               server->opts.ca_file);
+               return NULL;
+       }
+       SSL_CTX_set_client_CA_list(server->ctx,
+                       SSL_load_client_CA_file(server->opts.ca_file));
+
+       if (! SSL_CTX_use_certificate_file(server->ctx,
+                               server->opts.cert_file, SSL_FILETYPE_PEM)) {
+               ssl_log(SDB_LOG_ERR, "Failed to load SSL cert file %s",
+                               server->opts.cert_file);
+               return NULL;
+       }
+       if (! SSL_CTX_use_PrivateKey_file(server->ctx,
+                               server->opts.key_file, SSL_FILETYPE_PEM)) {
+               ssl_log(SDB_LOG_ERR, "Failed to load SSL key file %s",
+                               server->opts.key_file);
+               return NULL;
+       }
+       if (! SSL_CTX_check_private_key(server->ctx)) {
+               ssl_log(SDB_LOG_ERR, "Failed to verify SSL private key");
+               return NULL;
+       }
+
+       SSL_CTX_set_mode(server->ctx, SSL_MODE_AUTO_RETRY);
+       SSL_CTX_set_verify(server->ctx,
+                       SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+       SSL_CTX_set_verify_depth(server->ctx, 1);
+
+       /* TODO: handle server->opts.crl_file */
+       return server;
+} /* sdb_ssl_server_create */
+
+void
+sdb_ssl_server_destroy(sdb_ssl_server_t *server)
+{
+       if (! server)
+               return;
+
+       if (server->ctx)
+               SSL_CTX_free(server->ctx);
+       free_options(&server->opts);
+       free(server);
+} /* sdb_ssl_server_destroy */
+
+sdb_ssl_session_t *
+sdb_ssl_server_accept(sdb_ssl_server_t *server, int fd)
+{
+       sdb_ssl_session_t *session;
+       int status;
+       BIO *bio;
+
+       if ((! server) || (fd < 0))
+               return NULL;
+
+       session = calloc(1, sizeof(*session));
+       if (! session)
+               return NULL;
+
+       bio = BIO_new_socket(fd, BIO_NOCLOSE);
+       if (! bio) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL socket");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       session->ssl = SSL_new(server->ctx);
+       if (! session->ssl) {
+               ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL object");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       SSL_set_bio(session->ssl, bio, bio);
+
+       while (42) {
+               if ((status = SSL_accept(session->ssl)) <= 0) {
+                       if (SSL_get_error(session->ssl, status) == SSL_ERROR_WANT_READ)
+                               continue;
+
+                       ssl_log_err(SDB_LOG_ERR, session->ssl, status,
+                                       "ssl: Failed to initialize SSL session");
+                       sdb_ssl_session_destroy(session);
+                       return NULL;
+               }
+               break;
+       }
+       if (SSL_get_verify_result(session->ssl) != X509_V_OK) {
+               sdb_log(SDB_LOG_ERR, "Failed to verify SSL connection");
+               sdb_ssl_session_destroy(session);
+               return NULL;
+       }
+       return session;
+} /* sdb_ssl_server_accept */
+
+void
+sdb_ssl_session_destroy(sdb_ssl_session_t *session)
+{
+       if (! session)
+               return;
+
+       if (session->ssl) {
+               SSL_shutdown(session->ssl);
+               SSL_clear(session->ssl);
+               SSL_free(session->ssl);
+       }
+       free(session);
+} /* sdb_ssl_session_destroy */
+
+char *
+sdb_ssl_session_peer(sdb_ssl_session_t *session)
+{
+       X509 *x509;
+       X509_NAME *name;
+
+       char *peer = NULL;
+       char p[1024];
+
+       if (! session)
+               return NULL;
+
+       x509 = SSL_get_peer_certificate(session->ssl);
+       if (! x509)
+               return NULL;
+       name = X509_get_subject_name(x509);
+       if (! name) {
+               X509_free(x509);
+               return NULL;
+       }
+
+       if (X509_NAME_get_text_by_NID(name, NID_commonName, p, sizeof(p)) > 0)
+               peer = strdup(p);
+
+       X509_free(x509);
+       return peer;
+} /* sdb_ssl_session_peer */
+
+ssize_t
+sdb_ssl_session_write(sdb_ssl_session_t *session, const void *buf, size_t n)
+{
+       int status;
+
+       if (! session)
+               return -1;
+
+       status = SSL_write(session->ssl, buf, (int)n);
+       if (status) {
+               if ((status < 0) && (errno != EAGAIN))
+                       ssl_log_err(SDB_LOG_ERR, session->ssl, status, "ssl: Write error");
+               return (ssize_t)status;
+       }
+
+       status = SSL_get_error(session->ssl, status);
+       if (status == SSL_ERROR_ZERO_RETURN)
+               return 0;
+
+       if ((status == SSL_ERROR_WANT_READ) || (status == SSL_ERROR_WANT_WRITE)) {
+               errno = EWOULDBLOCK;
+               return -1;
+       }
+       errno = ECONNRESET;
+       return -1;
+} /* sdb_ssl_session_write */
+
+ssize_t
+sdb_ssl_session_read(sdb_ssl_session_t *session, void *buf, size_t n)
+{
+       int status;
+
+       if (! session)
+               return -1;
+
+       status = SSL_read(session->ssl, buf, (int)n);
+       if (status) {
+               if ((status < 0) && (errno != EAGAIN))
+                       ssl_log_err(SDB_LOG_ERR, session->ssl, status, "ssl: Read error");
+               return (ssize_t)status;
+       }
+
+       status = SSL_get_error(session->ssl, status);
+       if (status == SSL_ERROR_ZERO_RETURN)
+               return 0;
+
+       if ((status == SSL_ERROR_WANT_READ) || (status == SSL_ERROR_WANT_WRITE)) {
+               errno = EWOULDBLOCK;
+               return -1;
+       }
+       errno = ECONNRESET;
+       return -1;
+} /* sdb_ssl_session_read */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+