]> jfr.im git - solanum.git/blobdiff - librb/src/gnutls.c
librb/helper: call rb_clear_cloexec on child fds
[solanum.git] / librb / src / gnutls.c
index b374dd0c911e0cf1cbb144eac06e92c9c5e0a188..0741c1b52f986890fe463bae2fae2cbbfe445c63 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  librb: a library used by charybdis and other things
+ *  libratbox: a library used by ircd-ratbox and other things
  *  gnutls.c: gnutls related code
  *
  *  Copyright (C) 2007-2008 ircd-ratbox development team
 
 #include <librb_config.h>
 #include <rb_lib.h>
+
+#ifdef HAVE_GNUTLS
+
 #include <commio-int.h>
 #include <commio-ssl.h>
 #include <stdbool.h>
-#ifdef HAVE_GNUTLS
 
 #include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
+
 #include <gnutls/abstract.h>
+#include <gnutls/x509.h>
 
 #if (GNUTLS_VERSION_MAJOR < 3)
 # include <gcrypt.h>
 # include <gnutls/crypto.h>
 #endif
 
-static gnutls_certificate_credentials_t x509;
-static gnutls_dh_params_t dh_params;
-static gnutls_priority_t default_priority;
+#include "gnutls_ratbox.h"
 
-/* These are all used for getting GnuTLS to supply a client cert. */
+typedef enum
+{
+       RB_FD_TLS_DIRECTION_IN = 0,
+       RB_FD_TLS_DIRECTION_OUT = 1
+} rb_fd_tls_direction;
+
+#define SSL_P(x) *((gnutls_session_t *) ((x)->ssl))
+
+
+
+// Server side variables
+static gnutls_certificate_credentials_t server_cert_key;
+static gnutls_dh_params_t server_dhp;
+
+// Client side variables
 #define MAX_CERTS 6
-static unsigned int x509_cert_count;
-static gnutls_x509_crt_t x509_cert[MAX_CERTS];
-static gnutls_x509_privkey_t x509_key;
-#if GNUTLS_VERSION_MAJOR < 3
-static int cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs,
-       const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr_st *st);
+static gnutls_x509_crt_t client_cert[MAX_CERTS];
+static gnutls_x509_privkey_t client_key;
+static unsigned int client_cert_count;
+
+// Shared variables
+static gnutls_priority_t default_priority;
+
+
+
+struct ssl_connect
+{
+       CNCB *callback;
+       void *data;
+       int timeout;
+};
+
+static const char *rb_ssl_strerror(int);
+static void rb_ssl_connect_realcb(rb_fde_t *, int, struct ssl_connect *);
+
+
+
+/*
+ * Internal GNUTLS-specific code
+ */
+
+/*
+ * We only have one certificate to authenticate with, as both a client and server.
+ *
+ * Unfortunately, GNUTLS tries to be clever, and as client, will attempt to use a certificate that the server will
+ * trust. We usually use self-signed certs, though, so the result of this search is always nothing. Therefore, it
+ * uses no certificate to authenticate as a client. This is undesirable, as it breaks fingerprint authentication;
+ * e.g. the connect::fingerprint on the remote ircd will not match.
+ *
+ * Thus, we use this callback to force GNUTLS to authenticate with our (server) certificate as a client.
+ */
+static int
+rb_ssl_cert_auth_cb(gnutls_session_t session,
+                    const gnutls_datum_t *const req_ca_rdn, const int req_ca_rdn_len,
+                    const gnutls_pk_algorithm_t *const sign_algos, const int sign_algos_len,
+#if (GNUTLS_VERSION_MAJOR < 3)
+                    gnutls_retr_st *const st)
+#else
+                    gnutls_retr2_st *const st)
+#endif
+{
+#if (GNUTLS_VERSION_MAJOR < 3)
+       st->type = GNUTLS_CRT_X509;
 #else
-static int cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs,
-       const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr2_st *st);
+       st->cert_type = GNUTLS_CRT_X509;
+       st->key_type = GNUTLS_PRIVKEY_X509;
 #endif
 
-#define SSL_P(x) *((gnutls_session_t *)F->ssl)
+       st->ncerts = client_cert_count;
+       st->cert.x509 = client_cert;
+       st->key.x509 = client_key;
+       st->deinit_all = 0;
 
-void
-rb_ssl_shutdown(rb_fde_t *F)
-{
-       int i;
-       if(F == NULL || F->ssl == NULL)
-               return;
-       for(i = 0; i < 4; i++)
-       {
-               if(gnutls_bye(SSL_P(F), GNUTLS_SHUT_RDWR) == GNUTLS_E_SUCCESS)
-                       break;
-       }
-       gnutls_deinit(SSL_P(F));
-       rb_free(F->ssl);
+       return 0;
 }
 
-unsigned int
-rb_ssl_handshake_count(rb_fde_t *F)
+static ssize_t
+rb_sock_net_recv(gnutls_transport_ptr_t context_ptr, void *const buf, const size_t count)
 {
-       return F->handshake_count;
+       const int fd = rb_get_fd((rb_fde_t *)context_ptr);
+
+       return recv(fd, buf, count, 0);
 }
 
-void
-rb_ssl_clear_handshake_count(rb_fde_t *F)
+static ssize_t
+rb_sock_net_xmit(gnutls_transport_ptr_t context_ptr, const void *const buf, const size_t count)
 {
-       F->handshake_count = 0;
+       const int fd = rb_get_fd((rb_fde_t *)context_ptr);
+
+       return send(fd, buf, count, 0);
 }
 
 static void
-rb_ssl_timeout(rb_fde_t *F, void *notused)
+rb_ssl_init_fd(rb_fde_t *const F, const rb_fd_tls_direction dir)
 {
-       lrb_assert(F->accept != NULL);
-       F->accept->callback(F, RB_ERR_TIMEOUT, NULL, 0, F->accept->data);
-}
+       F->ssl = rb_malloc(sizeof(gnutls_session_t));
 
+       if(F->ssl == NULL)
+       {
+               rb_lib_log("%s: rb_malloc: allocation failure", __func__);
+               rb_close(F);
+               return;
+       }
 
