There has been a plan for some time now to make smtpd use libtls
instead of openssl. Recent changes in libtls allow to move forward
with this.  Here is a diff to start the switch. I've tried to keep
it as small as possible, sticking to the necessary changes. There is
still a lot of code that can be removed but that will be done in a
second time.

There are two visible changes that the user must be aware of.

First, certificate verification is now delegated to libtls, so peer
certificate, if provided, can only be reported as either "valid" or
"unverified" (if no-verify is requested).  In other words, invalid
certificates can no longer (at least for now) be detected but still
accepted.

Secondly, the way SNI is configured has changed: the "pki" listener
option can now be used multiple times to add extra certificates on
a listener. Each tls-enabled listener must have at least one certificate
explicitly set. The extra ones are used for SNI, and only the specified
certificates will be used for this listener. So there is no more fallback
or global lookup on configured pki names.

Tests and comments would be greatly appreciated.

Eric.

Index: ca.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/ca.c,v
retrieving revision 1.37
diff -u -p -r1.37 ca.c
--- ca.c        31 Dec 2020 08:27:15 -0000      1.37
+++ ca.c        19 Jan 2021 11:09:54 -0000
@@ -69,6 +69,7 @@ static int ecdsae_do_verify(const unsign
     EC_KEY *);
 
 
+static struct dict pkeys;
 static uint64_t         reqid = 0;
 
 static void
@@ -132,26 +133,29 @@ ca_init(void)
        struct pki      *pki;
        const char      *k;
        void            *iter_dict;
+       char            *hash;
 
        log_debug("debug: init private ssl-tree");
+       dict_init(&pkeys);
        iter_dict = NULL;
        while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) {
                if (pki->pki_key == NULL)
                        continue;
 
-               if ((in = BIO_new_mem_buf(pki->pki_key,
-                   pki->pki_key_len)) == NULL)
-                       fatalx("ca_launch: key");
-
-               if ((pkey = PEM_read_bio_PrivateKey(in,
-                   NULL, NULL, NULL)) == NULL)
-                       fatalx("ca_launch: PEM");
+               in = BIO_new_mem_buf(pki->pki_key, pki->pki_key_len);
+               if (in == NULL)
+                       fatalx("ca_init: key");
+               pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
+               if (pkey == NULL)
+                       fatalx("ca_init: PEM");
                BIO_free(in);
 
-               pki->pki_pkey = pkey;
-
-               freezero(pki->pki_key, pki->pki_key_len);
-               pki->pki_key = NULL;
+               hash = ssl_pubkey_hash(pki->pki_cert, pki->pki_cert_len);
+               if (dict_check(&pkeys, hash))
+                       EVP_PKEY_free(pkey);
+               else
+                       dict_xset(&pkeys, hash, pkey);
+               free(hash);
        }
 }
 