-static int
-do_ssl_handshake(rb_fde_t *F, PF * callback, void *data)
-{
-       int ret;
-       int flags;
+       unsigned int init_flags = 0;
 
-       ret = gnutls_handshake(SSL_P(F));
-       if(ret < 0)
+       switch(dir)
        {
-               if((ret == GNUTLS_E_INTERRUPTED && rb_ignore_errno(errno)) || ret == GNUTLS_E_AGAIN)
-               {
-                       if(gnutls_record_get_direction(SSL_P(F)) == 0)
-                               flags = RB_SELECT_READ;
-                       else
-                               flags = RB_SELECT_WRITE;
-                       rb_setselect(F, flags, callback, data);
-                       return 0;
-               }
-               F->ssl_errno = ret;
-               return -1;
+       case RB_FD_TLS_DIRECTION_IN:
+               init_flags |= GNUTLS_SERVER;
+               break;
+       case RB_FD_TLS_DIRECTION_OUT:
+               init_flags |= GNUTLS_CLIENT;
+               break;
        }
-       return 1;               /* handshake is finished..go about life */
+
+       gnutls_init((gnutls_session_t *) F->ssl, init_flags);
+       gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, server_cert_key);
+       gnutls_dh_set_prime_bits(SSL_P(F), 2048);
+
+       gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) F);
+       gnutls_transport_set_pull_function(SSL_P(F), rb_sock_net_recv);
+       gnutls_transport_set_push_function(SSL_P(F), rb_sock_net_xmit);
+
+       if (gnutls_priority_set(SSL_P(F), default_priority) != GNUTLS_E_SUCCESS)
+               gnutls_set_default_priority(SSL_P(F));
+
+       if(dir == RB_FD_TLS_DIRECTION_IN)
+               gnutls_certificate_server_set_request(SSL_P(F), GNUTLS_CERT_REQUEST);
 }
 
 static void
-rb_ssl_tryaccept(rb_fde_t *F, void *data)
+rb_ssl_accept_common(rb_fde_t *const F, void *const data)
 {
-       int ret;
-       struct acceptdata *ad;
-
+       lrb_assert(F != NULL);
        lrb_assert(F->accept != NULL);
+       lrb_assert(F->accept->callback != NULL);
+       lrb_assert(F->ssl != NULL);
 
-       ret = do_ssl_handshake(F, rb_ssl_tryaccept, NULL);
+       errno = 0;
 
-       /* do_ssl_handshake does the rb_setselect */
-       if(ret == 0)
+       const int ret = gnutls_handshake(SSL_P(F));
+       const int err = errno;
+
+       if(ret == GNUTLS_E_AGAIN || (ret == GNUTLS_E_INTERRUPTED && (err == 0 || rb_ignore_errno(err))))
+       {
+               unsigned int flags = (gnutls_record_get_direction(SSL_P(F)) == 0) ? RB_SELECT_READ : RB_SELECT_WRITE;
+               rb_setselect(F, flags, rb_ssl_accept_common, data);
                return;
+       }
 
-       ad = F->accept;
-       F->accept = NULL;
+       // These 2 calls may affect errno, which is why we save it above and restore it below
        rb_settimeout(F, 0, NULL, NULL);
        rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL);
 
-       if(ret > 0)
+       struct acceptdata *const ad = F->accept;
+       F->accept = NULL;
+
+       if(ret == GNUTLS_E_SUCCESS)
+       {
+               F->handshake_count++;
                ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data);
+       }
+       else if(ret == GNUTLS_E_INTERRUPTED && err != 0)
+       {
+               errno = err;
+               ad->callback(F, RB_ERROR, NULL, 0, ad->data);
+       }
        else
+       {
+               errno = EIO;
+               F->ssl_errno = (unsigned long) -ret;
                ad->callback(F, RB_ERROR_SSL, NULL, 0, ad->data);
+       }
 
        rb_free(ad);
 }
 
-void
-rb_ssl_start_accepted(rb_fde_t *new_F, ACCB * cb, void *data, int timeout)
+static void
+rb_ssl_connect_common(rb_fde_t *const F, void *const data)
 {
-       gnutls_session_t *ssl;
-       new_F->type |= RB_FD_SSL;
-       ssl = new_F->ssl = rb_malloc(sizeof(gnutls_session_t));
-       new_F->accept = rb_malloc(sizeof(struct acceptdata));
+       lrb_assert(F != NULL);
+       lrb_assert(F->ssl != NULL);
 
-       new_F->accept->callback = cb;
-       new_F->accept->data = data;
-       rb_settimeout(new_F, timeout, rb_ssl_timeout, NULL);
+       errno = 0;
 
-       new_F->accept->addrlen = 0;
+       const int ret = gnutls_handshake(SSL_P(F));
+       const int err = errno;
 
-       gnutls_init(ssl, GNUTLS_SERVER);
-       gnutls_set_default_priority(*ssl);
-       gnutls_credentials_set(*ssl, GNUTLS_CRD_CERTIFICATE, x509);
-       gnutls_dh_set_prime_bits(*ssl, 1024);
-       gnutls_transport_set_ptr(*ssl, (gnutls_transport_ptr_t) (long int)new_F->fd);
-       gnutls_certificate_server_set_request(*ssl, GNUTLS_CERT_REQUEST);
-       gnutls_priority_set(*ssl, default_priority);
-
-       if(do_ssl_handshake(new_F, rb_ssl_tryaccept, NULL))
+       if(ret == GNUTLS_E_AGAIN || (ret == GNUTLS_E_INTERRUPTED && (err == 0 || rb_ignore_errno(err))))
        {
-               struct acceptdata *ad = new_F->accept;
-               new_F->accept = NULL;
-               ad->callback(new_F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data);
-               rb_free(ad);
+               unsigned int flags = (gnutls_record_get_direction(SSL_P(F)) == 0) ? RB_SELECT_READ : RB_SELECT_WRITE;
+               rb_setselect(F, flags, rb_ssl_connect_common, data);
+               return;
        }
 
-}
-
-
+       // These 2 calls may affect errno, which is why we save it above and restore it below
+       rb_settimeout(F, 0, NULL, NULL);
+       rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL);
 
+       struct ssl_connect *const sconn = data;
 
-void
-rb_ssl_accept_setup(rb_fde_t *F, rb_fde_t *new_F, struct sockaddr *st, int addrlen)
-{
-       new_F->type |= RB_FD_SSL;
-       new_F->ssl = rb_malloc(sizeof(gnutls_session_t));
-       new_F->accept = rb_malloc(sizeof(struct acceptdata));
-
-       new_F->accept->callback = F->accept->callback;
-       new_F->accept->data = F->accept->data;
-       rb_settimeout(new_F, 10, rb_ssl_timeout, NULL);
-       memcpy(&new_F->accept->S, st, addrlen);
-       new_F->accept->addrlen = addrlen;
-
-       gnutls_init((gnutls_session_t *) new_F->ssl, GNUTLS_SERVER);
-       gnutls_set_default_priority(SSL_P(new_F));
-       gnutls_credentials_set(SSL_P(new_F), GNUTLS_CRD_CERTIFICATE, x509);
-       gnutls_dh_set_prime_bits(SSL_P(new_F), 1024);
-       gnutls_transport_set_ptr(SSL_P(new_F), (gnutls_transport_ptr_t) (long int)rb_get_fd(new_F));
-       gnutls_certificate_server_set_request(SSL_P(new_F), GNUTLS_CERT_REQUEST);
-       gnutls_priority_set(SSL_P(F), default_priority);
-
-       if(do_ssl_handshake(F, rb_ssl_tryaccept, NULL))
-       {
-               struct acceptdata *ad = F->accept;
-               F->accept = NULL;
-               ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data);
-               rb_free(ad);
+       if(ret == GNUTLS_E_SUCCESS)
+       {
+               F->handshake_count++;
+               rb_ssl_connect_realcb(F, RB_OK, sconn);
+       }
+       else if(ret == GNUTLS_E_INTERRUPTED && err != 0)
+       {
+               errno = err;
+               rb_ssl_connect_realcb(F, RB_ERROR, sconn);
+       }
+       else
+       {
+               errno = EIO;
+               F->ssl_errno = (unsigned long) -ret;
+               rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn);
        }
 }
 
-
-
+static const char *
+rb_ssl_strerror(const int err)
+{
+       return gnutls_strerror(err);
+}
 
 static ssize_t
-rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size_t count)
+rb_ssl_read_or_write(const int r_or_w, rb_fde_t *const F, void *const rbuf, const void *const wbuf, const size_t count)
 {
        ssize_t ret;
-       gnutls_session_t *ssl = F->ssl;
+
+       errno = 0;
 
        if(r_or_w == 0)
-               ret = gnutls_record_recv(*ssl, rbuf, count);
+               ret = gnutls_record_recv(SSL_P(F), rbuf, count);
        else
-               ret = gnutls_record_send(*ssl, wbuf, count);
+               ret = gnutls_record_send(SSL_P(F), wbuf, count);
+
+       if(ret >= 0)
+               return ret;
 
-       if(ret < 0)
+       if(ret == GNUTLS_E_AGAIN || (ret == GNUTLS_E_INTERRUPTED && (errno == 0 || rb_ignore_errno(errno))))
        {
-               switch (ret)
-               {
-               case GNUTLS_E_AGAIN:
-               case GNUTLS_E_INTERRUPTED:
-                       if(rb_ignore_errno(errno))
-                       {
-                               if(gnutls_record_get_direction(*ssl) == 0)
-                                       return RB_RW_SSL_NEED_READ;
-                               else
-                                       return RB_RW_SSL_NEED_WRITE;
-                               break;
-                       }
-               default:
-                       F->ssl_errno = ret;
-                       errno = EIO;
-                       return RB_RW_IO_ERROR;
-               }
+               if(gnutls_record_get_direction(SSL_P(F)) == 0)
+                       return RB_RW_SSL_NEED_READ;
+               else
+                       return RB_RW_SSL_NEED_WRITE;
        }
-       return ret;
-}
 
-ssize_t
-rb_ssl_read(rb_fde_t *F, void *buf, size_t count)
-{
-       return rb_ssl_read_or_write(0, F, buf, NULL, count);
-}
+       if(ret == GNUTLS_E_INTERRUPTED && errno != 0)
+               return RB_RW_IO_ERROR;
 
-ssize_t
-rb_ssl_write(rb_fde_t *F, const void *buf, size_t count)
-{
-       return rb_ssl_read_or_write(1, F, NULL, buf, count);
+       errno = EIO;
+       F->ssl_errno = (unsigned long) -ret;
+       return RB_RW_SSL_ERROR;
 }
 
 #if (GNUTLS_VERSION_MAJOR < 3)
 static void
-rb_gcry_random_seed(void *unused)
+rb_gcry_random_seed(void *const unused)
 {
        gcry_fast_random_poll();
 }
 #endif
 
-int
-rb_init_ssl(void)
+static void
+rb_free_datum_t(gnutls_datum_t *const datum)
 {
-       gnutls_global_init();
-
-       if(gnutls_certificate_allocate_credentials(&x509) != GNUTLS_E_SUCCESS)
-       {
-               rb_lib_log("rb_init_ssl: Unable to allocate SSL/TLS certificate credentials");
-               return 0;
-       }
-
-#if GNUTLS_VERSION_MAJOR < 3
-       gnutls_certificate_client_set_retrieve_function(x509, cert_callback);
-#else
-       gnutls_certificate_set_retrieve_function(x509, cert_callback);
-#endif
-
-#if (GNUTLS_VERSION_MAJOR < 3)
-       rb_event_addish("rb_gcry_random_seed", rb_gcry_random_seed, NULL, 300);
-#endif
+       if(datum == NULL)
+               return;
 
-       return 1;
+       rb_free(datum->data);
+       rb_free(datum);
 }
 
-/* We only have one certificate to authenticate with, as both client and server.  Unfortunately,
- * GnuTLS tries to be clever, and as client, will attempt to use a certificate that the server
- * will trust.  We usually use self-signed certs, though, so the result of this search is always
- * nothing.  Therefore, it uses no certificate to authenticate as a client.  This is undesirable
- * as it breaks fingerprint auth.  Thus, we use this callback to force GnuTLS to always
- * authenticate with our certificate at all times.
- */
-#if GNUTLS_VERSION_MAJOR < 3
-static int
-cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs,
-       const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr_st *st)
-#else
-static int
-cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs,
-       const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr2_st *st)
-#endif
+static gnutls_datum_t *
+rb_load_file_into_datum_t(const char *const file)
 {
-       /* XXX - ugly hack. Tell GnuTLS to use the first (only) certificate we have for auth. */
-#if (GNUTLS_VERSION_MAJOR < 3)
-       st->type = GNUTLS_CRT_X509;
-#else
-       st->cert_type = GNUTLS_CRT_X509;
-       st->key_type = GNUTLS_PRIVKEY_X509;
-#endif
-       st->ncerts = x509_cert_count;
-       st->cert.x509 = x509_cert;
-       st->key.x509 = x509_key;
-       st->deinit_all = 0;
+       const int datum_fd = open(file, O_RDONLY);
+       if(datum_fd < 0)
+               return NULL;
 
-       return 0;
-}
+       struct stat fileinfo;
+       if(fstat(datum_fd, &fileinfo) != 0)
+       {
+               (void) close(datum_fd);
+               return NULL;
+       }
 