@@ -223,15 +227,15 @@ end:
 void
 ca_imsg(struct mproc *p, struct imsg *imsg)
 {
+       EVP_PKEY                *pkey;
        RSA                     *rsa = NULL;
        EC_KEY                  *ecdsa = NULL;
        const void              *from = NULL;
        unsigned char           *to = NULL;
        struct msg               m;
-       const char              *pkiname;
+       const char              *hash;
        size_t                   flen, tlen, padding;
        int                      buf_len;
-       struct pki              *pki;
        int                      ret = 0;
        uint64_t                 id;
        int                      v;
@@ -267,16 +271,15 @@ ca_imsg(struct mproc *p, struct imsg *im
        case IMSG_CA_RSA_PRIVDEC:
                m_msg(&m, imsg);
                m_get_id(&m, &id);
-               m_get_string(&m, &pkiname);
+               m_get_string(&m, &hash);
                m_get_data(&m, &from, &flen);
                m_get_size(&m, &tlen);
                m_get_size(&m, &padding);
                m_end(&m);
 
-               pki = dict_get(env->sc_pki_dict, pkiname);
-               if (pki == NULL || pki->pki_pkey == NULL ||
-                   (rsa = EVP_PKEY_get1_RSA(pki->pki_pkey)) == NULL)
-                       fatalx("ca_imsg: invalid pki");
+               pkey = dict_get(&pkeys, hash);
+               if (pkey == NULL || (rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+                       fatalx("ca_imsg: invalid pkey hash");
 
                if ((to = calloc(1, tlen)) == NULL)
                        fatalx("ca_imsg: calloc");
@@ -306,14 +309,14 @@ ca_imsg(struct mproc *p, struct imsg *im
        case IMSG_CA_ECDSA_SIGN:
                m_msg(&m, imsg);
                m_get_id(&m, &id);
-               m_get_string(&m, &pkiname);
+               m_get_string(&m, &hash);
                m_get_data(&m, &from, &flen);
                m_end(&m);
 
-               pki = dict_get(env->sc_pki_dict, pkiname);
-               if (pki == NULL || pki->pki_pkey == NULL ||
-                   (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL)
-                       fatalx("ca_imsg: invalid pki");
+               pkey = dict_get(&pkeys, hash);
+               if (pkey == NULL ||
+                   (ecdsa = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
+                       fatalx("ca_imsg: invalid pkey hash");
 
                buf_len = ECDSA_size(ecdsa);
                if ((to = calloc(1, buf_len)) == NULL)
@@ -350,12 +353,12 @@ rsae_send_imsg(int flen, const unsigned 
        struct imsg      imsg;
        int              n, done = 0;
        const void      *toptr;
-       char            *pkiname;
+       char            *hash;
        size_t           tlen;
        struct msg       m;
        uint64_t         id;
 
-       if ((pkiname = RSA_get_ex_data(rsa, 0)) == NULL)
+       if ((hash = RSA_get_ex_data(rsa, 0)) == NULL)
                return (0);
 
        /*
@@ -365,7 +368,7 @@ rsae_send_imsg(int flen, const unsigned 
        m_create(p_ca, cmd, 0, 0, -1);
        reqid++;
        m_add_id(p_ca, reqid);
-       m_add_string(p_ca, pkiname);
+       m_add_string(p_ca, hash);
        m_add_data(p_ca, (const void *)from, (size_t)flen);
        m_add_size(p_ca, (size_t)RSA_size(rsa));
        m_add_size(p_ca, (size_t)padding);
@@ -536,13 +539,13 @@ ecdsae_send_enc_imsg(const unsigned char
        struct imsg      imsg;
        int              n, done = 0;
        const void      *toptr;
-       char            *pkiname;
+       char            *hash;
        size_t           tlen;
        struct msg       m;
        uint64_t         id;
        ECDSA_SIG       *sig = NULL;
 
-       if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL)
+       if ((hash = ECDSA_get_ex_data(eckey, 0)) == NULL)
                return (0);
 
        /*
@@ -552,7 +555,7 @@ ecdsae_send_enc_imsg(const unsigned char
        m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1);
        reqid++;
        m_add_id(p_ca, reqid);
-       m_add_string(p_ca, pkiname);
+       m_add_string(p_ca, hash);
        m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len);
        m_flush(p_ca);
 
Index: config.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/config.c,v
retrieving revision 1.53
diff -u -p -r1.53 config.c
--- config.c    19 Jan 2021 09:16:20 -0000      1.53
+++ config.c    19 Jan 2021 11:47:07 -0000
@@ -252,6 +252,7 @@ purge_config(uint8_t what)
        if (what & PURGE_LISTENERS) {
                while ((l = TAILQ_FIRST(env->sc_listeners)) != NULL) {
                        TAILQ_REMOVE(env->sc_listeners, l, entry);
+                       free(l->pki);
                        free(l);
                }
                free(env->sc_listeners);
Index: dispatcher.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/dispatcher.c,v
retrieving revision 1.1
diff -u -p -r1.1 dispatcher.c
--- dispatcher.c        31 Dec 2020 08:27:15 -0000      1.1
+++ dispatcher.c        19 Jan 2021 11:04:32 -0000
@@ -154,6 +154,8 @@ dispatcher(void)
 {
        struct passwd   *pw;
 
+       ca_engine_init();
+
        mda_postfork();
        mta_postfork();
        smtp_postfork();
@@ -195,8 +197,6 @@ dispatcher(void)
        config_peer(PROC_LKA);
        config_peer(PROC_CONTROL);
        config_peer(PROC_CA);
-
-       ca_engine_init();
 
        if (pledge("stdio inet unix recvfd sendfd", NULL) == -1)
                err(1, "pledge");
Index: iobuf.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/iobuf.c,v
retrieving revision 1.14
diff -u -p -r1.14 iobuf.c
--- iobuf.c     23 Jan 2021 16:11:11 -0000      1.14
+++ iobuf.c     25 Jan 2021 11:14:58 -0000
@@ -21,15 +21,14 @@
 
 #include <errno.h>
 #include <limits.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
-
 #ifdef IO_TLS
-#include <openssl/err.h>
-#include <openssl/ssl.h>
+#include <tls.h>
 #endif
+#include <unistd.h>
 
 #include "iobuf.h"
 
@@ -388,7 +387,7 @@ iobuf_flush(struct iobuf *io, int fd)
 #ifdef IO_TLS
 
 int
-iobuf_flush_tls(struct iobuf *io, void *tls)
+iobuf_flush_tls(struct iobuf *io, struct tls *tls)
 {
        ssize_t s;
 
@@ -400,55 +399,42 @@ iobuf_flush_tls(struct iobuf *io, void *
 }
 
 ssize_t
-iobuf_write_tls(struct iobuf *io, void *tls)
+iobuf_write_tls(struct iobuf *io, struct tls *tls)
 {
        struct ioqbuf   *q;
        ssize_t          n;
 
        q = io->outq;
-       n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos);
-       if (n <= 0) {
-               switch (SSL_get_error(tls, n)) {
-               case SSL_ERROR_WANT_READ:
-                       return (IOBUF_WANT_READ);
-               case SSL_ERROR_WANT_WRITE:
-                       return (IOBUF_WANT_WRITE);
-               case SSL_ERROR_ZERO_RETURN: /* connection closed */
-                       return (IOBUF_CLOSED);
-               case SSL_ERROR_SYSCALL:
-                       if (ERR_peek_last_error())
-                               return (IOBUF_TLSERROR);
-                       return (IOBUF_ERROR);
-               default:
-                       return (IOBUF_TLSERROR);
-               }
-       }
+
+       n = tls_write(tls, q->buf + q->rpos, q->wpos - q->rpos);
+       if (n == TLS_WANT_POLLIN)
+               return (IOBUF_WANT_READ);
+       else if (n == TLS_WANT_POLLOUT)
+               return (IOBUF_WANT_WRITE);
+       else if (n == 0)
+               return (IOBUF_CLOSED);
+       else if (n == -1)
+               return (IOBUF_ERROR);
+
        iobuf_drain(io, n);
 
        return (n);
 }
 
 ssize_t
-iobuf_read_tls(struct iobuf *io, void *tls)
+iobuf_read_tls(struct iobuf *io, struct tls *tls)
 {
        ssize_t n;
 
-       n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io));
-       if (n < 0) {
-               switch (SSL_get_error(tls, n)) {
-               case SSL_ERROR_WANT_READ:
-                       return (IOBUF_WANT_READ);
-               case SSL_ERROR_WANT_WRITE:
-                       return (IOBUF_WANT_WRITE);
-               case SSL_ERROR_SYSCALL:
-                       if (ERR_peek_last_error())
-                               return (IOBUF_TLSERROR);
-                       return (IOBUF_ERROR);
-               default:
-                       return (IOBUF_TLSERROR);
-               }
-       } else if (n == 0)
+       n = tls_read(tls, io->buf + io->wpos, iobuf_left(io));
+       if (n == TLS_WANT_POLLIN)
+               return (IOBUF_WANT_READ);
+       else if (n == TLS_WANT_POLLOUT)
+               return (IOBUF_WANT_WRITE);
+       else if (n == 0)
                return (IOBUF_CLOSED);
+       else if (n == -1)
+               return (IOBUF_ERROR);
 
        io->wpos += n;
 
Index: iobuf.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/iobuf.h,v
retrieving revision 1.5
diff -u -p -r1.5 iobuf.h
--- iobuf.h     12 Jun 2019 17:42:53 -0000      1.5
+++ iobuf.h     25 Jan 2021 11:14:33 -0000
@@ -35,11 +35,12 @@ struct iobuf {
        struct ioqbuf   *outqlast;
 };
 
+struct tls;
+
 #define IOBUF_WANT_READ                -1
 #define IOBUF_WANT_WRITE       -2
 #define IOBUF_CLOSED           -3
 #define IOBUF_ERROR            -4
-#define IOBUF_TLSERROR         -5
 
 int    iobuf_init(struct iobuf *, size_t, size_t);
 void   iobuf_clear(struct iobuf *);
@@ -53,7 +54,7 @@ size_t        iobuf_left(struct iobuf *);
 char   *iobuf_data(struct iobuf *);
 char   *iobuf_getline(struct iobuf *, size_t *);
 ssize_t        iobuf_read(struct iobuf *, int);
-ssize_t        iobuf_read_tls(struct iobuf *, void *);
+ssize_t        iobuf_read_tls(struct iobuf *, struct tls *);
 
 size_t  iobuf_queued(struct iobuf *);
 void*   iobuf_reserve(struct iobuf *, size_t);
@@ -62,6 +63,6 @@ int   iobuf_queuev(struct iobuf *, const s
 int    iobuf_fqueue(struct iobuf *, const char *, ...);
 int    iobuf_vfqueue(struct iobuf *, const char *, va_list);
 int    iobuf_flush(struct iobuf *, int);
-int    iobuf_flush_tls(struct iobuf *, void *);
+int    iobuf_flush_tls(struct iobuf *, struct tls *);
 ssize_t        iobuf_write(struct iobuf *, int);
-ssize_t        iobuf_write_tls(struct iobuf *, void *);
+ssize_t        iobuf_write_tls(struct iobuf *, struct tls *);
Index: ioev.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/ioev.c,v
retrieving revision 1.43
diff -u -p -r1.43 ioev.c
--- ioev.c      23 Jan 2021 16:11:11 -0000      1.43
+++ ioev.c      25 Jan 2021 11:16:47 -0000
@@ -27,16 +27,14 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#ifdef IO_TLS
+#include <tls.h>
+#endif
 #include <unistd.h>
 
 #include "ioev.h"
 #include "iobuf.h"
 
-#ifdef IO_TLS
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-#endif
-
 enum {
        IO_STATE_NONE,
        IO_STATE_CONNECT,
@@ -65,7 +63,9 @@ struct io {
        int              flags;
        int              state;
        struct event     ev;
-       void            *tls;
+       struct tls      *tls;
+       char            *name;
+
        const char      *error; /* only valid immediately on callback */
 };
 
@@ -85,9 +85,7 @@ void  io_frame_enter(const char *, struct
 void   io_frame_leave(struct io *);
 
 #ifdef IO_TLS
-void   ssl_error(const char *); /* XXX external */
-
-static const char* io_tls_error(void);
+void   io_dispatch_handshake_tls(int, short, void *);
 void   io_dispatch_accept_tls(int, short, void *);
 void   io_dispatch_connect_tls(int, short, void *);
 void   io_dispatch_read_tls(int, short, void *);
@@ -111,10 +109,9 @@ io_strio(struct io *io)
        ssl[0] = '\0';
 #ifdef IO_TLS
        if (io->tls) {
-               (void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d",
-                   SSL_get_version(io->tls),
-                   SSL_get_cipher_name(io->tls),
-                   SSL_get_cipher_bits(io->tls, NULL));
+               (void)snprintf(ssl, sizeof ssl, " tls=%s:%s",
+                   tls_conn_version(io->tls),
+                   tls_conn_cipher(io->tls));
        }
 #endif
 
@@ -272,7 +269,7 @@ io_free(struct io *io)
                current = NULL;
 
 #ifdef IO_TLS
-       SSL_free(io->tls);
+       tls_free(io->tls);
        io->tls = NULL;
 #endif
 
@@ -283,6 +280,7 @@ io_free(struct io *io)
                io->sock = -1;
        }
 
+       free(io->name);
        iobuf_clear(&io->iobuf);
        free(io);
 }
@@ -397,7 +395,7 @@ io_error(struct io *io)
        return io->error;
 }
 
-void *
+struct tls *
 io_tls(struct io *io)
 {
        return io->tls;
@@ -807,57 +805,85 @@ io_dispatch_connect(int fd, short ev, vo
 }
 
 #ifdef IO_TLS
-
-static const char*
-io_tls_error(void)
+int
+io_connect_tls(struct io *io, struct tls *tls, const char *hostname)
 {
-       static char     buf[128];
-       unsigned long   e;
+       int     mode;
+
+       mode = io->flags & IO_RW;
+       if (mode != IO_WRITE)
+               errx(1, "io_connect_tls: expect IO_WRITE mode");
+
+       if (io->tls)
+               errx(1, "io_connect_tls: TLS already started");
 
-       e = ERR_peek_last_error();
-       if (e) {
-               ERR_error_string(e, buf);
-               return (buf);
+       if (hostname) {
+               if ((io->name = strdup(hostname)) == NULL)
+                       err(1, "io_connect_tls");
        }
 
-       return ("No TLS error");
+       io->tls = tls;
+       io->state = IO_STATE_CONNECT_TLS;
+       io_reset(io, EV_WRITE, io_dispatch_connect_tls);
+
+       return (0);
 }
 
 int
-io_start_tls(struct io *io, void *tls)
+io_accept_tls(struct io *io, struct tls *tls)
 {
        int     mode;
 
        mode = io->flags & IO_RW;
-       if (mode == 0 || mode == IO_RW)
-               errx(1, "io_start_tls(): full-duplex or unset");
+       if (mode != IO_READ)
+               errx(1, "io_connect_tls: expect IO_READ mode");
 
        if (io->tls)
                errx(1, "io_start_tls(): TLS already started");
        io->tls = tls;
+       io->state = IO_STATE_ACCEPT_TLS;
+       io_reset(io, EV_READ, io_dispatch_accept_tls);
+
+       return (0);
+}
 
-       if (SSL_set_fd(io->tls, io->sock) == 0) {
-               ssl_error("io_start_tls:SSL_set_fd");
-               return (-1);
+void
+io_dispatch_handshake_tls(int fd, short event, void *humppa)
+{
+       struct io       *io = humppa;
+       int             ret;
+
+       io_frame_enter("io_dispatch_handshake_tls", io, event);
+
+       if (event == EV_TIMEOUT) {
+               io_callback(io, IO_TIMEOUT);
+               goto leave;
        }
 
-       if (mode == IO_WRITE) {
-               io->state = IO_STATE_CONNECT_TLS;
-               SSL_set_connect_state(io->tls);
-               io_reset(io, EV_WRITE, io_dispatch_connect_tls);
-       } else {
-               io->state = IO_STATE_ACCEPT_TLS;
-               SSL_set_accept_state(io->tls);
-               io_reset(io, EV_READ, io_dispatch_accept_tls);
+       if ((ret = tls_handshake(io->tls)) == 0) {
+               io->state = IO_STATE_UP;
+               io_callback(io, IO_TLSREADY);
+               goto leave;
+       }
+       if (ret == TLS_WANT_POLLIN)
+               io_reset(io, EV_READ, io_dispatch_handshake_tls);
+       else if (ret == TLS_WANT_POLLOUT)
+               io_reset(io, EV_WRITE, io_dispatch_handshake_tls);
+       else {
+               io->error = tls_error(io->tls);
+               io_callback(io, IO_ERROR);
        }
 
-       return (0);
+ leave:
+       io_frame_leave(io);
+       return;
 }
 
 void
 io_dispatch_accept_tls(int fd, short event, void *humppa)
 {
        struct io       *io = humppa;
+       struct tls      *cctx = NULL;
        int              ret;
 
        io_frame_enter("io_dispatch_accept_tls", io, event);
@@ -867,28 +893,17 @@ io_dispatch_accept_tls(int fd, short eve
                goto leave;
        }
 
-       if ((ret = SSL_accept(io->tls)) > 0) {
-               io->state = IO_STATE_UP;
-               io_callback(io, IO_TLSREADY);
+       if ((ret = tls_accept_socket(io->tls, &cctx, io->sock)) == 0) {
+               io->tls = cctx;
+               io_reset(io, EV_READ|EV_WRITE, io_dispatch_handshake_tls);
                goto leave;
        }
+       io->error = tls_error(io->tls);
+       io_callback(io, IO_ERROR);
 
-       switch (SSL_get_error(io->tls, ret)) {
-       case SSL_ERROR_WANT_READ:
-               io_reset(io, EV_READ, io_dispatch_accept_tls);
-               break;
-       case SSL_ERROR_WANT_WRITE:
-               io_reset(io, EV_WRITE, io_dispatch_accept_tls);
-               break;
-       default:
-               io->error = io_tls_error();
-               ssl_error("io_dispatch_accept_tls:SSL_accept");
-               io_callback(io, IO_ERROR);
-               break;
-       }
-
-    leave:
+ leave:
        io_frame_leave(io);
+       return;
 }
 
 void
@@ -904,27 +919,15 @@ io_dispatch_connect_tls(int fd, short ev
                goto leave;
        }
 
-       if ((ret = SSL_connect(io->tls)) > 0) {
-               io->state = IO_STATE_UP;
-               io_callback(io, IO_TLSREADY);
+       if ((ret = tls_connect_socket(io->tls, io->sock, io->name)) == 0) {
+               io_reset(io, EV_READ|EV_WRITE, io_dispatch_handshake_tls);
                goto leave;
        }
 
-       switch (SSL_get_error(io->tls, ret)) {
-       case SSL_ERROR_WANT_READ:
-               io_reset(io, EV_READ, io_dispatch_connect_tls);
-               break;
-       case SSL_ERROR_WANT_WRITE:
-               io_reset(io, EV_WRITE, io_dispatch_connect_tls);
-               break;
-       default:
-               io->error = io_tls_error();
-               ssl_error("io_dispatch_connect_ssl:SSL_connect");
-               io_callback(io, IO_TLSERROR);
-               break;
-       }
+       io->error = tls_error(io->tls);
+       io_callback(io, IO_ERROR);
 
-    leave:
+ leave:
        io_frame_leave(io);
 }
 
@@ -932,7 +935,7 @@ void
 io_dispatch_read_tls(int fd, short event, void *humppa)
 {
        struct io       *io = humppa;
-       int              n, saved_errno;
+       int              n;
 
        io_frame_enter("io_dispatch_read_tls", io, event);
 
@@ -943,7 +946,7 @@ io_dispatch_read_tls(int fd, short event
 
 again:
        iobuf_normalize(&io->iobuf);
-       switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) {
+       switch ((n = iobuf_read_tls(&io->iobuf, io->tls))) {
        case IOBUF_WANT_READ:
                io_reset(io, EV_READ, io_dispatch_read_tls);
                break;
@@ -954,20 +957,13 @@ again:
                io_callback(io, IO_DISCONNECTED);
                break;
        case IOBUF_ERROR:
-               saved_errno = errno;
-               io->error = strerror(errno);
-               errno = saved_errno;
-               io_callback(io, IO_ERROR);
-               break;
-       case IOBUF_TLSERROR:
-               io->error = io_tls_error();
-               ssl_error("io_dispatch_read_tls:SSL_read");
+               io->error = tls_error(io->tls);
                io_callback(io, IO_ERROR);
                break;
        default:
                io_debug("io_dispatch_read_tls(...) -> r=%d\n", n);
                io_callback(io, IO_DATAIN);
-               if (current == io && IO_READING(io) && SSL_pending(io->tls))
+               if (current == io && IO_READING(io))
                        goto again;
        }
 
@@ -979,7 +975,7 @@ void
 io_dispatch_write_tls(int fd, short event, void *humppa)
 {
        struct io       *io = humppa;
-       int              n, saved_errno;
+       int              n;
        size_t           w2, w;
 
        io_frame_enter("io_dispatch_write_tls", io, event);
@@ -990,7 +986,7 @@ io_dispatch_write_tls(int fd, short even
        }
 
        w = io_queued(io);
-       switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) {
+       switch ((n = iobuf_write_tls(&io->iobuf, io->tls))) {
        case IOBUF_WANT_READ:
                io_reset(io, EV_READ, io_dispatch_write_tls);
                break;
@@ -1001,14 +997,7 @@ io_dispatch_write_tls(int fd, short even
                io_callback(io, IO_DISCONNECTED);
                break;
        case IOBUF_ERROR:
-               saved_errno = errno;
-               io->error = strerror(errno);
-               errno = saved_errno;
-               io_callback(io, IO_ERROR);
-               break;
-       case IOBUF_TLSERROR:
-               io->error = io_tls_error();
-               ssl_error("io_dispatch_write_tls:SSL_write");
+               io->error = tls_error(io->tls);
                io_callback(io, IO_ERROR);
                break;
        default:
Index: ioev.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/ioev.h,v
retrieving revision 1.18
diff -u -p -r1.18 ioev.h
--- ioev.h      11 Sep 2019 04:19:19 -0000      1.18
+++ ioev.h      18 Jan 2021 19:58:19 -0000
@@ -18,7 +18,6 @@
 enum {
        IO_CONNECTED = 0,       /* connection successful        */
        IO_TLSREADY,            /* TLS started successfully     */
-       IO_TLSERROR,            /* XXX - needs more work        */
        IO_DATAIN,              /* new data in input buffer     */
        IO_LOWAT,               /* output queue running low     */
        IO_DISCONNECTED,        /* error?                       */
@@ -30,6 +29,7 @@ enum {
 #define IO_OUT         0x02
 
 struct io;
+struct tls;
 
 void io_set_nonblocking(int);
 void io_set_nolinger(int);
@@ -46,11 +46,12 @@ void io_pause(struct io *, int);
 void io_resume(struct io *, int);
 void io_reload(struct io *);
 int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *);
-int io_start_tls(struct io *, void *);
+int io_connect_tls(struct io *, struct tls *, const char *);
+int io_accept_tls(struct io *, struct tls *);
 const char* io_strio(struct io *);
 const char* io_strevent(int);
 const char* io_error(struct io *);
-void* io_tls(struct io *);
+struct tls* io_tls(struct io *);
 int io_fileno(struct io *);
 int io_paused(struct io *, int);
 
Index: mta.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/mta.c,v
retrieving revision 1.234
diff -u -p -r1.234 mta.c
--- mta.c       21 Dec 2019 10:34:07 -0000      1.234
+++ mta.c       22 Jan 2021 09:00:21 -0000
@@ -38,10 +38,12 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#include <tls.h>
 #include <unistd.h>
 
 #include "smtpd.h"
 #include "log.h"
+#include "ssl.h"
 
 #define MAXERROR_PER_ROUTE     4
 
@@ -57,6 +59,7 @@
 #define RELAY_ONHOLD           0x01
 #define RELAY_HOLDQ            0x02
 
+static void mta_setup_dispatcher(struct dispatcher *);
 static void mta_handle_envelope(struct envelope *, const char *);
 static void mta_query_smarthost(struct envelope *);
 static void mta_on_smarthost(struct envelope *, const char *);
@@ -138,6 +141,12 @@ int mta_is_blocked(struct mta_source *, 
 static int mta_block_cmp(const struct mta_block *, const struct mta_block *);
 SPLAY_PROTOTYPE(mta_block_tree, mta_block, entry, mta_block_cmp);
 
+/*
+ * This function is not publicy exported because it is a hack until libtls
+ * has a proper privsep setup
+ */
+void tls_config_use_fake_private_key(struct tls_config *config);
+
 static struct mta_relay_tree           relays;
 static struct mta_domain_tree          domains;
 static struct mta_host_tree            hosts;
@@ -463,6 +472,70 @@ mta_imsg(struct mproc *p, struct imsg *i
 void
 mta_postfork(void)
 {
+       struct dispatcher *dispatcher;
+       const char *key;
+       void *iter;
+
+       iter = NULL;
+       while (dict_iter(env->sc_dispatchers, &iter, &key, (void 
**)&dispatcher)) {
+               log_debug("%s: %s", __func__, key);
+               mta_setup_dispatcher(dispatcher);
+       }
+}
+
+static void
+mta_setup_dispatcher(struct dispatcher *dispatcher)
+{
+       struct dispatcher_remote *remote;
+       static const char *dheparams[] = { "none", "auto", "legacy" };
+       struct tls_config *config;
+       struct pki *pki;
+       struct ca *ca;
+
+       if (dispatcher->type != DISPATCHER_REMOTE)
+               return;
+
+       remote = &dispatcher->u.remote;
+
+       if ((config = tls_config_new()) == NULL)
+               fatal("smtpd: tls_config_new");
+
+       if (env->sc_tls_ciphers) {
+               if (tls_config_set_ciphers(config, env->sc_tls_ciphers) == -1)
+                       err(1, "%s", tls_config_error(config));
+       }
+
+       if (remote->pki) {
+               pki = dict_get(env->sc_pki_dict, remote->pki);
+               if (pki == NULL)
+                       err(1, "client pki \"%s\" not found ", remote->pki);
+
+               tls_config_set_dheparams(config, dheparams[pki->pki_dhe]);
+               tls_config_use_fake_private_key(config);
+               if (tls_config_set_keypair_mem(config, pki->pki_cert,
+                   pki->pki_cert_len, NULL, 0) == -1)
+               fatal("tls_config_set_keypair_mem");
+       }
+
+       if (remote->ca) {
+               ca = dict_get(env->sc_ca_dict, remote->ca);
+               if (tls_config_set_ca_mem(config, ca->ca_cert, ca->ca_cert_len)
+                   == -1)
+                       fatal("tls_config_set_ca_mem");
+       }
+       else if (tls_config_set_ca_file(config, tls_default_ca_cert_file())
+           == -1)
+               fatal("tls_config_set_ca_file");
+
+       if (remote->tls_noverify) {
+               tls_config_insecure_noverifycert(config);
+               tls_config_insecure_noverifyname(config);
+               tls_config_insecure_noverifytime(config);
+       }
+       else
+               tls_config_verify(config);
+
+       remote->tls_config = config;
 }
 
 void
Index: mta_session.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/mta_session.c,v
retrieving revision 1.138
diff -u -p -r1.138 mta_session.c
--- mta_session.c       21 Dec 2020 11:44:07 -0000      1.138
+++ mta_session.c       19 Jan 2021 10:09:32 -0000
@@ -43,6 +43,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#include <tls.h>
 #include <unistd.h>
 
 #include "smtpd.h"
@@ -153,11 +154,8 @@ static void mta_send(struct mta_session 
 static ssize_t mta_queue_data(struct mta_session *);
 static void mta_response(struct mta_session *, char *);
 static const char * mta_strstate(int);
-static void mta_cert_init(struct mta_session *);
-static void mta_cert_init_cb(void *, int, const char *, const void *, size_t);
-static void mta_cert_verify(struct mta_session *);
-static void mta_cert_verify_cb(void *, int);
-static void mta_tls_verified(struct mta_session *);
+static void mta_tls_init(struct mta_session *);
+static void mta_tls_started(struct mta_session *);
 static struct mta_session *mta_tree_pop(struct tree *, uint64_t);
 static const char * dsn_strret(enum dsn_ret);
 static const char * dsn_strnotify(uint8_t);
@@ -974,7 +972,7 @@ mta_response(struct mta_session *s, char
                        return;
                }
 
-               mta_cert_init(s);
+               mta_tls_init(s);
                break;
 
        case MTA_AUTH_PLAIN:
@@ -1229,7 +1227,7 @@ mta_io(struct io *io, int evt, void *arg
 
                if (s->use_smtps) {
                        io_set_write(io);
-                       mta_cert_init(s);
+                       mta_tls_init(s);
                }
                else {
                        mta_enter_state(s, MTA_BANNER);
@@ -1239,13 +1237,14 @@ mta_io(struct io *io, int evt, void *arg
 
        case IO_TLSREADY:
                log_info("%016"PRIx64" mta tls ciphers=%s",
-                   s->id, ssl_to_text(io_tls(s->io)));
+                   s->id, tls_to_text(io_tls(s->io)));
                s->flags |= MTA_TLS;
+               if (!s->relay->dispatcher->u.remote.tls_noverify)
+                       s->flags |= MTA_TLS_VERIFIED;
 
+               mta_tls_started(s);
                mta_report_link_tls(s,
-                   ssl_to_text(io_tls(s->io)));
-
-               mta_cert_verify(s);
+                   tls_to_text(io_tls(s->io)));
                break;
 
        case IO_DATAIN:
@@ -1378,7 +1377,6 @@ mta_io(struct io *io, int evt, void *arg
                break;
 
        case IO_ERROR:
-       case IO_TLSERROR:
                log_debug("debug: mta: %p: IO error: %s", s, io_error(io));
 
                if (s->state == MTA_STARTTLS && s->use_smtp_tls) {
@@ -1579,152 +1577,42 @@ mta_error(struct mta_session *s, const c
 }
 
 static void
-mta_cert_init(struct mta_session *s)
-{
-       const char *name;
-       int fallback;
-
-       if (s->relay->pki_name) {
-               name = s->relay->pki_name;
-               fallback = 0;
-       }
-       else {
-               name = s->helo;
-               fallback = 1;
-       }
-
-       if (cert_init(name, fallback, mta_cert_init_cb, s)) {
-               tree_xset(&wait_tls_init, s->id, s);
-               s->flags |= MTA_WAIT;
-       }
-}
-
-static void
-mta_cert_init_cb(void *arg, int status, const char *name, const void *cert,
-    size_t cert_len)
+mta_tls_init(struct mta_session *s)
 {
-       struct mta_session *s = arg;
-       void *ssl;
-       char *xname = NULL, *xcert = NULL;
-       union {
-               struct in_addr in4;
-               struct in6_addr in6;
-       } addrbuf;
-
-       if (s->flags & MTA_WAIT)
-               mta_tree_pop(&wait_tls_init, s->id);
+       struct tls_config *tls_config;
+       struct tls *tls;
 
-       if (status == CA_FAIL && s->relay->pki_name) {
-               log_info("%016"PRIx64" mta closing reason=ca-failure", s->id);
+       if ((tls = tls_client()) == NULL) {
+               log_info("%016"PRIx64" mta closing reason=tls-failure", s->id);
                mta_free(s);
                return;
        }
 
-       if (name)
-               xname = xstrdup(name);
-       if (cert)
-               xcert = xmemdup(cert, cert_len);
-       ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers);
-       free(xname);
-       free(xcert);
-       if (ssl == NULL)
-               fatal("mta: ssl_mta_init");
-
-       /*
-        * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not
-        * permitted in "HostName".
-        */
-       if (s->relay->domain->as_host == 1) {
-               if (inet_pton(AF_INET, s->relay->domain->name, &addrbuf) != 1 &&
-                   inet_pton(AF_INET6, s->relay->domain->name, &addrbuf) != 1) 
{
-                       log_debug("%016"PRIx64" mta tls setting SNI name=%s",
-                           s->id, s->relay->domain->name);
-                       if (SSL_set_tlsext_host_name(ssl, 
s->relay->domain->name) == 0)
-                               log_warnx("%016"PRIx64" mta tls setting SNI 
failed",
-                                  s->id);
-               }
-       }
-
-       io_start_tls(s->io, ssl);
-}
-
-static void
-mta_cert_verify(struct mta_session *s)
-{
-       const char *name;
-       int fallback;
-
-       if (s->relay->ca_name) {
-               name = s->relay->ca_name;
-               fallback = 0;
-       }
-       else {
-               name = s->helo;
-               fallback = 1;
-       }
-
-       if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) {
-               tree_xset(&wait_tls_verify, s->id, s);
-               io_pause(s->io, IO_IN);
-               s->flags |= MTA_WAIT;
-       }
-}
-
-static void
-mta_cert_verify_cb(void *arg, int status)
-{
-       struct mta_session *s = arg;
-       int match, resume = 0;
-       X509 *cert;
-
-       if (s->flags & MTA_WAIT) {
-               mta_tree_pop(&wait_tls_verify, s->id);
-               resume = 1;
-       }
-
-       if (status == CERT_OK) {
-               cert = SSL_get_peer_certificate(io_tls(s->io));
-               if (!cert)
-                       status = CERT_NOCERT;
-               else {
-                       match = 0;
-                       (void)ssl_check_name(cert, s->mxname, &match);
-                       X509_free(cert);
-                       if (!match) {
-                               log_info("%016"PRIx64" mta "
-                                   "ssl_check_name: no match for '%s' in cert",
-                                   s->id, s->mxname);
-                               status = CERT_INVALID;
-                       }
-               }
-       }
-
-       if (status == CERT_OK)
-               s->flags |= MTA_TLS_VERIFIED;
-       else if (s->relay->flags & RELAY_TLS_VERIFY) {
-               errno = 0;
-               mta_error(s, "SSL certificate check failed");
+       tls_config = s->relay->dispatcher->u.remote.tls_config;
+       if (tls_configure(tls, tls_config) == -1) {
+               log_info("%016"PRIx64" mta closing reason=tls-failure", s->id);
+               tls_free(tls);
                mta_free(s);
                return;
        }
 
-       mta_tls_verified(s);
-       if (resume)
-               io_resume(s->io, IO_IN);
+       io_connect_tls(s->io, tls, s->route->dst->ptrname);
 }
 
 static void
-mta_tls_verified(struct mta_session *s)
+mta_tls_started(struct mta_session *s)
 {
-       X509 *x;
-
-       x = SSL_get_peer_certificate(io_tls(s->io));
-       if (x) {
-         log_info("%016"PRIx64" mta "
-                  "server-cert-check result=\"%s\"",
-                  s->id,
-                  (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure");
-               X509_free(x);
+       if (tls_peer_cert_provided(io_tls(s->io))) {
+               log_info("%016"PRIx64" mta "
+                   "cert-check result=\"%s\" fingerprint=\"%s\"",
+                   s->id,
+                   (s->flags & MTA_TLS_VERIFIED) ? "valid" : "unverified",
+                   tls_peer_cert_hash(io_tls(s->io)));
+       }
+       else {
+               log_info("%016"PRIx64" smtp "
+                   "cert-check result=\"no certificate presented\"",
+                   s->id);
        }
 
        if (s->use_smtps) {
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/parse.y,v
retrieving revision 1.284
diff -u -p -r1.284 parse.y
--- parse.y     23 Jan 2021 16:11:11 -0000      1.284
+++ parse.y     25 Jan 2021 07:19:15 -0000
@@ -128,13 +128,15 @@ enum listen_options {
        LO_PROXY        = 0x008000,
 };
 
+#define PKI_MAX        32
 static struct listen_opts {
        char           *ifx;
        int             family;
        in_port_t       port;
        uint16_t        ssl;
        char           *filtername;
-       char           *pki;
+       char           *pki[PKI_MAX];
+       int             pkicount;
        char           *ca;
        uint16_t        auth;
        struct table   *authtable;
@@ -2316,12 +2318,11 @@ opt_if_listen : INET4 {
                        listen_opts.ssl = 
F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY;
                }
                | PKI STRING                    {
-                       if (listen_opts.options & LO_PKI) {
-                               yyerror("pki already specified");
+                       if (listen_opts.pkicount == PKI_MAX) {
+                               yyerror("too many pki specified");
                                YYERROR;
                        }
-                       listen_opts.options |= LO_PKI;
-                       listen_opts.pki = $2;
+                       listen_opts.pki[listen_opts.pkicount++] = $2;
                }
                | CA STRING                     {
                        if (listen_opts.options & LO_CA) {
@@ -3221,8 +3222,10 @@ create_if_listener(struct listen_opts *l
        if (lo->auth != 0 && !lo->ssl)
                errx(1, "invalid listen option: auth requires tls/smtps");
 
-       if (lo->pki && !lo->ssl)
+       if (lo->pkicount && !lo->ssl)
                errx(1, "invalid listen option: pki requires tls/smtps");
+       if (lo->pkicount == 0 && lo->ssl)
+               errx(1, "invalid listen option: pki required for tls/smtps");
 
        flags = lo->flags;
 
@@ -3259,6 +3262,8 @@ create_if_listener(struct listen_opts *l
 static void
 config_listener(struct listener *h,  struct listen_opts *lo)
 {
+       int i;
+
        h->fd = -1;
        h->port = lo->port;
        h->flags = lo->flags;
@@ -3273,17 +3278,19 @@ config_listener(struct listener *h,  str
                    sizeof(h->filter_name));
        }
 
-       h->pki_name[0] = '\0';
-
        if (lo->authtable != NULL)
                (void)strlcpy(h->authtable, lo->authtable->t_name, 
sizeof(h->authtable));
-       if (lo->pki != NULL) {
-               if (!lowercase(h->pki_name, lo->pki, sizeof(h->pki_name))) {
-                       log_warnx("pki name too long: %s", lo->pki);
-                       fatalx(NULL);
-               }
-               if (dict_get(conf->sc_pki_dict, h->pki_name) == NULL) {
-                       log_warnx("pki name not found: %s", lo->pki);
+
+       h->pkicount = lo->pkicount;
+       if (h->pkicount) {
+               h->pki = calloc(h->pkicount, sizeof(*h->pki));
+               if (h->pki == NULL)
+                       fatal("calloc");
+       }
+       for (i = 0; i < lo->pkicount; i++) {
+               h->pki[i] = dict_get(conf->sc_pki_dict, lo->pki[i]);
+               if (h->pki[i] == NULL) {
+                       log_warnx("pki name not found: %s", lo->pki[i]);
                        fatalx(NULL);
                }
        }
Index: smtp.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtp.c,v
retrieving revision 1.166
diff -u -p -r1.166 smtp.c
--- smtp.c      10 Aug 2019 16:07:01 -0000      1.166
+++ smtp.c      22 Jan 2021 09:00:46 -0000
@@ -34,6 +34,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <tls.h>
 #include <unistd.h>
 
 #include <openssl/ssl.h>
@@ -50,7 +51,7 @@ static void smtp_dropped(struct listener
 static int smtp_enqueue(void);
 static int smtp_can_accept(void);
 static void smtp_setup_listeners(void);
-static int smtp_sni_callback(SSL *, int *, void *);
+static void smtp_setup_listener_tls(struct listener *);
 
 int
 proxy_session(struct listener *listener, int sock,
@@ -62,6 +63,11 @@ proxy_session(struct listener *listener,
 
 static void smtp_accepted(struct listener *, int, const struct 
sockaddr_storage *, struct io *);
 
+/*
+ * This function are not publicy exported because it is a hack until libtls
+ * has a proper privsep setup
+ */
+void tls_config_use_fake_private_key(struct tls_config *config);
 
 #define        SMTP_FD_RESERVE 5
 static size_t  sessions;
@@ -145,6 +151,10 @@ smtp_setup_listeners(void)
                        }
                        fatal("smtpd: socket");
                }
+
+               if (l->flags & F_SSL)
+                       smtp_setup_listener_tls(l);
+
                opt = 1;
                if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt,
                    sizeof(opt)) == -1)
@@ -155,19 +165,77 @@ smtp_setup_listeners(void)
 }
 
 static void
+smtp_setup_listener_tls(struct listener *l)
+{
+       static const char *dheparams[] = { "none", "auto", "legacy" };
+       struct tls_config *config;
+       struct pki *pki;
+       struct ca *ca;
+       int i;
+
+       if ((config = tls_config_new()) == NULL)
+               fatal("smtpd: tls_config_new");
+
+       if (env->sc_tls_ciphers &&
+           tls_config_set_ciphers(config, env->sc_tls_ciphers) == -1)
+                       err(1, "%s", tls_config_error(config));
+
+       pki = l->pki[0];
+       if (pki == NULL)
+               fatal("no pki defined");
+
+       if (tls_config_set_dheparams(config, dheparams[pki->pki_dhe]) == -1)
+               fatal("tls_config_set_dheparams");
+
+       tls_config_use_fake_private_key(config);
+       for (i = 0; i < l->pkicount; i++) {
+               pki = l->pki[i];
+               if (i == 0) {
+                       if (tls_config_set_keypair_mem(config, pki->pki_cert,
+                           pki->pki_cert_len, NULL, 0) == -1)
+                               fatal("tls_config_set_keypair_mem");
+               } else {
+                       if (tls_config_add_keypair_mem(config, pki->pki_cert,
+                           pki->pki_cert_len, NULL, 0) == -1)
+                               fatal("tls_config_add_keypair_mem");
+               }
+       }
+       free(l->pki);
+       l->pkicount = 0;
+
+       if (l->ca_name[0]) {
+               ca = dict_get(env->sc_ca_dict, l->ca_name);
+               if (tls_config_set_ca_mem(config, ca->ca_cert, ca->ca_cert_len)
+                   == -1)
+                       fatal("tls_config_set_ca_mem");
+       }
+       else if (tls_config_set_ca_file(config, tls_default_ca_cert_file())
+           == -1)
+               fatal("tls_config_set_ca_file");
+
+       if (l->flags & F_TLS_VERIFY)
+               tls_config_verify_client(config);
+       else
+               tls_config_verify_client_optional(config);
+
+       l->tls = tls_server();
+       if (l->tls == NULL)
+               fatal("tls_server");
+       if (tls_configure(l->tls, config) == -1) {
+               fatal("tls_configure: %s", tls_error(l->tls));
+       }
+       tls_config_free(config);
+}
+
+
+static void
 smtp_setup_events(void)
 {
        struct listener *l;
-       struct pki      *pki;
-       SSL_CTX         *ssl_ctx;
-       void            *iter;
-       const char      *k;
 
        TAILQ_FOREACH(l, env->sc_listeners, entry) {
-               log_debug("debug: smtp: listen on %s port %d flags 0x%01x"
-                   " pki \"%s\""
-                   " ca \"%s\"", ss_to_text(&l->ss), ntohs(l->port),
-                   l->flags, l->pki_name, l->ca_name);
+               log_debug("debug: smtp: listen on %s port %d flags 0x%01x",
+                   ss_to_text(&l->ss), ntohs(l->port), l->flags);
 
                io_set_nonblocking(l->fd);
                if (listen(l->fd, SMTPD_BACKLOG) == -1)
@@ -178,14 +246,6 @@ smtp_setup_events(void)
                        event_add(&l->ev, NULL);
        }
 
-       iter = NULL;
-       while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) {
-               if (!ssl_setup((SSL_CTX **)&ssl_ctx, pki, smtp_sni_callback,
-                       env->sc_tls_ciphers))
-                       fatal("smtp_setup_events: ssl_setup failure");
-               dict_xset(env->sc_ssl_dict, k, ssl_ctx);
-       }
-
        purge_config(PURGE_PKI_KEYS);
 
        maxsessions = (getdtablesize() - getdtablecount()) / 2 - 
SMTP_FD_RESERVE;
@@ -317,22 +377,6 @@ smtp_collect(void)
                env->sc_flags &= ~SMTPD_SMTP_DISABLED;
                smtp_resume();
        }
-}
-
-static int
-smtp_sni_callback(SSL *ssl, int *ad, void *arg)
-{
-       const char              *sn;
-       void                    *ssl_ctx;
-
-       sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-       if (sn == NULL)
-               return SSL_TLSEXT_ERR_NOACK;
-       ssl_ctx = dict_get(env->sc_ssl_dict, sn);
-       if (ssl_ctx == NULL)
-               return SSL_TLSEXT_ERR_NOACK;
-       SSL_set_SSL_CTX(ssl, ssl_ctx);
-       return SSL_TLSEXT_ERR_OK;
 }
 
 static void
Index: smtp.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtp.h,v
retrieving revision 1.3
diff -u -p -r1.3 smtp.h
--- smtp.h      2 Sep 2019 20:05:21 -0000       1.3
+++ smtp.h      19 Jan 2021 09:41:16 -0000
@@ -46,6 +46,7 @@ struct smtp_params {
        /* TLS options */
        int                      tls_req;       /* requested TLS mode */
        int                      tls_verify;    /* need valid server 
certificate */
+       const char              *tls_servname;  /* SNI */
 
        /* SMTP options */
        int                      lmtp;          /* use LMTP protocol */
Index: smtp_client.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtp_client.c,v
retrieving revision 1.14
diff -u -p -r1.14 smtp_client.c
--- smtp_client.c       24 Apr 2020 11:34:07 -0000      1.14
+++ smtp_client.c       19 Jan 2021 09:44:34 -0000
@@ -187,7 +187,7 @@ smtp_cert_verified(struct smtp_client *p
 void
 smtp_set_tls(struct smtp_client *proto, void *ctx)
 {
-       io_start_tls(proto->io, ctx);
+       io_connect_tls(proto->io, ctx, proto->params.tls_servname);
 }
 
 void
@@ -624,8 +624,13 @@ smtp_client_io(struct io *io, int evt, v
 
        case IO_TLSREADY:
                proto->flags |= FLAG_TLS;
-               io_pause(proto->io, IO_IN);
-               smtp_verify_server_cert(proto->tag, proto, io_tls(proto->io));
+               if (proto->state == STATE_INIT)
+                       smtp_client_state(proto, STATE_BANNER);
+               else {
+                       /* Clear extensions before re-issueing an EHLO command. 
*/
+                       proto->ext = 0;
+                       smtp_client_state(proto, STATE_EHLO);
+               }
                break;
 
        case IO_DATAIN:
@@ -646,10 +651,6 @@ smtp_client_io(struct io *io, int evt, v
                break;
 
        case IO_ERROR:
-               smtp_client_abort(proto, FAIL_CONN, io_error(io));
-               break;
-
-       case IO_TLSERROR:
                smtp_client_abort(proto, FAIL_CONN, io_error(io));
                break;
 
Index: smtp_session.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtp_session.c,v
retrieving revision 1.428
diff -u -p -r1.428 smtp_session.c
--- smtp_session.c      21 Dec 2020 11:44:07 -0000      1.428
+++ smtp_session.c      19 Jan 2021 11:06:22 -0000
@@ -38,6 +38,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <tls.h>
 #include <unistd.h>
 #include <vis.h>
 
@@ -184,7 +185,8 @@ static void smtp_getnameinfo_cb(void *, 
 static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *);
 static void smtp_connected(struct smtp_session *);
 static void smtp_send_banner(struct smtp_session *);
-static void smtp_tls_verified(struct smtp_session *);
+static void smtp_tls_init(struct smtp_session *);
+static void smtp_tls_started(struct smtp_session *);
 static void smtp_io(struct io *, int, void *);
 static void smtp_enter_state(struct smtp_session *, int);
 static void smtp_reply(struct smtp_session *, char *, ...);
@@ -193,10 +195,6 @@ static void smtp_rfc4954_auth_plain(stru
 static void smtp_rfc4954_auth_login(struct smtp_session *, char *);
 static void smtp_free(struct smtp_session *, const char *);
 static const char *smtp_strstate(int);
-static void smtp_cert_init(struct smtp_session *);
-static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t);
-static void smtp_cert_verify(struct smtp_session *);
-static void smtp_cert_verify_cb(void *, int);
 static void smtp_auth_failure_pause(struct smtp_session *);
 static void smtp_auth_failure_resume(int, short, void *);
 
@@ -1066,17 +1064,26 @@ smtp_session_imsg(struct mproc *p, struc
 }
 
 static void
-smtp_tls_verified(struct smtp_session *s)
+smtp_tls_init(struct smtp_session *s)
 {
-       X509 *x;
+       io_set_read(s->io);
+       io_accept_tls(s->io, s->listener->tls);
+}
 
-       x = SSL_get_peer_certificate(io_tls(s->io));
-       if (x) {
+static void
+smtp_tls_started(struct smtp_session *s)
+{
+       if (tls_peer_cert_provided(io_tls(s->io))) {
                log_info("%016"PRIx64" smtp "
-                   "client-cert-check result=\"%s\"",
+                   "cert-check result=\"%s\" fingerprint=\"%s\"",
                    s->id,
-                   (s->flags & SF_VERIFIED) ? "success" : "failure");
-               X509_free(x);
+                   (s->flags & SF_VERIFIED) ? "verified" : "unchecked",
+                   tls_peer_cert_hash(io_tls(s->io)));
+       }
+       else {
+               log_info("%016"PRIx64" smtp "
+                   "cert-check result=\"no certificate presented\"",
+                   s->id);
        }
 
        if (s->listener->flags & F_SMTPS) {
@@ -1105,14 +1112,16 @@ smtp_io(struct io *io, int evt, void *ar
 
        case IO_TLSREADY:
                log_info("%016"PRIx64" smtp tls ciphers=%s",
-                   s->id, ssl_to_text(io_tls(s->io)));
+                   s->id, tls_to_text(io_tls(s->io)));
 
-               smtp_report_link_tls(s, ssl_to_text(io_tls(s->io)));
+               smtp_report_link_tls(s, tls_to_text(io_tls(s->io)));
 
                s->flags |= SF_SECURE;
+               if (s->listener->flags & F_TLS_VERIFY)
+                       s->flags |= SF_VERIFIED;
                s->helo[0] = '\0';
 
-               smtp_cert_verify(s);
+               smtp_tls_started(s);
                break;
 
        case IO_DATAIN:
@@ -1193,7 +1202,7 @@ smtp_io(struct io *io, int evt, void *ar
 
                /* Wait for the client to start tls */
                if (s->state == STATE_TLS) {
-                       smtp_cert_init(s);
+                       smtp_tls_init(s);
                        break;
                }
 
@@ -2071,7 +2080,7 @@ static void
 smtp_proceed_connected(struct smtp_session *s)
 {
        if (s->listener->flags & F_SMTPS)
-               smtp_cert_init(s);
+               smtp_tls_init(s);
        else
                smtp_send_banner(s);
 }
@@ -2261,112 +2270,6 @@ smtp_mailaddr(struct mailaddr *maddr, ch
 }
 
 static void
-smtp_cert_init(struct smtp_session *s)
-{
-       const char *name;
-       int fallback;
-
-       if (s->listener->pki_name[0]) {
-               name = s->listener->pki_name;
-               fallback = 0;
-       }
-       else {
-               name = s->smtpname;
-               fallback = 1;
-       }
-
-       if (cert_init(name, fallback, smtp_cert_init_cb, s))
-               tree_xset(&wait_ssl_init, s->id, s);
-}
-
-static void
-smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert,
-    size_t cert_len)
-{
-       struct smtp_session *s = arg;
-       void *ssl, *ssl_ctx;
-
-       tree_pop(&wait_ssl_init, s->id);
-
-       if (status == CA_FAIL) {
-               log_info("%016"PRIx64" smtp disconnected "
-                   "reason=ca-failure",
-                   s->id);
-               smtp_free(s, "CA failure");
-               return;
-       }
-
-       ssl_ctx = dict_get(env->sc_ssl_dict, name);
-       ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY);
-       io_set_read(s->io);
-       io_start_tls(s->io, ssl);
-}
-
-static void
-smtp_cert_verify(struct smtp_session *s)
-{
-       const char *name;
-       int fallback;
-
-       if (s->listener->ca_name[0]) {
-               name = s->listener->ca_name;
-               fallback = 0;
-       }
-       else {
-               name = s->smtpname;
-               fallback = 1;
-       }
-
-       if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) 
{
-               tree_xset(&wait_ssl_verify, s->id, s);
-               io_pause(s->io, IO_IN);
-       }
-}
-
-static void
-smtp_cert_verify_cb(void *arg, int status)
-{
-       struct smtp_session *s = arg;
-       const char *reason = NULL;
-       int resume;
-
-       resume = tree_pop(&wait_ssl_verify, s->id) != NULL;
-
-       switch (status) {
-       case CERT_OK:
-               reason = "cert-ok";
-               s->flags |= SF_VERIFIED;
-               break;
-       case CERT_NOCA:
-               reason = "no-ca";
-               break;
-       case CERT_NOCERT:
-               reason = "no-client-cert";
-               break;
-       case CERT_INVALID:
-               reason = "cert-invalid";
-               break;
-       default:
-               reason = "cert-check-failed";
-               break;
-       }
-
-       log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason);
-
-       if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) {
-               log_info("%016"PRIx64" smtp disconnected "
-                   " reason=%s", s->id,
-                   reason);
-               smtp_free(s, "SSL certificate check failed");
-               return;
-       }
-
-       smtp_tls_verified(s);
-       if (resume)
-               io_resume(s->io, IO_IN);
-}
-
-static void
 smtp_auth_failure_resume(int fd, short event, void *p)
 {
        struct smtp_session *s = p;
@@ -2844,7 +2747,6 @@ static void
 smtp_message_begin(struct smtp_tx *tx)
 {
        struct smtp_session *s;
-       X509 *x;
        int     (*m_printf)(struct smtp_tx *, const char *, ...);
 
        m_printf = smtp_message_printf;
@@ -2879,13 +2781,11 @@ smtp_message_begin(struct smtp_tx *tx)
            tx->msgid);
 
        if (s->flags & SF_SECURE) {
-               x = SSL_get_peer_certificate(io_tls(s->io));
                m_printf(tx, " (%s:%s:%d:%s)",
-                   SSL_get_version(io_tls(s->io)),
-                   SSL_get_cipher_name(io_tls(s->io)),
-                   SSL_get_cipher_bits(io_tls(s->io), NULL),
-                   (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO"));
-               X509_free(x);
+                   tls_conn_version(io_tls(s->io)),
+                   tls_conn_cipher(io_tls(s->io)),
+                   tls_conn_cipher_strength(io_tls(s->io)),
+                   (s->flags & SF_VERIFIED) ? "YES" : "NO");
 
                if (s->listener->flags & F_RECEIVEDAUTH) {
                        m_printf(tx, " auth=%s",
Index: smtpc.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpc.c,v
retrieving revision 1.13
diff -u -p -r1.13 smtpc.c
--- smtpc.c     29 Dec 2020 12:17:54 -0000      1.13
+++ smtpc.c     19 Jan 2021 09:58:18 -0000
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <syslog.h>
+#include <tls.h>
 #include <unistd.h>
 
 #include <openssl/ssl.h>
@@ -48,8 +49,7 @@ static struct addrinfo *res0, *ai;
 static struct smtp_params params;
 static struct smtp_mail mail;
 static const char *servname = NULL;
-
-static SSL_CTX *ssl_ctx;
+static struct tls_config *tls_config;
 
 static void
 usage(void)
@@ -156,16 +156,20 @@ main(int argc, char **argv)
                mail.rcptcount = argc;
        }
 
-       ssl_init();
+       tls_init();
        event_init();
 
-       ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL);
-       if (!SSL_CTX_load_verify_locations(ssl_ctx,
-           X509_get_default_cert_file(), NULL))
-               fatal("SSL_CTX_load_verify_locations");
-       if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method()))
-               fatal("SSL_CTX_set_ssl_version");
-       SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL);
+       tls_config = tls_config_new();
+       if (tls_config == NULL)
+               fatal("tls_config_new");
+       if (tls_config_set_ca_file(tls_config, tls_default_ca_cert_file()) == 
-1)
+               fatal("tls_set_ca_file");
+       if (!params.tls_verify) {
+               tls_config_insecure_noverifycert(tls_config);
+               tls_config_insecure_noverifyname(tls_config);
+               tls_config_insecure_noverifytime(tls_config);
+       } else
+               tls_config_verify(tls_config);
 
        if (pledge("stdio inet dns tmppath", NULL) == -1)
                fatal("pledge");
@@ -282,6 +286,7 @@ parse_server(char *server)
 
        if (servname == NULL)
                servname = host;
+       params.tls_servname = servname;
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
@@ -399,11 +404,16 @@ smtp_verify_server_cert(void *tag, struc
 void
 smtp_require_tls(void *tag, struct smtp_client *proto)
 {
-       SSL *ssl = NULL;
+       struct tls *tls;
+
+       tls = tls_client();
+       if (tls == NULL)
+               fatal("tls_client");
+
+       if (tls_configure(tls, tls_config) == -1)
+               fatal("tls_configure");
 
-       if ((ssl = SSL_new(ssl_ctx)) == NULL)
-               fatal("SSL_new");
-       smtp_set_tls(proto, ssl);
+       smtp_set_tls(proto, tls);
 }
 
 void
Index: smtpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpd.c,v
retrieving revision 1.336
diff -u -p -r1.336 smtpd.c
--- smtpd.c     31 Dec 2020 08:27:15 -0000      1.336
+++ smtpd.c     19 Jan 2021 09:45:44 -0000
@@ -49,6 +49,7 @@
 #include <string.h>
 #include <sysexits.h>
 #include <time.h>
+#include <tls.h>
 #include <unistd.h>
 
 #include <openssl/ssl.h>
@@ -609,7 +610,7 @@ main(int argc, char *argv[])
 
        env->sc_opts |= opts;
 
-       ssl_init();
+       tls_init();
 
        if (parse_config(conf, conffile, opts))
                exit(1);
Index: smtpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpd.h,v
retrieving revision 1.661
diff -u -p -r1.661 smtpd.h
--- smtpd.h     19 Jan 2021 09:16:20 -0000      1.661
+++ smtpd.h     19 Jan 2021 11:47:07 -0000
@@ -542,6 +542,10 @@ struct listener {
        TAILQ_ENTRY(listener)    entry;
 
        int                      local;         /* there must be a better way */
+
+       struct tls              *tls;
+       struct pki              **pki;
+       int                      pkicount;
 };
 
 struct smtpd {
@@ -1176,6 +1180,7 @@ struct dispatcher_remote {
 
        char    *source;
 
+       struct tls_config *tls_config;
        char    *ca;
        char    *pki;
 
@@ -1690,6 +1695,7 @@ const char *rule_to_text(struct rule *);
 const char *sockaddr_to_text(const struct sockaddr *);
 const char *mailaddr_to_text(const struct mailaddr *);
 const char *expandnode_to_text(struct expandnode *);
+const char *tls_to_text(struct tls *);
 
 
 /* util.c */
Index: ssl.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/ssl.c,v
retrieving revision 1.93
diff -u -p -r1.93 ssl.c
--- ssl.c       5 Jun 2019 06:40:13 -0000       1.93
+++ ssl.c       19 Jan 2021 08:41:42 -0000
@@ -450,3 +450,59 @@ ssl_ctx_fake_private_key(SSL_CTX *ctx, c
 
        return (ret);
 }
+
+static void
+hash_x509(X509 *cert, char *hash, size_t hashlen)
+{
+       static const char       hex[] = "0123456789abcdef";
+       size_t                  off;
+       char                    digest[EVP_MAX_MD_SIZE];
+       int                     dlen, i;
+
+       if (X509_pubkey_digest(cert, EVP_sha256(), digest, &dlen) != 1)
+               fatalx("%s: X509_pubkey_digest failed", __func__);
+
+       if (hashlen < 2 * dlen + sizeof("SHA256:"))
+               fatalx("%s: hash buffer to small", __func__);
+
+       off = strlcpy(hash, "SHA256:", hashlen);
+
+       for (i = 0; i < dlen; i++) {
+               hash[off++] = hex[(digest[i] >> 4) & 0x0f];
+               hash[off++] = hex[digest[i] & 0x0f];
+       }
+       hash[off] = 0;
+}
+
+char *
+ssl_pubkey_hash(const char *buf, off_t len)
+{
+#define TLS_CERT_HASH_SIZE     128
+       BIO             *in;
+       X509            *x509 = NULL;
+       char            *hash = NULL;
+
+       if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
+               log_warnx("%s: BIO_new_mem_buf failed", __func__);
+               return NULL;
+       }
+
+       if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL) {
+               log_warnx("%s: PEM_read_bio_X509 failed", __func__);
+               goto fail;
+       }
+
+       if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) {
+               log_warn("%s: malloc", __func__);
+               goto fail;
+       }
+       hash_x509(x509, hash, TLS_CERT_HASH_SIZE);
+
+fail:
+       BIO_free(in);
+
+       if (x509)
+               X509_free(x509);
+
+       return hash;
+}
Index: ssl.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/ssl.h,v
retrieving revision 1.21
diff -u -p -r1.21 ssl.h
--- ssl.h       18 Sep 2019 11:26:30 -0000      1.21
+++ ssl.h       19 Jan 2021 09:06:14 -0000
@@ -15,6 +15,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <openssl/ssl.h>
+
 #define SSL_CIPHERS            "HIGH:!aNULL:!MD5"
 #define        SSL_SESSION_TIMEOUT     300
 
@@ -62,6 +64,7 @@ int           ssl_load_pkey(const void *, size_t,
                    X509 **, EVP_PKEY **);
 int            ssl_ctx_fake_private_key(SSL_CTX *, const void *, size_t,
                    char *, off_t, X509 **, EVP_PKEY **);
+char *ssl_pubkey_hash(const char *, off_t);
 
 /* ssl_privsep.c */
 int            ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char 
**);
Index: to.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/to.c,v
retrieving revision 1.45
diff -u -p -r1.45 to.c
--- to.c        19 Jan 2021 09:16:20 -0000      1.45
+++ to.c        19 Jan 2021 11:47:07 -0000
@@ -43,6 +43,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#if IO_TLS
+#include <tls.h>
+#endif
 #include <unistd.h>
 
 #include "smtpd.h"
@@ -795,3 +798,18 @@ alias_is_error(struct expandnode *alias,
        alias->type = EXPAND_ERROR;
        return 1;
 }
+
+#if IO_TLS
+const char *
+tls_to_text(struct tls *tls)
+{
+       static char buf[256];
+
+       (void)snprintf(buf, sizeof buf, "%s:%s:%d",
+           tls_conn_version(tls),
+           tls_conn_cipher(tls),
+           tls_conn_cipher_strength(tls));
+
+       return (buf);
+}
+#endif
Index: smtp/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtp/Makefile,v
retrieving revision 1.3
diff -u -p -r1.3 Makefile
--- smtp/Makefile       18 Sep 2019 11:26:30 -0000      1.3
+++ smtp/Makefile       19 Jan 2021 09:08:22 -0000
@@ -17,7 +17,7 @@ SRCS+=        ssl_verify.c
 
 CPPFLAGS+= -DIO_TLS
 
-LDADD+=        -levent -lutil -lssl -lcrypto -lm -lz
-DPADD+=        ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ}
+LDADD+=        -levent -lutil -ltls -lssl -lcrypto -lm -lz
+DPADD+=        ${LIBEVENT} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBM} 
${LIBZ}
 
 .include <bsd.prog.mk>
Index: smtpd/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpd/Makefile,v
retrieving revision 1.110
diff -u -p -r1.110 Makefile
--- smtpd/Makefile      31 Dec 2020 08:27:15 -0000      1.110
+++ smtpd/Makefile      19 Jan 2021 08:36:15 -0000
@@ -82,8 +82,8 @@ SRCS+=                stat_ramstat.c
 MAN=           sendmail.8 smtpd.8 smtpd.conf.5 table.5
 BINDIR=                /usr/sbin
 
-LDADD+=                -levent -lutil -lssl -lcrypto -lm -lz
-DPADD+=                ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} 
${LIBZ}
+LDADD+=                -levent -lutil -ltls -lssl -lcrypto -lm -lz
+DPADD+=                ${LIBEVENT} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} 
${LIBM} ${LIBZ}
 
 CFLAGS+=       -fstack-protector-all
 CFLAGS+=       -I${.CURDIR}/..

Reply via email to