-static void
-rb_free_datum_t(gnutls_datum_t * d)
-{
-       rb_free(d->data);
-       rb_free(d);
-}
+       const size_t datum_size = (fileinfo.st_size < 131072) ? (size_t) fileinfo.st_size : 131072;
+       if(datum_size == 0)
+       {
+               (void) close(datum_fd);
+               return NULL;
+       }
 
-static gnutls_datum_t *
-rb_load_file_into_datum_t(const char *file)
-{
-       FILE *f;
        gnutls_datum_t *datum;
-       struct stat fileinfo;
-       if((f = fopen(file, "r")) == NULL)
+       if((datum = rb_malloc(sizeof *datum)) == NULL)
+       {
+               (void) close(datum_fd);
                return NULL;
-       if(fstat(fileno(f), &fileinfo))
+       }
+       if((datum->data = rb_malloc(datum_size + 1)) == NULL)
+       {
+               rb_free(datum);
+               (void) close(datum_fd);
                return NULL;
+       }
 
-       datum = rb_malloc(sizeof(gnutls_datum_t));
+       for(size_t data_read = 0; data_read < datum_size; )
+       {
+               ssize_t ret = read(datum_fd, ((unsigned char *)datum->data) + data_read, datum_size - data_read);
 
-       if(fileinfo.st_size > 131072)   /* deal with retards */
-               datum->size = 131072;
-       else
-               datum->size = fileinfo.st_size;
+               if(ret <= 0)
+               {
+                       rb_free_datum_t(datum);
+                       (void) close(datum_fd);
+                       return NULL;
+               }
+
+               data_read += (size_t) ret;
+       }
+       (void) close(datum_fd);
 
-       datum->data = rb_malloc(datum->size + 1);
-       fread(datum->data, datum->size, 1, f);
-       fclose(f);
+       datum->data[datum_size] = '\0';
+       datum->size = (unsigned int) datum_size;
        return datum;
 }
 
-int
-rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile, const char *cipher_list)
+static int
+make_certfp(gnutls_x509_crt_t cert, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
 {
-       int ret;
-       const char *err;
-       gnutls_datum_t *d_cert, *d_key;
-       if(cert == NULL)
+       int hashlen;
+       gnutls_digest_algorithm_t md_type;
+
+       bool spki = false;
+
+       switch(method)
        {
-               rb_lib_log("rb_setup_ssl_server: No certificate file");
+       case RB_SSL_CERTFP_METH_CERT_SHA1:
+               hashlen = RB_SSL_CERTFP_LEN_SHA1;
+               md_type = GNUTLS_DIG_SHA1;
+               break;
+       case RB_SSL_CERTFP_METH_SPKI_SHA256:
+               spki = true;
+       case RB_SSL_CERTFP_METH_CERT_SHA256:
+               hashlen = RB_SSL_CERTFP_LEN_SHA256;
+               md_type = GNUTLS_DIG_SHA256;
+               break;
+       case RB_SSL_CERTFP_METH_SPKI_SHA512:
+               spki = true;
+       case RB_SSL_CERTFP_METH_CERT_SHA512:
+               hashlen = RB_SSL_CERTFP_LEN_SHA512;
+               md_type = GNUTLS_DIG_SHA512;
+               break;
+       default:
                return 0;
        }
 
-       if((d_cert = rb_load_file_into_datum_t(cert)) == NULL)
+       if(! spki)
        {
-               rb_lib_log("rb_setup_ssl_server: Error loading certificate: %s", strerror(errno));
-               return 0;
+               size_t digest_size = (size_t) hashlen;
+
+               if(gnutls_x509_crt_get_fingerprint(cert, md_type, certfp, &digest_size) != 0)
+                       return 0;
+
+               return hashlen;
        }
 
-       if((d_key = rb_load_file_into_datum_t(keyfile)) == NULL)
-       {
-               rb_lib_log("rb_setup_ssl_server: Error loading key: %s", strerror(errno));
+       gnutls_pubkey_t pubkey;
+
+       if(gnutls_pubkey_init(&pubkey) != 0)
                return 0;
-       }
 
-       /* In addition to creating the certificate set, we also need to store our cert elsewhere
-        * so we can force GnuTLS to identify with it when acting as a client.
-        */
-       gnutls_x509_privkey_init(&x509_key);
-       if ((ret = gnutls_x509_privkey_import(x509_key, d_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS)
+       if(gnutls_pubkey_import_x509(pubkey, cert, 0) != 0)
        {
-               rb_lib_log("rb_setup_ssl_server: Error loading key file: %s", gnutls_strerror(ret));
+               gnutls_pubkey_deinit(pubkey);
                return 0;
        }
 
-       x509_cert_count = MAX_CERTS;
-       if ((ret = gnutls_x509_crt_list_import(x509_cert, &x509_cert_count, d_cert, GNUTLS_X509_FMT_PEM,
-               GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED)) < 0)
+       unsigned char derkey[262144];   // Should be big enough to hold any SubjectPublicKeyInfo structure
+       size_t derkey_len = sizeof derkey;
+
+       if(gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, derkey, &derkey_len) != 0)
        {
-               rb_lib_log("rb_setup_ssl_server: Error loading certificate: %s", gnutls_strerror(ret));
+               gnutls_pubkey_deinit(pubkey);
                return 0;
        }
-       x509_cert_count = ret;
 
-       if((ret =
-           gnutls_certificate_set_x509_key_mem(x509, d_cert, d_key,
-                                               GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS)
-       {
-               rb_lib_log("rb_setup_ssl_server: Error loading certificate or key file: %s",
-                          gnutls_strerror(ret));
+       gnutls_pubkey_deinit(pubkey);
+
+       if(gnutls_hash_fast(md_type, derkey, derkey_len, certfp) != 0)
                return 0;
-       }
 
-       rb_free_datum_t(d_cert);
-       rb_free_datum_t(d_key);
+       return hashlen;
+}
 
-       if(dhfile != NULL)
-       {
-               if(gnutls_dh_params_init(&dh_params) == GNUTLS_E_SUCCESS)
-               {
-                       gnutls_datum_t *data;
-                       int xret;
-                       data = rb_load_file_into_datum_t(dhfile);
-                       if(data != NULL)
-                       {
-                               xret = gnutls_dh_params_import_pkcs3(dh_params, data,
-                                                                    GNUTLS_X509_FMT_PEM);
-                               if(xret < 0)
-                                       rb_lib_log
-                                               ("rb_setup_ssl_server: Error parsing DH file: %s\n",
-                                                gnutls_strerror(xret));
-                               rb_free_datum_t(data);
-                       }
-                       gnutls_certificate_set_dh_params(x509, dh_params);
-               }
-               else
-                       rb_lib_log("rb_setup_ssl_server: Unable to setup DH parameters");
-       }
 
-       ret = gnutls_priority_init(&default_priority, cipher_list, &err);
-       if (ret < 0)
+
+/*
+ * External GNUTLS-specific code
+ */
+
+void
+rb_ssl_shutdown(rb_fde_t *const F)
+{
+       if(F == NULL || F->ssl == NULL)
+               return;
+
+       for(int i = 0; i < 4; i++)
        {
-               rb_lib_log("rb_setup_ssl_server: syntax error (using defaults instead) in ssl cipher list at: %s", err);
-               gnutls_priority_init(&default_priority, NULL, &err);
-               return 1;
+               int ret = gnutls_bye(SSL_P(F), GNUTLS_SHUT_RDWR);
+
+               if(ret != GNUTLS_E_INTERRUPTED && ret != GNUTLS_E_AGAIN)
+                       break;
        }
 
-       return 1;
+       gnutls_deinit(SSL_P(F));
+
+       rb_free(F->ssl);
+       F->ssl = NULL;
 }
 
 int
-rb_ssl_listen(rb_fde_t *F, int backlog, int defer_accept)
+rb_init_ssl(void)
 {
-       int result;
+       int ret;
 
-       result = rb_listen(F, backlog, defer_accept);
-       F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL;
+       if((ret = gnutls_global_init()) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_global_init: %s", __func__, rb_ssl_strerror(ret));
+               return 0;
+       }
 
-       return result;
+#if (GNUTLS_VERSION_MAJOR < 3)
+       rb_event_addish("rb_gcry_random_seed", rb_gcry_random_seed, NULL, 300);
+#endif
+
+       return 1;
 }
 
-struct ssl_connect
+int
+rb_setup_ssl_server(const char *const certfile, const char *keyfile,
+                    const char *const dhfile, const char *cipherlist)
 {
-       CNCB *callback;
-       void *data;
-       int timeout;
-};
-
-static void
-rb_ssl_connect_realcb(rb_fde_t *F, int status, struct ssl_connect *sconn)
-{
-       F->connect->callback = sconn->callback;
-       F->connect->data = sconn->data;
-       rb_free(sconn);
-       rb_connect_callback(F, status);
-}
+       if(certfile == NULL)
+       {
+               rb_lib_log("%s: no certificate file specified", __func__);
+               return 0;
+       }
 
-static void
-rb_ssl_tryconn_timeout_cb(rb_fde_t *F, void *data)
-{
-       rb_ssl_connect_realcb(F, RB_ERR_TIMEOUT, data);
-}
+       if(keyfile == NULL)
+               keyfile = certfile;
 
-static void
-rb_ssl_tryconn_cb(rb_fde_t *F, void *data)
-{
-       struct ssl_connect *sconn = data;
-       int ret;
+       if(cipherlist == NULL)
+               cipherlist = rb_gnutls_default_priority_str;
 
-       ret = do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn);
 
-       switch (ret)
+       gnutls_datum_t *const d_cert = rb_load_file_into_datum_t(certfile);
+       if(d_cert == NULL)
        {
-       case -1:
-               rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn);
-               break;
-       case 0:
-               /* do_ssl_handshake does the rb_setselect stuff */
-               return;
-       default:
-               break;
-
-
+               rb_lib_log("%s: Error loading certificate: %s", __func__, strerror(errno));
+               return 0;
        }
-       rb_ssl_connect_realcb(F, RB_OK, sconn);
-}
 
-static void
-rb_ssl_tryconn(rb_fde_t *F, int status, void *data)
-{
-       struct ssl_connect *sconn = data;
-       if(status != RB_OK)
+       gnutls_datum_t *const d_key = rb_load_file_into_datum_t(keyfile);
+       if(d_key == NULL)
        {
-               rb_ssl_connect_realcb(F, status, sconn);
-               return;
+               rb_lib_log("%s: Error loading key: %s", __func__, strerror(errno));
+               rb_free_datum_t(d_cert);
+               return 0;
        }
 
-       F->type |= RB_FD_SSL;
+       int ret;
 
+       if((ret = gnutls_certificate_allocate_credentials(&server_cert_key)) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_certificate_allocate_credentials: %s", __func__, rb_ssl_strerror(ret));
+               rb_free_datum_t(d_cert);
+               rb_free_datum_t(d_key);
+               return 0;
+       }
 
-       rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
-       F->ssl = rb_malloc(sizeof(gnutls_session_t));
-       gnutls_init(F->ssl, GNUTLS_CLIENT);
-       gnutls_set_default_priority(SSL_P(F));
-       gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509);
-       gnutls_dh_set_prime_bits(SSL_P(F), 1024);
-       gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd);
-       gnutls_priority_set(SSL_P(F), default_priority);
+#if (GNUTLS_VERSION_MAJOR < 3)
+       gnutls_certificate_client_set_retrieve_function(server_cert_key, rb_ssl_cert_auth_cb);
+#else
+       gnutls_certificate_set_retrieve_function(server_cert_key, rb_ssl_cert_auth_cb);
+#endif
 
-       do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn);
-}
+       if((ret = gnutls_certificate_set_x509_key_mem(server_cert_key, d_cert, d_key,
+                                                     GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_certificate_set_x509_key_mem: %s", __func__, rb_ssl_strerror(ret));
+               gnutls_certificate_free_credentials(server_cert_key);
+               rb_free_datum_t(d_cert);
+               rb_free_datum_t(d_key);
+               return 0;
+       }
+       if((ret = gnutls_x509_crt_list_import(client_cert, &client_cert_count, d_cert, GNUTLS_X509_FMT_PEM,
+                                             GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED)) < 1)
+       {
+               rb_lib_log("%s: gnutls_x509_crt_list_import: %s", __func__, rb_ssl_strerror(ret));
+               gnutls_certificate_free_credentials(server_cert_key);
+               rb_free_datum_t(d_cert);
+               rb_free_datum_t(d_key);
+               return 0;
+       }
+       client_cert_count = (unsigned int) ret;
 
-void
-rb_connect_tcp_ssl(rb_fde_t *F, struct sockaddr *dest,
-                  struct sockaddr *clocal, CNCB * callback, void *data, int timeout)
-{
-       struct ssl_connect *sconn;
-       if(F == NULL)
-               return;
+       if((ret = gnutls_x509_privkey_init(&client_key)) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_x509_privkey_init: %s", __func__, rb_ssl_strerror(ret));
+               gnutls_certificate_free_credentials(server_cert_key);
+               for(unsigned int i = 0; i < client_cert_count; i++)
+                       gnutls_x509_crt_deinit(client_cert[i]);
+               rb_free_datum_t(d_cert);
+               rb_free_datum_t(d_key);
+               return 0;
+       }
+       if((ret = gnutls_x509_privkey_import(client_key, d_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_x509_privkey_import: %s", __func__, rb_ssl_strerror(ret));
+               gnutls_certificate_free_credentials(server_cert_key);
+               for(unsigned int i = 0; i < client_cert_count; i++)
+                       gnutls_x509_crt_deinit(client_cert[i]);
+               gnutls_x509_privkey_deinit(client_key);
+               rb_free_datum_t(d_cert);
+               rb_free_datum_t(d_key);
+               return 0;
+       }
 
-       sconn = rb_malloc(sizeof(struct ssl_connect));
-       sconn->data = data;
-       sconn->callback = callback;
-       sconn->timeout = timeout;
-       rb_connect_tcp(F, dest, clocal, rb_ssl_tryconn, sconn, timeout);
+       rb_free_datum_t(d_cert);
+       rb_free_datum_t(d_key);
 
-}
+       if(dhfile != NULL)
+       {
+               gnutls_datum_t *const d_dhp = rb_load_file_into_datum_t(dhfile);
 
-void
-rb_ssl_start_connected(rb_fde_t *F, CNCB * callback, void *data, int timeout)
-{
-       struct ssl_connect *sconn;
-       if(F == NULL)
-               return;
+               if(d_dhp == NULL)
+               {
+                       rb_lib_log("%s: Error parsing DH parameters: %s", __func__, strerror(errno));
+                       gnutls_certificate_free_credentials(server_cert_key);
+                       for(unsigned int i = 0; i < client_cert_count; i++)
+                               gnutls_x509_crt_deinit(client_cert[i]);
+                       gnutls_x509_privkey_deinit(client_key);
+                       return 0;
+               }
+               if((ret = gnutls_dh_params_init(&server_dhp)) != GNUTLS_E_SUCCESS)
+               {
+                       rb_lib_log("%s: Error parsing DH parameters: %s", __func__, rb_ssl_strerror(ret));
+                       gnutls_certificate_free_credentials(server_cert_key);
+                       for(unsigned int i = 0; i < client_cert_count; i++)
+                               gnutls_x509_crt_deinit(client_cert[i]);
+                       gnutls_x509_privkey_deinit(client_key);
+                       rb_free_datum_t(d_dhp);
+                       return 0;
+               }
+               if((ret = gnutls_dh_params_import_pkcs3(server_dhp, d_dhp, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS)
+               {
+                       rb_lib_log("%s: Error parsing DH parameters: %s", __func__, rb_ssl_strerror(ret));
+                       gnutls_certificate_free_credentials(server_cert_key);
+                       for(unsigned int i = 0; i < client_cert_count; i++)
+                               gnutls_x509_crt_deinit(client_cert[i]);
+                       gnutls_x509_privkey_deinit(client_key);
+                       gnutls_dh_params_deinit(server_dhp);
+                       rb_free_datum_t(d_dhp);
+                       return 0;
+               }
 
-       sconn = rb_malloc(sizeof(struct ssl_connect));
-       sconn->data = data;
-       sconn->callback = callback;
-       sconn->timeout = timeout;
-       F->connect = rb_malloc(sizeof(struct conndata));
-       F->connect->callback = callback;
-       F->connect->data = data;
-       F->type |= RB_FD_SSL;
-       F->ssl = rb_malloc(sizeof(gnutls_session_t));
+               gnutls_certificate_set_dh_params(server_cert_key, server_dhp);
+               rb_free_datum_t(d_dhp);
+       }
 
-       gnutls_init(F->ssl, GNUTLS_CLIENT);
-       gnutls_set_default_priority(SSL_P(F));
-       gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509);
-       gnutls_dh_set_prime_bits(SSL_P(F), 1024);
-       gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd);
-       gnutls_priority_set(SSL_P(F), default_priority);
+       const char *err = NULL;
+       if((ret = gnutls_priority_init(&default_priority, cipherlist, &err)) != GNUTLS_E_SUCCESS)
+       {
+               rb_lib_log("%s: gnutls_priority_init: %s, error begins at '%s'? -- using defaults instead",
+                          __func__, rb_ssl_strerror(ret), err ? err : "<unknown>");
 
-       rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
+               (void) gnutls_priority_init(&default_priority, NULL, &err);
+       }
 
-       do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn);
+       rb_lib_log("%s: TLS configuration successful", __func__);
+       return 1;
 }
 
 int
-rb_init_prng(const char *path, prng_seed_t seed_type)
+rb_init_prng(const char *const path, prng_seed_t seed_type)
 {
-#if GNUTLS_VERSION_MAJOR < 3
+#if (GNUTLS_VERSION_MAJOR < 3)
        gcry_fast_random_poll();
+       rb_lib_log("%s: PRNG initialised", __func__);
+#else
+       rb_lib_log("%s: Skipping PRNG initialisation; not required by GNUTLS v3+ backend", __func__);
 #endif
        return 1;
 }
 
 int
-rb_get_random(void *buf, size_t length)
+rb_get_random(void *const buf, const size_t length)
 {
-#if GNUTLS_VERSION_MAJOR < 3
+#if (GNUTLS_VERSION_MAJOR < 3)
        gcry_randomize(buf, length, GCRY_STRONG_RANDOM);
 #else
        gnutls_rnd(GNUTLS_RND_KEY, buf, length);
@@ -590,140 +654,262 @@ rb_get_random(void *buf, size_t length)
 }
 
 const char *
-rb_get_ssl_strerror(rb_fde_t *F)
+rb_get_ssl_strerror(rb_fde_t *const F)
 {
-       return gnutls_strerror(F->ssl_errno);
+       const int err = (int) F->ssl_errno;
+       return rb_ssl_strerror(-err);
 }
 
 int
-rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method)
+rb_get_ssl_certfp(rb_fde_t *const F, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
 {
-       gnutls_x509_crt_t cert;
-       gnutls_digest_algorithm_t algo;
-       unsigned int cert_list_size;
-       const gnutls_datum_t *cert_list;
-       uint8_t digest[RB_SSL_CERTFP_LEN * 2];
-       size_t digest_size;
-       int len;
-       bool spki = false;
+       if(gnutls_certificate_type_get(SSL_P(F)) != GNUTLS_CRT_X509)
+               return 0;
 
-       if (gnutls_certificate_type_get(SSL_P(F)) != GNUTLS_CRT_X509)
+       unsigned int cert_list_size = 0;
+       const gnutls_datum_t *const cert_list = gnutls_certificate_get_peers(SSL_P(F), &cert_list_size);
+       if(cert_list == NULL || cert_list_size < 1)
                return 0;
 
-       if (gnutls_x509_crt_init(&cert) < 0)
+       gnutls_x509_crt_t peer_cert;
+       if(gnutls_x509_crt_init(&peer_cert) != 0)
                return 0;
 
-       cert_list_size = 0;
-       cert_list = gnutls_certificate_get_peers(SSL_P(F), &cert_list_size);
-       if (cert_list == NULL)
+       if(gnutls_x509_crt_import(peer_cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
        {
-               gnutls_x509_crt_deinit(cert);
+               gnutls_x509_crt_deinit(peer_cert);
                return 0;
        }
 
-       if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
+       const int len = make_certfp(peer_cert, certfp, method);
+
+       gnutls_x509_crt_deinit(peer_cert);
+
+       return len;
+}
+
+int
+rb_get_ssl_certfp_file(const char *const filename, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
+{
+       gnutls_datum_t *const datum_cert = rb_load_file_into_datum_t(filename);
+       if(datum_cert == NULL)
+               return -1;
+
+       gnutls_x509_crt_t cert;
+       if(gnutls_x509_crt_init(&cert) != 0)
        {
-               gnutls_x509_crt_deinit(cert);
+               rb_free_datum_t(datum_cert);
                return 0;
        }
-
-       switch(method)
+       if(gnutls_x509_crt_import(cert, datum_cert, GNUTLS_X509_FMT_PEM) < 0)
        {
-       case RB_SSL_CERTFP_METH_CERT_SHA1:
-               algo = GNUTLS_DIG_SHA1;
-               len = RB_SSL_CERTFP_LEN_SHA1;
-               break;
-       case RB_SSL_CERTFP_METH_SPKI_SHA256:
-               spki = true;
-       case RB_SSL_CERTFP_METH_CERT_SHA256:
-               algo = GNUTLS_DIG_SHA256;
-               len = RB_SSL_CERTFP_LEN_SHA256;
-               break;
-       case RB_SSL_CERTFP_METH_SPKI_SHA512:
-               spki = true;
-       case RB_SSL_CERTFP_METH_CERT_SHA512:
-               algo = GNUTLS_DIG_SHA512;
-               len = RB_SSL_CERTFP_LEN_SHA512;
-               break;
-       default:
+               gnutls_x509_crt_deinit(cert);
+               rb_free_datum_t(datum_cert);
                return 0;
        }
 
-       if (!spki)
-       {
-               if (gnutls_x509_crt_get_fingerprint(cert, algo, digest, &digest_size) < 0)
-                       len = 0;
-       }
-       else
-       {
-               gnutls_pubkey_t pubkey;
-               unsigned char *der_pubkey = NULL;
-               size_t der_pubkey_len = 0;
+       const int len = make_certfp(cert, certfp, method);
 
-               if (gnutls_pubkey_init(&pubkey) == GNUTLS_E_SUCCESS)
-               {
-                       if (gnutls_pubkey_import_x509(pubkey, cert, 0) == GNUTLS_E_SUCCESS)
-                       {
-                               if (gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, der_pubkey, &der_pubkey_len) == GNUTLS_E_SHORT_MEMORY_BUFFER)
-                               {
-                                       der_pubkey = rb_malloc(der_pubkey_len);
-
-                                       if (gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, der_pubkey, &der_pubkey_len) != GNUTLS_E_SUCCESS)
-                                       {
-                                               rb_free(der_pubkey);
-                                               der_pubkey = NULL;
-                                       }
-                               }
-                       }
-
-                       gnutls_pubkey_deinit(pubkey);
-               }
+       gnutls_x509_crt_deinit(cert);
+       rb_free_datum_t(datum_cert);
 
-               if (der_pubkey)
-               {
-                       if (gnutls_hash_fast(algo, der_pubkey, der_pubkey_len, digest) != 0)
-                               len = 0;
+       return len;
+}
 
-                       rb_free(der_pubkey);
-               }
-               else
-               {
-                       len = 0;
-               }
+void
+rb_get_ssl_info(char *const buf, const size_t len)
+{
+       (void) snprintf(buf, len, "GNUTLS: compiled (v%s), library (v%s)",
+                       LIBGNUTLS_VERSION, gnutls_check_version(NULL));
+}
+
+const char *
+rb_ssl_get_cipher(rb_fde_t *const F)
+{
+       if(F == NULL || F->ssl == NULL)
+               return NULL;
+
+       static char buf[512];
+
+       gnutls_protocol_t version_ptr = gnutls_protocol_get_version(SSL_P(F));
+       gnutls_cipher_algorithm_t cipher_ptr = gnutls_cipher_get(SSL_P(F));
+
+       const char *const version = gnutls_protocol_get_name(version_ptr);
+       const char *const cipher = gnutls_cipher_get_name(cipher_ptr);
+
+       (void) snprintf(buf, sizeof buf, "%s, %s", version, cipher);
+
+       return buf;
+}
+
+ssize_t
+rb_ssl_read(rb_fde_t *const F, void *const buf, const size_t count)
+{
+       return rb_ssl_read_or_write(0, F, buf, NULL, count);
+}
+
+ssize_t
+rb_ssl_write(rb_fde_t *const F, const void *const buf, const size_t count)
+{
+       return rb_ssl_read_or_write(1, F, NULL, buf, count);
+}
+
+
+
+/*
+ * Internal library-agnostic code
+ */
+
+static void
+rb_ssl_connect_realcb(rb_fde_t *const F, const int status, struct ssl_connect *const sconn)
+{
+       lrb_assert(F != NULL);
+       lrb_assert(F->connect != NULL);
+
+       F->connect->callback = sconn->callback;
+       F->connect->data = sconn->data;
+
+       rb_connect_callback(F, status);
+       rb_free(sconn);
+}
+
+static void
+rb_ssl_timeout_cb(rb_fde_t *const F, void *const data)
+{
+       lrb_assert(F->accept != NULL);
+       lrb_assert(F->accept->callback != NULL);
+
+       F->accept->callback(F, RB_ERR_TIMEOUT, NULL, 0, F->accept->data);
+}
+
+static void
+rb_ssl_tryconn_timeout_cb(rb_fde_t *const F, void *const data)
+{
+       rb_ssl_connect_realcb(F, RB_ERR_TIMEOUT, data);
+}
+
+static void
+rb_ssl_tryconn(rb_fde_t *const F, const int status, void *const data)
+{
+       lrb_assert(F != NULL);
+
+       struct ssl_connect *const sconn = data;
+
+       if(status != RB_OK)
+       {
+               rb_ssl_connect_realcb(F, status, sconn);
+               return;
        }
 
-       if (len)
-               memcpy(certfp, digest, len);
+       F->type |= RB_FD_SSL;
 
-       gnutls_x509_crt_deinit(cert);
-       return len;
+       rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
+       rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_OUT);
+       rb_ssl_connect_common(F, sconn);
 }
 
+
+
+/*
+ * External library-agnostic code
+ */
+
 int
 rb_supports_ssl(void)
 {
        return 1;
 }
 
+unsigned int
+rb_ssl_handshake_count(rb_fde_t *const F)
+{
+       return F->handshake_count;
+}
+
 void
-rb_get_ssl_info(char *buf, size_t len)
+rb_ssl_clear_handshake_count(rb_fde_t *const F)
 {
-       snprintf(buf, len, "GNUTLS: compiled (%s), library(%s)",
-                   LIBGNUTLS_VERSION, gnutls_check_version(NULL));
+       F->handshake_count = 0;
 }
 
-const char *
-rb_ssl_get_cipher(rb_fde_t *F)
+void
+rb_ssl_start_accepted(rb_fde_t *const F, ACCB *const cb, void *const data, const int timeout)
 {
-       static char buf[1024];
+       F->type |= RB_FD_SSL;
 
-       snprintf(buf, sizeof(buf), "%s-%s-%s-%s",
-               gnutls_protocol_get_name(gnutls_protocol_get_version(SSL_P(F))),
-               gnutls_kx_get_name(gnutls_kx_get(SSL_P(F))),
-               gnutls_cipher_get_name(gnutls_cipher_get(SSL_P(F))),
-               gnutls_mac_get_name(gnutls_mac_get(SSL_P(F))));
+       F->accept = rb_malloc(sizeof(struct acceptdata));
+       F->accept->callback = cb;
+       F->accept->data = data;
+       F->accept->addrlen = 0;
+       (void) memset(&F->accept->S, 0x00, sizeof F->accept->S);
 
-       return buf;
+       rb_settimeout(F, timeout, rb_ssl_timeout_cb, NULL);
+       rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_IN);
+       rb_ssl_accept_common(F, NULL);
+}
+
+void
+rb_ssl_accept_setup(rb_fde_t *const srv_F, rb_fde_t *const cli_F, struct sockaddr *const st, const int addrlen)
+{
+       cli_F->type |= RB_FD_SSL;
+
+       cli_F->accept = rb_malloc(sizeof(struct acceptdata));
+       cli_F->accept->callback = srv_F->accept->callback;
+       cli_F->accept->data = srv_F->accept->data;
+       cli_F->accept->addrlen = (rb_socklen_t) addrlen;
+       (void) memset(&cli_F->accept->S, 0x00, sizeof cli_F->accept->S);
+       (void) memcpy(&cli_F->accept->S, st, (size_t) addrlen);
+
+       rb_settimeout(cli_F, 10, rb_ssl_timeout_cb, NULL);
+       rb_ssl_init_fd(cli_F, RB_FD_TLS_DIRECTION_IN);
+       rb_ssl_accept_common(cli_F, NULL);
+}
+
+int
+rb_ssl_listen(rb_fde_t *const F, const int backlog, const int defer_accept)
+{
+       int result = rb_listen(F, backlog, defer_accept);
+
+       F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL;
+
+       return result;
+}
+
+void
+rb_connect_tcp_ssl(rb_fde_t *const F, struct sockaddr *const dest, struct sockaddr *const clocal,
+                   CNCB *const callback, void *const data, const int timeout)
+{
+       if(F == NULL)
+               return;
+
+       struct ssl_connect *const sconn = rb_malloc(sizeof *sconn);
+       sconn->data = data;
+       sconn->callback = callback;
+       sconn->timeout = timeout;
+
+       rb_connect_tcp(F, dest, clocal, rb_ssl_tryconn, sconn, timeout);
+}
+
+void
+rb_ssl_start_connected(rb_fde_t *const F, CNCB *const callback, void *const data, const int timeout)
+{
+       if(F == NULL)
+               return;
+
+       struct ssl_connect *const sconn = rb_malloc(sizeof *sconn);
+       sconn->data = data;
+       sconn->callback = callback;
+       sconn->timeout = timeout;
+
+       F->connect = rb_malloc(sizeof(struct conndata));
+       F->connect->callback = callback;
+       F->connect->data = data;
+
+       F->type |= RB_FD_SSL;
+
+       rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
+       rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_OUT);
+       rb_ssl_connect_common(F, sconn);
 }
 
 #endif /* HAVE_GNUTLS */